1206 行里五类毛病:完全重复 6、装修空 18、面积空 4、房龄负 3、单价离谱 9。 单价乘面积除一万必须等于总价——用这条自洽性规则抓错,比肉眼看可靠得多。 洗完 1184 行,清掉 22 条,原始文件一个字不动。
第 3 章讲的是「怎么查」,这一篇是「查到之后怎么处理」,而且多了一条规则:两列互相比。 第 10 章那份数据太干净,三项全 0;这一份才是真实项目的样子。
一、读进来,先把单位和类型对齐
read_csv 一句读完,1206 行 10 列。编号是字符串(FJ0001),不是数字。
单价是元,总价是万元,这一条是整章最容易出错的地方。
单价乘面积得到的是元,除以一万才是万元。少了这一步,总价那一列会大一万倍。
装修那一列是 object,有缺失,读进来是 NaN。房龄是 int64,负数一定是错的。 单位这事在报告里也要写清楚。读者默认「总价」就是万元,你按元写,他能吓一跳。
二、缺:能填的填,不能填的删
装修缺 18 条——填成「未知」,不删。房子还在,只是没写装修,删掉就少一套房源。 面积缺 4 条——删。面积没有,单价和总价都算不出来,留着也没法进任何统计。 同一个「缺失」,两种处理。判断标准只有一个:这一行还能不能参与后面的计算。
能参与就填,不能参与就删。反过来,看着别扭就删,是最容易把有效数据扔掉的坏习惯。
三、重与怪:去重、房龄、单价
| 毛病 | 查到 | 怎么处理 |
|---|---|---|
| 完全重复 | 6 条 | drop_duplicates 去掉 |
| 房龄为负 | 3 条 | 删(负数不可能) |
| 单价离谱 | 9 条 | 先存疑,交给一致性规则 |
去重一句 drop_duplicates() 就完事,1206 减 6 得 1200。
房龄那一列出现 -2,直接删。负数在业务上不成立,不需要再想。
单价从 3259 到 80347,差二十多倍——同一个城市不可能有这种差距。
但「离谱」这两个字靠不住。这次是差二十倍好认,差两成呢?肉眼就判不出来了。所以要有规则。
四、一致性校验:两列必须自洽
表里同时有单价和总价,这两个数本来就是一对:单价乘面积除一万,必须等于总价。 于是写一条规则:算出偏差率,超过 15% 的判为错,整行删掉。
import pandas as pd
df = pd.read_csv("二手房数据.csv")
df["验算"] = df["单价"] * df["面积"] / 10000.0
df["偏差"] = (df["验算"] - df["总价"]).abs() / df["总价"]
print("偏差超 15%% 的 %d 条" % (df["偏差"] > 0.15).sum())
df = df[df["偏差"] <= 0.15]
这条规则一次抓出 9 条,正好是前面那批单价离谱的记录。 为什么是 15%?因为正常记录只有四舍五入的误差,偏差在千分之一量级;真错了至少差几倍。 阈值取多少要看业务。取 5% 会误删正常数据,取 50% 会漏掉错数据,15% 是这条数据的分界线。
这就是「用数据自己的约束查数据」:不靠眼睛,不靠常识,靠两列之间的关系。
五、洗完另存,原文件不动
洗完 1184 行 10 列,存成 二手房数据_clean.csv。
1206 减 22 得 1184:去重 6、面积空 4、房龄负 3、单价错 9,一笔一笔都对得上。
清掉的全是「没法用」的,没有一条是「看着别扭」的。这个标准要能对别人说清楚。
原始文件一个字不动。清洗规则以后要是改了,还能拿原表重跑一遍。
六、动手 Demo:数据清洗
五步:去重、填装修、删面积空、删房龄负、按一致性删单价错的。 每一步都打印「减几条、剩几条」,账要能对上。
import pandas as pd
df = pd.read_csv("二手房数据.csv")
print("原始 %s" % (df.shape,))
print("重复 %d,装修空 %d,面积空 %d,房龄负 %d"
% (df.duplicated().sum(), df["装修"].isnull().sum(),
df["面积"].isnull().sum(), (df["房龄"] < 0).sum()))
df = df.drop_duplicates()
df["装修"] = df["装修"].fillna("未知")
df = df[df["面积"].notnull()]
df = df[(df["房龄"] >= 0) & (df["房龄"] <= 40)]
print("前三步洗完 %d 行" % len(df))
df["验算"] = df["单价"] * df["面积"] / 10000.0
df["偏差"] = (df["验算"] - df["总价"]).abs() / df["总价"]
bad = df["偏差"] > 0.15
print("偏差超 15%% 的有 %d 条" % bad.sum())
df = df[~bad].drop(columns=["验算", "偏差"])
print("洗完 %s" % (df.shape,))
df.to_csv("二手房数据_clean.csv", index=False, encoding="utf-8-sig")
实跑输出:
--------------------------------------------------------------------
数据清洗
--------------------------------------------------------------------
① 读进来
--------------------------------------------------------------------
读进来 1206 行 10 列。
read_csv 默认当第一行是表头,这份数据正好是。中文列名直接用。
编号那一列是字符串(FJ0001),不是数字。用 dtype 指定一下更稳,不指定也不会错。
② 类型和单位先对齐
--------------------------------------------------------------------
| 列 | 类型 | 要留意什么 |
|---|---|---|
| 面积 | float64 | 平方米,带一位小数 |
| 单价 | int64 | 元每平米 |
| 总价 | float64 | 万元,和单价的单位差一万倍 |
| 房龄 | int64 | 年,负数一定是错的 |
| 装修 | object | 有缺失,读进来是 NaN |
单价是元、总价是万元,这一条是整章最容易出错的地方。
单价乘面积得到的是元,除以一万才是万元。少了这一步,总价那一列会大一万倍。
③ 查:缺、重、怪
--------------------------------------------------------------------
原始 1206 行。
| 毛病 | 怎么查 | 这次查到 |
|---|---|---:|
| 完全重复 | duplicated().sum() | 6 条 |
| 装修缺失 | isnull().sum() | 18 条 |
| 面积缺失 | isnull().sum() | 4 条 |
| 房龄为负 | (房龄 < 0).sum() | 3 条 |
| 单价离谱 | 看最小最大 | 3259 到 80347 元 |
单价从 3259 到 80347,差 20 多倍。同一个城市不可能有这种差距,一眼就知道有错。
真实数据里这种「差一个数量级」的错,八成是录入时小数点错位或者多打了一个零。
④ 按规则洗:五步
--------------------------------------------------------------------
第一步去重:1206 减 6 得 1200。
第二步填装修:空的那 18 条填成「未知」。这是填,不是删——房子还在,只是没写装修。
第三步删面积缺失:1200 减 4 得 1196。面积没有,单价和总价都算不了,只能删。
第四步删房龄异常:1196 减 3 得 1193。负数不可能,超过 40 年的房子这个城市基本没有。
第五步按一致性删:单价乘面积除一万,和表里的总价对一遍,偏差超过 15% 的判为错。
删掉 9 条,剩 1184 条。
! 这一步是这一节的重点:不靠肉眼看,靠「两列必须自洽」这条规则。
单价和总价本来就是一对,对不上就至少有一个是错的。
⑤ 落回干净的一份
--------------------------------------------------------------------
洗完 1184 行 10 列,存成 二手房数据_clean.csv。
清掉 22 条,保住 1184 条。清掉的全是「没法用」的,没有一条是「看着别扭」的。
原始文件一个字不动。清洗规则以后要是改了,还能拿原表重跑一遍。
df[df["面积"].notnull()] 是筛选,不是删除——它返回一个新表,原表还在。要接住返回值。
df["装修"].fillna("未知") 如果没接住,等于白写一行。这类「写了但没生效」最难查。
drop(columns=[...]) 把两个临时列去掉,不然它们会跟着写进 clean 文件,多出两列。
30 秒实验
- 把偏差阈值从 0.15 改成 0.05,看会多删多少条。
- 把
fillna("未知")换成dropna(),看最后剩多少行。 - 想一想:如果表里只有总价、没有单价,这条一致性规则还能用吗?
常见误区
- 缺失一律删:装修空着的房源还是有效房源,填「未知」比删掉好。
- 靠肉眼判异常:差二十倍好认,差两成根本看不出来,得靠规则。
- 筛完不接返回值:
df[...]是返回新表,写成一句不赋值,什么都没发生。 - 临时列忘了删:验算和偏差两列跟着进了 clean 文件,后面统计多出两列莫名其妙的东西。
自测三问(点开对答案)
1. 装修缺失为什么填、面积缺失为什么删? —— 装修没有不影响算单价总价,房子还在;面积没有,单价和总价都算不出来,留着没用。 2. 一致性规则的依据是什么? —— 单价乘面积除一万必须等于总价。两列本来是一对,对不上就至少有一个错。 3. 1206 减到 1184,少的 22 条分别是什么? —— 重复 6、面积空 4、房龄负 3、单价错 9,加起来 22。数据干净了。下一篇上第一组分析:八个区的均价和房源占比。
- ☐ (本篇完,下一篇 11.4 区域均价与占比)