TT Lab
开始
学习 学习路径 课程

处理客户数据

校验全绿,数字仍然可疑

在 TT Lab 中继续学习

目标

构建工具 profile.py:通过每列的概要、交付之间的分布变化,以及首位数字分布,找出格式正确但值很奇怪的数据。放入本福特定律不适用的列作为反例,让代码自己说明这不是证据而是线索。

为什么重要

有时 schema 对,缺失值也符合规则,数字却很奇怪。这时只说“有问题”,没有人会行动。需要依据,而这个依据不是某一个值,而是分布。 光靠一份概要很难判断。因为异常不是绝对的值,而是通过与上一次交付的差异显现出来的。所以概要要在每次交付时都保存下来并进行比较,并且把阈值写在代码里:没有写下来的阈值,下一个人没法修改。 首位数字分布是既便宜又管用的信号,但也是最容易被误解的信号。分布偏离,意味着需要人来看,而不是有人做了手脚;而在值范围很窄的列上,即使数据是诚实的,也总会偏离。所以必须先判定是否适用。 评分器不会相信你的文字。它在临时目录里摆好评分器生成的交付数据,真实运行你的工具,并把概要和判定与它自己计算的值进行核对。交易方和金额每次运行都会变。

步骤

  1. 创建并运行 /root/prof/gen_feed.py,在 /root/prof/feed 下生成 2026-04.csv、2026-05.csv、2026-06.csv。三个都能通过格式验证。
  2. 在 /root/prof/profile.py 中实现 profile <파일>(占位符为文件),让它为每一列给出类型、缺失、唯一值比例、值长度、众数和范围。
  3. 增加 digits <파일> <칼럼>(占位符依次为文件、列名),让它给出值长度分布和偏离众数长度的值。
  4. 增加 drift <파일A> <파일B>(占位符依次为文件 A、文件 B),让它用阈值判定交付之间的分布变化。
  5. 增加 benford <파일> <칼럼>(占位符依次为文件、列名),让它给出首位数字分布与期望分布的差异,并把两个交付的对比写入 /root/prof/benford.json。
  6. 给 benford 增加适用性判定,对值范围很窄的列把 applicable 设为 false,并把 verdict 设为 not_applicable。结果写入 /root/prof/narrow.json。
  7. 把多个信号叠加起来,生成需要调查的对象清单,并把每一列接下来要确认的内容写入 /root/prof/leads.json。
  8. 把全部内容整理成一份报告,生成 /root/prof/prof_report.json 和 /root/prof/prof_report.md。

参考

生成三个月的交付数据

创建并运行 /root/prof/gen_feed.py,在 /root/prof/feed 下生成 2026-04.csv、2026-05.csv、2026-06.csv。其中两个月平平无奇,有一个月格式全都对,但值很奇怪。

生成金额时,要让数量级均匀分布:把指数均匀取值,再做成 10 的幂,首位分布就会变得自然。被动过手脚的那个月,要混入几个看起来像是人手工填写的金额,并同时放入交易方为空的行和电话号码位数不同的行。用固定种子的生成器代替标准库随机数,谁来运行都会得到同样的文件。

让每一列做自我介绍

在 /root/prof/profile.py 中实现 profile <파일>(占位符为文件),让它给出 rows 和 columns。每一列包含 type、nulls、null_rate、distinct、unique_ratio、len_min、len_max、modal_length、top,数值列再增加 min、max。

比例的分母是全部行数,长度和唯一值只统计去掉“没有”之后的值。top 要按条数降序排列,条数相同时按值的字典序,每次运行才会得到同样的答案。

把偏离值长度的值取出来看

增加 digits <파일> <칼럼>(占位符依次为文件、列名),让它给出 lengths、modal_length、outliers、sample_outliers。sample_outliers 是把不同的值排序后取前 3 个。

即使通过了格式检查的值,长度不同,也说明生成它的一方变了。把偏离的值直接取出几个来看,通常马上就能看出原因:如果是电话号码,就是缺了区号,或者连字符消失了。

与上一次交付比较

增加 drift <파일A> <파일B>(占位符依次为文件 A、文件 B),让它为每一列测量四种变化,并把超过阈值的放入 flags。响应中还要放入至少有一个 flags 的列的列表 flagged。

异常是通过差异而不是绝对值显现的。要看缺失比例 0.05、唯一值比例 0.20、众数占比 0.10,以及众数长度是否变了。把阈值提成常量,之后数据变了要修改时,只有一个地方要改。

测量首位数字的分布

增加 benford <파일> <칼럼>(占位符依次为文件、列名),让它给出 observed、expected、max_deviation、deviation_digit、verdict,并把平常月份和被动过手脚的月份针对 amount 的结果,以 normal、suspect 写入 /root/prof/benford.json。

期望比例对首位数字 d 为 log10(1 + 1/d)。首位是跳过符号、前导 0 和小数点之后遇到的第一个非 0 数字,值为 0 时不计入。人手工编造的金额会集中在特定的位数上,所以差异会拉得很大。

本福特定律不适用的列

给 benford 增加 span 和 applicable:条数不足 200,或者最大值与最小值之比不足 100,就视为不适用,并把 verdict 设为 not_applicable。把 score 列的结果,以 column、n、span、applicable、verdict、why 写入 /root/prof/narrow.json。

如果值只分布在很窄的区间里,首位就会被限制在少数几种,即使数据是诚实的,也会与期望分布大幅偏离。不做适用性判定就运行本福特定律,会不断对完好的数据发出告警,而这样一来就没有人再去读这些告警了。why 里用一两句话写明为什么不适用。

把信号叠加起来形成调查对象

把分布变化和本福特定律的结果叠加起来,在 /root/prof/leads.json 中写入 leads、not_leads、note。leads 的每个条目包含 column、signals、next_check,not_leads 中写入本福特定律不适用的列。

只命中一项的列和命中三项的列,处理方式应当不同。next_check 必须是“接下来要确认什么”,而不是“有问题”:比如看起来像手工填写的金额是否集中在某个负责人名下,位数变了的电话号码来自哪个系统。

把线索整理成一份交出去

在 /root/prof/prof_report.json 中写入 baseline、target、rows、flagged、benford、not_applicable、leads,并在 /root/prof/prof_report.md 中分 ## 무엇을 봤나 ## 지난 전달분과 무엇이 달라졌나 ## 첫 자리 숫자가 말해 주는 것 ## 무엇을 더 확인해야 하나 四节书写(韩文标题,依次意为“看了什么”“与上一次交付相比有什么变化”“首位数字说明了什么”“还需要确认什么”)。

benford 是包含 amount 列的 column、max_deviation、verdict 的对象。报告的第三节,在写本福特定律结果的同时,必须写明它为什么不是证据;第四节,为每一列写明接下来要确认的内容。把前面步骤留下的文件读取并合并即可。