新数据到手,先跑哪几步检查
一批新数据到手,检查都会做,难在每次做得一样:想到哪几项就查哪几项,换批数据、隔段时间,顺序和阈值都变了,跑完也没留下一张能照着重来的单子。
把五步检查按固定顺序固化成一个入口,每次输出同样格式的结果单,下一批数据来了只是重跑一次。
这篇按这个顺序把五步逐个摊开:每步看哪几个数、这些数能落下什么判断、什么情况要单独标出来。
下面用两期二手房挂牌数据走一遍,7600 行、16 列,字段固定、行数不同,是那种每个月都会重新拿到一份的表。
完整脚本文末分享,文中只贴关键几行和跑出来的结果。
1. 先给整张表做体检
第一遍几乎不做什么计算,只把每一列的类型、缺失率、唯一值数摆出来。
for c in df.columns:
s = df[c]
rows.append({"列": c, "类型": str(s.dtype),
"缺失率%": round(s.isna().mean() * 100, 1),
"唯一值": int(s.nunique(dropna=True))})
跑出来是这张体检表(节选):
列 类型 缺失率% 唯一值
朝向 str 20.5 5
装修 str 25.8 4
房源ID str 0.0 7600
面积 float64 0.0 7588
总价 float64 0.0 7600
挂牌天数 object 0.0 180
采集渠道 str 0.0 1
单价 float64 0.0 7600
[常量列] ['采集渠道']
[高基数列] ['房源ID', '面积', '总价', '单价']
[疑似类型误判] ['挂牌天数']
表里先冒出来三个信号。
采集渠道唯一值是 1,常量列,留着没用。房源ID7600 行 7600 个值,一列一个 ID,不能当类别特征,硬做编码只会把模型喂坏。挂牌天数类型那格是 object 而不是数字——里面混了百分之二的「未知」字符串,pandas 看见文本,整列就按文本处理,后面求均值、算天数差全都不成立。
这里还有个容易栽的地方:脚本按「唯一值超过行数九成就标高基数」扫出四列,面积、总价、单价 也被一起标上了,因为连续数值列本来就几乎行行不同。
所以高基数这个标记得限定在文本列上用,别拿它扫全表。
体检规则自己也会误报,这是第一处要留神的地方。
2. 缺的列到底为什么缺
第一遍报出来的缺失集中在两列:装修 25.8%、朝向 20.5%。
看到这个数第一反应通常是「填一下」,但先别急,得问一句为什么缺。
print(df.groupby("来源系统")["装修"]
.apply(lambda s: round(s.isna().mean() * 100, 1)).to_string())
按 来源系统 分开再跑一次,输出是:
各列缺失率(%):
装修 25.8
朝向 20.5
按来源系统分组看「装修」缺失率(%):
A系统 3.6
B系统 61.2
装修缺失的那些行里,朝向也缺的比例(%): 79.5
A系统 3.6%、B系统 61.2%,差了十几倍,说明这不是随机缺,而是跟着一个能观测到的字段走的。
B系统 那批录入时压根不填装修和朝向,这类缺失属于 MAR(随机缺失)——缺不缺跟别的变量有关,跟它自己本来该是什么值关系不大。79.5% 这个数又说明两列基本是同一批录入动作漏掉的。
这直接决定怎么填。
既然能不能观测到取决于来源系统,就该按系统分组处理,或者把「是否缺失」单独做成一个标记列;上来对整列取众数填一遍,等于把 B系统 那批房源硬说成是精装,后面算出来的装修溢价全是假的。
至于 MNAR(非随机缺失),得靠业务判断,脚本判不了,就不装能判。
还有个细节:体检表里 挂牌天数 报的是 0% 缺失,因为「未知」是字符串不是空值。
用 pd.to_numeric(..., errors="coerce") 强行转成数字之后,它才会变成真正的缺失。
原表就空着,和清洗过程中造出来的缺失,最好分开记。
这里关心的是「为什么缺」,跟「哪些时间点上少了一条记录」不是一回事:后者要靠一条完整时间轴把缺口对齐出来,属于另一类问题。
3. 数字列长什么样
确认哪些列能用之后,才轮到看形状。数值列看偏度和分位数就够,不用先画一堆直方图。
s = x[c].dropna()
rows.append({"列": c, "均值": round(s.mean(), 2),
"中位数": round(s.median(), 2), "偏度": round(s.skew(), 2)})
列 均值 中位数 偏度
面积 92.08 91.49 20.86
总价 222.11 185.93 0.94
单价 24548.13 19482.28 53.74
挂牌天数 90.12 91.00 0.00
户型频次 Top3:
4室2厅 1593
1室1厅 1520
2室1厅 1513
总价 均值 222.11 万、中位数 185.93 万,偏度 0.94。均值比中位数高出 36 万,接近两成,是典型的右偏——少数高总价房源把平均数拽上去了。
所以,汇报「平均一套 222 万」会明显偏高,说中位数更老实。
这类列后面进模型通常要取对数,但取完以后系数的解释就变成「面积变动百分之一,总价变动百分之几」,口径得跟着改。
户型频次那三行都在 1500 上下,五类没有哪一类压倒性多,不用额外合并稀有类。
同一张表里还有两个数字很扎眼:面积 偏度 20.86、单价 偏度 53.74。
正常房源面积不可能长出这种分布,这不是形状问题,是表里混进了不该有的值。
先记下来,下一步专门查。
4. 哪些行不对劲
偏度异常只是提示,得把具体是哪几行找出来。两种常用判定一起上,看它们认不认同彼此。
z = (s - s.mean()) / s.std()
m_iqr = (s < q1 - 1.5 * iqr) | (s > q3 + 1.5 * iqr)
m_z = z.abs() > 3
影响有多大,把共识的那几行剔掉再算一次:
clean = x[~both]
print(x["面积"].corr(x["总价"]), clean["面积"].corr(clean["总价"]))
两段跑完,输出就三行:
面积:IQR 命中 66 行,Z-score(>3) 命中 5 行,两者都命中 5 行
剔除共识异常前后,面积均值:92.08 → 91.75
面积 ~ 总价 相关系数:0.267 → 0.386
IQR 命中 66 行,Z-score 命中 5 行,两种都命中的只有 5 行。
IQR 那 66 行里大部分是 130 平以上的大户型,属于正常长尾;
真正有问题的是共识的那 5 行——$ 999m^2, 998m^2, 995m^2, 和 1.2m^2, 1.5m^2 $,一看就是面积单位或者小数点录错了。
只信一种方法,要么虚惊一场,要么正好漏掉,取交集是个省事又稳的做法。
后面两行才是这 5 行真正的影响:面积均值只从 92.08 变到 91.75,几乎没动;面积和总价的相关系数却从 0.267 跳到 0.386。
7600 行里 5 行错数据,对均值看不出影响,对相关系数已经是能改变结论的量。
这也解释了为什么不能一上来就算相关。
5. 列与列之间什么关系
异常清完,才轮到相关系数和共线性。
这里额外算一次 VIF(方差膨胀因子):用其余数值列做回归来预测本列,1 / (1 - R²) 越大,说明这列越能被别人替代。
补充:VIF是用来衡量回归里某一列能被其他列线性替代到什么程度,也就是查多重共线性的指标。
脚本打印出来的是这两组数:
清洗后两两相关:
配对 Pearson Spearman
面积 ~ 总价 0.388 0.405
面积 ~ 单价 0.021 0.020
面积 ~ 挂牌天数 0.014 0.010
总价 ~ 单价 0.916 0.907
总价 ~ 挂牌天数 0.014 0.015
单价 ~ 挂牌天数 0.010 0.012
VIF(原始数据 / 剔除面积异常后):
原始 清洗后
面积 1.11 6.41
总价 1.26 39.66
单价 1.18 33.70
挂牌天数 1.00 1.00
总价 和 单价 的 Pearson 0.916、Spearman 0.907,两个数差不多,说明是实打实的线性重复,不是几个极端点造出来的假象。
原因也直白:单价 是平台拿 总价 除以 面积 算出来的,三列装的是同一份信息,一起塞进回归就是重复计数,系数会乱跳。
对照表里清洗后那一列,把 VIF 最高的 总价 拿掉,留 单价 和 面积 就够了。
更值得看的是原始那一列:全在 1.2 上下,一派「没有共线问题」的样子。
正是上一节那 5 行错面积把关系搅散了,把真实的共线盖得严严实实。
同一份数据、同一段代码,只差一步清洗,结论完全反过来。
Pearson 和 Spearman 一起看还有个用处:某一对上两个数差得远,多半是有异常点或者非线性关系,值得回头再查一遍。
6. 串成一个入口
五步各写成一个函数,最后用一个入口按序跑完,打印出来就是一份完整报告。
def run_eda(df):
for title, fn in steps: # steps 里按序放着 5 个检查函数
print(title)
fn(df)
批次变了、行数变了,入口一个字都不用改,这就是把检查固化下来的意义:不是每次更聪明地临时想该查什么,而是让该查的每次都查到。
真要出一份给别人看的漂亮报告,ydata-profiling 这类库一行就能生成,适合交付;但判断阈值、误报规则、哪些列要特殊对待,还是攥在自己手里比较踏实,所以这套检查留成代码比留成一次性的图更划算。
文中所用的完整代码共享在:eda_pipeline.py https://url11.ctfile.com/f/45455611-17569912058499-8fdceb?p=6872

浙公网安备 33010602011771号