让 AI 写数据分析脚本:口径没说清,算得再快也是错的
数据截至 2026-07。文中涉及的 pandas、SQL 函数行为与时区默认值会随版本变化,请以你所用版本的官方文档为准;不同数据仓库对空值、去重和时间边界的语义也有差异,落地前先在自己的环境里把验证代码跑一遍。
分析脚本算错,多数时候不是模型写错了代码,而是你没把口径说清楚,模型替你补了一个看上去合理的默认值。 这件事最难受的地方在于:脚本能跑,不报错,数字也在合理区间,评审的时候没人看得出问题,等到被业务方拿去对账才发现活跃用户少算了一截。你回头去看代码,每一行都写得挺规矩——去重了、过滤了、按天聚合了,只是它去重用的是 user_id,而你们业务口径里同一个人可能有多个 user_id;它过滤掉了状态为退款的订单,而你们的口径是退款也要算进 GMV,只在净收入里扣。模型没有编造,它只是在你留白的地方做了选择。
所以排查的入口不是”模型行不行”,而是”这个结果错在哪一层”。下面按可执行的顺序走。
一、先分因:把三类错拆开
一个数字不对,可能的成因只有三类,混在一起查会绕很久。
第一类是口径错。 代码正确执行了某个定义,但那个定义不是你要的。特征是数字稳定可复现,换个人跑、换台机器跑都是同一个错值,而且错得”很整齐”——比如恒定少 12%,或者每个月都少同一批人。这类错误最贵,因为它不触发任何异常。
第二类是数据错。 定义对、代码对,但进去的数据本身有问题:分页拉取时漏了最后一页、时间字段是本地时间和 UTC 混着存的、某个渠道的表当天没同步完就被读了。特征是结果不稳定,同一份脚本今天跑和明天跑不一样,或者只有某几个分组的数字离谱。
第三类是代码错。 类型截断、精度丢失、连接条件写成了笛卡尔积、分组键漏了一个维度。特征往往是数量级异常或者行数异常,比较容易被发现。
判别顺序建议倒过来:先用行数和数量级排掉第三类(最快),再用重跑一致性排掉第二类,剩下的基本都是第一类。很多人下意识先怀疑模型代码,结果在最不可能的地方花了最多时间。
顺带说清楚本篇和站内几篇相邻文章的分工:数值本身怎么算才不丢精度,那是 金额浮点精度 的题目;把分析动作接到表格和日常流程里怎么落地,看 AI 自动化 Excel;这篇只管一件事——在开写之前和跑完之后,怎么保证脚本算的是你要的那个东西。
二、判别表:现象对应成因和动作
| 现象 | 大概率成因 | 怎么验证 | 处置动作 |
|---|---|---|---|
| 数字稳定但一直偏小/偏大,比例固定 | 口径错:去重键、过滤条件或归属规则与业务定义不一致 | 手工挑 10 条已知答案的样本,逐条对脚本的中间结果 | 把口径写成显式清单,让脚本按清单逐条实现,每条对应一段可单独输出的中间表 |
| 同一脚本重跑结果不同 | 数据错:上游未跑完、分页漏取、增量任务重叠 | 固定一个历史时间窗重跑三次,对比行数与主键集合差异 | 加数据就绪判断,读取前先校验分区行数或水位,不满足就直接失败而不是继续算 |
| 只有某几个分组的值离谱 | 数据错:该来源字段含义不同,或存在脏枚举值 | 对该分组单独做取值分布统计,看有没有空串、未知枚举、异常时间 | 在清洗层显式列出允许的枚举,遇到未知值报错而不是静默归入”其他” |
| 结果行数远超预期 | 代码错:连接条件不唯一,多对多放大 | 对连接两侧的键各做一次唯一性统计 | 先把维表去重成一行一键,或改为先聚合再连接 |
| 跨月/跨天边界的数字对不上 | 口径错或数据错:时区与时间字段选错 | 挑边界时刻前后各几条记录,打印原始时间字段和转换后的值 | 统一存储时区并在脚本入口做一次显式转换,参考 时区时间戳错误 的处理方式 |
| 明细能对上,汇总对不上 | 代码错:聚合前的过滤与聚合后的过滤顺序颠倒 | 把中间结果落一张临时表,分别统计过滤前后行数 | 明确每一步是”先过滤后聚合”还是”先聚合后过滤”,在注释里写死 |
| 分页拉数据时总数忽多忽少 | 数据错:分页游标基于会变动的字段 | 连续两次全量拉取比对主键集合 | 换成基于稳定主键的游标,具体见 分页重复漏掉 |
这张表的用法是:先定位到行,再执行”怎么验证”那一列,验证通过了才做处置。跳过验证直接改代码,是返工率最高的做法。
三、口径必须人来定,这部分不要交给模型
模型可以写循环、写连接、写窗口函数,也可以帮你把一段口径描述翻译成 SQL 或 pandas 代码。它做不了的是替你决定业务上的选择。你至少要在动手前把下面这几件事写下来,一条一句话,不要留形容词:
- 统计对象是谁。 是设备、账号、还是自然人?三者去重后的量级完全不同。
- 去重键是什么。 写清楚字段名,以及一个人对应多个键时怎么合并。
- 时间归属规则。 一笔订单算在下单日还是支付日还是发货日?跨天的会话算哪一天?
- 口径边界。 测试账号、内部账号、退款订单、试用订单,各自算不算,算在哪个指标里。
- 空值和未知值怎么处理。 是丢弃、归入其他、还是让脚本直接报错。默认丢弃是最危险的选项。
- 对比基准。 这个数字将来要和什么对齐?和财务报表、和已有看板、还是和某张现成的表。基准决定了验收标准。
把这六条写在脚本顶部的注释里,比写在任何文档里都管用,因为它和代码一起被 review、一起被改。你让模型写代码的时候,把这段注释一起给它,它的自由发挥空间会显著变小。反过来,如果你只说”统计一下上个月的活跃用户”,那它必须猜,猜错了责任不在它。
这里有个容易混淆的点:模型在缺信息时给出合理但错误的默认值,和它凭空编造一个不存在的函数或字段,是两种不同的失效。后者比较好抓,代码直接报错;前者不报错,需要靠口径清单和样本对账才能发现。真正吃亏的都是前者。
四、AI 能帮到哪一步:分工与验收动作
明确了口径之后,把工作切成四段,每段有各自的交付物和验收方式。
第一段,探查数据。 这一步适合让模型写:字段分布、空值率、主键唯一性、时间范围、枚举取值清单。产出是一份统计输出,不是结论。你要看的是有没有超出预期的东西——比如时间范围里出现了 1970 年,那基本是时间戳单位搞混了。
第二段,写口径实现。 让模型按清单逐条实现,一条口径对应一个可单独执行的步骤,中间结果都能落盘或打印。不要接受一个从原始表直接算到最终数字的长查询,那种代码没法验证,只能整体信或整体不信。
第三段,对账验收。 这是唯一能证明脚本对的环节,也是最常被跳过的。可执行的做法有三个,从便宜到贵:
- 极值检查。总数、最大值、最小值、非空率,看有没有明显不可能的值。
- 已知答案对账。挑 5 到 10 个你手工能算出结果的样本,逐条对。样本要包含边界情况:跨月的、有退款的、多设备的。
- 交叉口径复核。用另一条路径算同一个数字,比如从明细汇总和从日汇总表相加,两边应该一致,不一致说明中间有一层过滤没对齐。
第四段,固化。 把验收里用到的检查写成脚本的一部分,让它每次跑都自己校验,不满足就退出非零。一段最朴素的守卫就够用:
# start / end 是本次统计窗口的起止时间,和读取数据时用的过滤条件保持同一个来源
assert df["user_id"].notna().all(), "存在空的去重键,检查上游清洗"
assert df["stat_date"].between(start, end).all(), "存在超出统计窗口的记录"
assert df["order_id"].is_unique, "订单主键不唯一,检查连接条件"
这三行不优雅,但它们把”下次别人改坏了没人知道”这个风险挡在了外面。让脚本在数据不对的时候直接失败,比让它输出一个错误数字要好得多。
有一个细节值得记住:Python 用 -O 参数启动时,assert 语句会被整体跳过,校验等于没写。本地手工跑一般不会带这个参数,但调度平台或容器镜像的启动命令里带了你未必知道。所以如果这段校验是要长期守在生产链路上的,稳妥做法是写成显式判断,条件不满足就主动抛异常或者退出非零:
if not df["order_id"].is_unique:
raise ValueError("订单主键不唯一,检查连接条件")
两种写法的取舍很直白:探查和一次性分析用 assert 足够,写起来快;进了调度、被别人接手的脚本,用显式判断。判断标准就一条——这段代码将来是不是由不了解口径的人来执行。
还有一点容易被忽略:校验失败时的提示信息里,最好带上具体是哪几条记录出了问题,而不是只说”有重复”。比如把不唯一的主键取前几个一起打印出来,排查的人不用再自己写一段查询去找。信息越具体,下一个人处理这次失败的时间越短。
另外提醒一句:探查阶段千万别用模型生成的假数据代替真实数据来验证逻辑。用造出来的数据跑通,只能证明代码语法没问题,证明不了口径对。假数据混进正式流程的后果,模拟数据进入生产 里说得比较细。
五、什么情况下别再折腾了
排查是有成本的,下面几种情况该停。
口径本身没有结论时,停。 如果你追到最后发现业务方内部对”活跃”的定义有分歧,那这不是技术问题,继续改代码只是在两个错误之间来回切换。正确动作是把分歧摆到台面上,让能拍板的人定,定不了就先出两个口径的数字并标注差异来源。
同一个数字改了三轮还对不上,停下来重写。 反复打补丁的脚本会积累互相冲突的过滤条件,越改越难验证。这时候扔掉重来往往比继续修快,前提是你手上有第三段那份对账样本——有了它,重写只需要几十分钟就能验证。
上游数据质量本身不达标时,别在下游硬补。 如果源表里同一个业务含义有三种编码方式、时间字段一半是字符串一半是时间戳,你在分析脚本里写再多兼容分支也守不住,下次上游再加一种编码就又破了。合理的处置是把问题反馈到数据生产侧,同时在自己这边加一道硬校验:遇到未知编码就失败,而不是猜。
回滚点在哪里。 分析脚本的回滚不是回滚代码,是回滚已经发出去的数字。所以每次对外给数时,记下脚本的版本(提交号就行)、跑的时间窗、以及口径清单的快照。数字被质疑时你能一分钟内还原当时算的是什么。做法很简单:
git rev-parse --short HEAD
把这个值和结果一起写进输出文件的头部。没有这一步,三周后没人能说清那份报表是怎么来的。
换条路的判断依据。 如果这个分析需要每天跑、被多个部门引用、且口径还在变,那它就不该是一个人手里的脚本,应该沉淀成有测试、有调度、有告警的正式任务。判断线大致是:被引用的场景超过两个,或者连续跑了一个月以上,就该迁走。继续用临时脚本维护,出事只是时间问题。
六、避坑清单
坑一:让模型一次写完整个分析。 会踩是因为省事,一句话描述加一段长代码看起来效率最高。避法是拆步骤,每步有中间输出。长查询最大的问题不是难写,是没法定位错在哪一层。
坑二:接受”看起来合理”的默认处理。 会踩是因为模型给的默认值通常确实是行业常见做法,你扫一眼觉得没毛病。避法是对每一处过滤和每一处去重都问一句”这是谁定的”,答不上来的就是隐患。
坑三:用样本数据验证后直接上全量。 会踩是因为小数据跑得快、结果好看。避法是样本必须包含边界记录,或者干脆用一个历史时间窗的全量数据做验证,反正只跑一次。
坑四:时间字段不做显式转换。 会踩是因为很多环境默认时区和你以为的不一样,代码里不写就用了系统默认,本机和服务器还可能不同。避法是在读入后第一件事就做一次显式转换并打印前后各三条对照。
坑五:静默地把未知值归入”其他”。 会踩是因为这样脚本永远不会挂,看着很稳。避法是反过来——未知值直接抛异常。你需要的是知道数据变了,不是把变化藏起来。
坑六:连接前不检查键的唯一性。 会踩是因为默认维表就是一行一键,而现实中维表经常带历史版本。避法是连接前对两侧键各统计一次唯一性,不唯一就先想清楚保留哪一条。
坑七:改完不重跑历史。 会踩是因为改完看当天数字对了就收工。避法是每次口径调整都回跑至少一个完整周期,和之前的数字做差异对比,说得清差在哪、为什么差。
坑八:把口径写在聊天记录里。 会踩是因为当时讨论清楚了就以为记住了。避法是所有口径决定必须落到代码注释或同目录的文本文件里,跟着代码一起进版本库。
坑九:外部工具的可用性想当然。 一部分海外的分析类工具和模型服务,官方对中国大陆存在区域限制、不支持直连,市面上有第三方中转但可靠性和合规性各不相同,这里不做推荐。真要依赖,先确认长期可用性再把它写进每日流程,否则某天流程断了你会很被动。
收尾:交付前的自检清单
分析脚本的价值在于结果可信,而可信是靠可验证撑起来的,不是靠代码写得快。交付一份数字之前,过一遍下面这七条:
- 口径清单写在代码里了吗?统计对象、去重键、时间归属、边界、空值处理、对比基准,六项齐不齐。
- 每一步有可单独查看的中间结果吗?
- 挑过 5 条以上手工能算的样本对过账吗?包含边界情况吗?
- 换一条路径算同一个数字,两边一致吗?
- 脚本里有硬断言吗?数据不对时它会失败还是会算出个数?
- 输出里带上了提交号、时间窗和口径快照吗?
- 上一次口径变更之后,历史数据回跑并解释过差异了吗?
有一条答不上来,那份数字就先别发出去。模型确实能把写脚本这段时间压掉一大截,但省下来的部分不该直接变成”更快交付”,而应该花在这七条上——写得快本来就不是这件事的瓶颈,算得对才是。