Step 2(10%):数据从哪来,以及怎么描述它
KDD 把这一步称为「creating a target data set:selecting a data set, or focusing on a subset of variables or data samples」。注意它说的是创建——你不是被动接收一份数据,你是从可得的数据里划出一份要分析的目标数据集,而这个划分动作本身需要理由。这一章处理 2.1 和 2.2;探索与质量(2.3、2.4)在下一章。
2.1 数据的身份证:七件事
这一小节短,但每一项缺了都是明确的扣分点。它的检验标准是:一个陌生人拿着你的报告,能不能重新拿到同一份数据。
| # | 要写 | 示例 |
|---|---|---|
| 1 | 来源机构 + 完整 URL | World Bank Open Data,https://data.worldbank.org/indicator/SH.DYN.MORT |
| 2 | 获取日期 | 2026-07-26 下载(写日期,因为这类数据会被回溯修订) |
| 3 | 许可 / 使用条款 | CC BY 4.0,允许学术使用,需标注来源 |
| 4 | 规模:行 × 列 + 文件大小 | 487 行 × 16 列,约 62 KB CSV |
| 5 | 粒度(一行是什么)+ 时间跨度 | 一个地区一年;2015–2023 共 9 年、6 个地区 |
| 6 | 你做了什么筛选,以及为什么 | 「原始面板含 40 个指标,本研究选取 7 个与 SDG 3.2 直接相关的社会经济与卫生服务指标,理由见下表;未选取的指标中,X 缺失率超过 60%,Y 与目标定义重复。」 |
| 7 | 多来源时:怎么拼的 | 「人口数据来自 A,卫生服务数据来自 B,按(地区码、年)左连接,匹配率 96.3%,未匹配的 18 行处理方式见 3.4。」 |
第 6 项是 2.1 的真正得分点,因为它对应 KDD 那句「focusing on a subset of variables」。「我全都要」不是一个选择,「我选了这 7 列,因为……」才是。
公开数据集会更新。世界银行会回溯修订历史数据,Kaggle 的数据集作者会上传新版本。如果你在第 3 周下载了一份、第 10 周重新下载做 BDAS,两次迭代的数字对不上,而你无法解释——这在四遍结构里是致命的,因为跨产线对比是 Step 8 的加分项。
做法:把原始文件原样存一份(不要覆盖),命名带日期,例如 u5mr_worldbank_2026-07-26_raw.csv,四条产线全部从这一份读。顺手把它连同 Notebook 一起放进 GitHub 仓库(BDAS 那次要用 GitHub,正好提前建)。
2.2 描述数据:逐列的体检报告
这一小节最常见的做法是把 df.describe() 或 SPSS 的 Data Audit 截图往上一贴。这样只能拿到一半分,因为 describe 给的是统计量,而 2.2 要的是你对每一列的理解:它是什么、单位是什么、合法取值范围是什么、现在这个分布合不合理。
先看机器能给你的那一半。下面这台体检台在本书的示例数据(480 行合成 SDG 3 数据,注入了六种脏)上真的跑了一遍——切换「原始/清洗后」看差别:
原始那一栏里有三处红色,每一处都是一类典型问题,而它们全都能只靠这一张表发现:
gdp_pc最大值 8 641 900,标准差 398 901,偏度 20.82。人均 GDP 八百多万美元不存在——这是量纲错误(多打了三个零)。注意它把标准差和偏度都污染了,所以任何基于均值和标准差的判断在这一列上暂时都不可信(第 10 章会证明 z-score 法在这里彻底失效)。vaccine最大值 999,而这是一个百分比列。哨兵值——某人用 999 表示「未测量」。它不是异常值,是伪装成数字的缺失值。doctors缺 7.4%、literacy缺 11.3%。真正的缺失。而这两个缺失的性质完全不同:一个是完全随机的,一个不是(下一章会用数据证明)。
清洗之后同一张表:偏度从 20.82 降到 3.39,vaccine 回到 47.9–99 的合理区间,缺失全为 0,行数从 487 变 480(去掉 7 条重复)。把这两张表并排放进报告,就是 2.2 → 2.4 → 3.2 的完整证据链。
数据字典:那另一半分
机器给不了的部分,要你手写。一列一行,六列信息:
| 列名 | 业务含义 | 类型 | 单位 | 合法范围 | 观察到的问题 |
|---|---|---|---|---|---|
region | 行政区 | 类别(6 类) | — | 6 个固定取值 | 大小写与尾空格不一致(128 处),实际唯一值 > 6 |
year | 统计年份 | 整数 | 年 | 2015–2023 | 无 |
record_date | 录入日期 | 文本 | — | ISO 8601 | 两种格式混用(94 处为 DD/MM/YYYY) |
gdp_pc | 人均 GDP | 连续 | 美元 | 100–20 000 | 4 行超出上界 100 倍 → 量纲错 |
doctors | 医生密度 | 连续 | 人/千人 | 0–10 | 缺失 36 格(7.4%) |
vaccine | 疫苗覆盖率 | 连续 | % | 0–100 | 5 行为 999(哨兵值) |
literacy | 女性识字率 | 连续 | % | 0–100 | 缺失 55 格(11.3%),疑似非随机 |
u5mr | 5 岁以下死亡率 | 连续 | ‰ | 0–200 | 目标变量 |
rate_reported | 上报死亡率 | 整数 | ‰ | 0–200 | 与目标同源,不可作特征(r = 0.9998) |
「合法范围」这一列是自己写的,不是从数据里算的——它是你的领域判断,也是发现问题的尺子。先写下「疫苗覆盖率合法范围 0–100」,再去看数据,999 就自己跳出来了。反过来,如果你只看 describe 的输出,很容易把 999 当成一个「较高的值」放过去。
书里所有 Demo 跑的都是一份合成数据:480 行「地区-年」,7 个特征,目标是 5 岁以下儿童死亡率,二分阈值取 SDG 3.2 的真实指标 25‰。生成规则写在 engine.js 里,固定随机种子 7220,任何人重跑都得到同一份。
用合成数据的理由有两个。第一,我不能凭记忆编造 WHO 的真实数字——那会变成假数据冒充真数据,比用合成数据糟糕得多。第二,也是更重要的:合成数据的「正确答案」是已知的。我知道 spend 那一列是纯噪声,所以当 PCA 给它单开一个主成分(第 12 章)、当决策树在深处劈它一刀(第 15 章),我能明确指出「这就是过拟合的样子」。真实数据做不到这一点。
你自己的报告当然要用真实数据。这份合成数据的用途是:让你在动手之前,先看清每一种脏数据在指标上留下的痕迹长什么样。
类别列和时间列不要偷懒
describe() 默认只统计数值列,于是很多报告的 2.2 里,类别列一个字都没有。而类别列的问题往往最致命——本书示例里 region 只该有 6 个取值,实际因为大小写和空格产生了 18 个:
> df['region'].value_counts() East 64 North 61 South 60 Coastal 60 West 57 Central 57 WEST 15 <- 全大写 coastal 15 <- 小写 + 尾随空格 central 14 CENTRAL 12 south 12 north 12 SOUTH 11 east 8 west 8 EAST 8 NORTH 7 COASTAL 6 Name: count, dtype: int64 > df['region'].nunique() 18 <- 应该是 6
这个问题如果不在 Step 3.2 修掉,后果是:独热编码会生成 18 列而不是 5 列,模型把「North」和「NORTH」当成两个不同地区,而你的准确率不会因此明显下降(所以你发现不了),只是模型悄悄变差、结论悄悄变得没法解释。
对时间列,2.2 要写三件事:格式(是否统一)、跨度(有没有缺年份)、是否等间隔。如果你的数据是面板(多个单元 × 多期),还要检查每个单元的期数是否一致——不一致意味着后面按时间划分训练测试集时会有单元凭空出现或消失。
| ISAS · SPSS Modeler | OSAS · Python | BDAS · PySpark | |
|---|---|---|---|
| 逐列统计 | Data Audit 节点(一次给出类型、缺失、最小最大、均值、偏度、直方图,是三条线里最省事的) | df.describe(include='all')、df.info()、df.isna().sum() | df.describe().show()、df.summary();缺失要自己 agg(count(when(col.isNull(),1))) |
| 类别列 | Data Audit 自带唯一值计数 | df['region'].value_counts() | df.groupBy('region').count().show() |
| 截图证据 | Data Audit 输出表直接截图 | Notebook 单元输出 | .show() 的文本输出 |
提示:ISAS 那次的 Data Audit 输出是四条产线里最漂亮的一张表,把它截图存好,OSAS 和 BDAS 的报告里可以引用「与 Iteration 2 的 Data Audit 结果一致」,省下重排版的时间。
1. Step 2.1(半页):七件事逐条写清,含 URL、下载日期、许可、规模、粒度、选列理由、多源拼接方式与匹配率。
2. Step 2.2(一页半):机器统计表(Data Audit / describe 截图)+手写数据字典表(列名、业务含义、类型、单位、合法范围、观察到的问题)。两张表都要,缺一张扣一半。
3. 类别列与时间列各一小段:唯一值计数(暴露大小写问题)、时间格式与跨度、面板是否平衡。
4. 一个文件命名约定:<主题>_<来源>_<下载日期>_raw.csv,并在报告里写出来。可复现性是论文那 17% 的评分项之一。
这一章的一句话
2.1 的检验标准是「陌生人能否重新拿到同一份数据」,2.2 的检验标准是「每一列你都写出了它的单位与合法范围」——合法范围是你的领域判断,而 999 这样的哨兵值只有在你先写下「0–100」之后才会自己跳出来。
下一章把数据打开来看:直方图、相关矩阵、散点图,以及那张决定 Step 3 全部工作的数据质量清单。它也是提案的最后一块。