63 行文本、2190 个字符、平均每行 34.8 个字符——三个数先确认文件没坏。 同一份文件里三种分隔符同时在用(# 172 次、/ 35 次、,40 次),说明它是分批存进来的。 第一步永远是看清楚,不是动手改。改完再想看原来的样子,就没了。
第 12 章讲数据存在多张表里。第 13 章讲另一件事:拿到手的原始数据根本不长成表。 这一章用的是一份造出来的脏名单,生成规则写死在脚本里,跑出来人人一样。
一、名单是怎么来的
一家竞争对手停业了,它的客户名单流了出来。Head First 猎头公司想拿它去打电话。 文件到手:一串文本,一行一条记录,63 行。没有表头,没有说明,没有字段定义。 没人知道它当初是怎么导出的,也没人保证格式统一。
这就是所谓脏数据:它不是错的,它是从来没有被要求过「一致」。 所以第一步不是清洗,是搞清楚它现在到底是什么样子。
二、先数三个数
行数 63、总字符数 2190、平均每行 34.8 个字符。 三个数都在合理范围:没有空文件、没有超长行、没有混进二进制乱码。 如果平均行长忽然变成 500,那多半是某一行把所有记录挤在一起了,得先解决这个。
这三个数是最便宜的体检,两行代码,能挡住后面一半的意外。
三、数一数有哪些分隔符
井号出现 172 次、斜杠 35 次、中文逗号 40 次。 一份文件里三种分隔符同时在用,说明它是好几批数据、由不同的人或不同的系统存进来的。 这时候不能随便挑一个切——挑错了,那批数据会整行变成一列。
分隔符不统一,是「这批数据来源不唯一」的直接证据。
四、再看字段里有什么脏东西
姓名里有 7 行带 ^,4 行名字中间夹了空格。 电话有三种写法:带 +86 的 13 行、带横杠的 12 行、纯数字 38 行。 城市有 10 行前后带空格。
这些东西都「不影响人看懂」,但全部影响程序匹配——同一个电话号码在程序眼里是三个不同的值。 脏数据的定义不是「人看不懂」,是「机器对不上」。
五、最后数重复
63 行去重后剩 60 行,也就是有 3 行是整行完全一样的。 重复行不一定是错:可能是同一个人被录了两次,也可能是两个人恰好同名同号。 所以发现重复之后不要急着删,先看这 3 行长什么样。
清点做完,才轮到动手。下一篇开始改。
六、动手 Demo:先看清楚这份名单长什么样
造一份 63 行的脏名单(含 3 行重复),然后只做统计,一个字符都不改。 数行数、字符数、分隔符、姓名的脏字符、电话的三种写法、城市空格、重复行。
SUR = ["王", "李", "张", "刘", "陈", "杨", "赵", "黄", "欧阳", "司马", "周", "吴"]
GIV = ["小明", "伟", "芳", "娜", "强", "磊", "静", "洋", "勇", "艳"]
CITY = ["北京 朝阳", "上海 浦东", "广州 天河", "深圳 南山",
"杭州 西湖", "成都 武侯", "武汉 洪山", "西安 雁塔"]
LV = ["VIP", "A", "B", "C"]
def tel(i):
if i % 5 == 0:
return "+8613%09d" % (i * 137 + 1000)
if i % 5 == 1:
d = "%08d" % (i * 271 + 3000)
return "139-%s-%s" % (d[:4], d[4:])
return "138%08d" % (i * 313 + 5000)
def nm(i):
s, g = SUR[i % 12], GIV[(i * 3) % 10]
if i % 11 == 0:
return s + "^" + g
if i % 13 == 0:
return s + " " + g
return s + g
def sep(i):
return "/" if i % 7 == 0 else ("," if i % 7 == 3 else "#")
RAW = []
for i in range(60):
c = CITY[i % 8]
if i % 6 == 0:
c = " " + c + " "
f = [nm(i), tel(i), c, "2023-%02d-%02d" % (i % 12 + 1, i % 27 + 1), LV[i % 4]]
if i % 17 == 5:
f = f[:2] + f[3:]
RAW.append(sep(i).join(f))
RAW += [RAW[5], RAW[17], RAW[33]]
import re
def parts(s):
return re.split("[#/,]", s)
print("行数 %d 字符数 %d 平均每行 %.1f"
% (len(RAW), sum(len(s) for s in RAW), sum(len(s) for s in RAW) / float(len(RAW))))
for ch in ("#", "/", ","):
print("分隔符 %s 出现 %d 次" % (ch, sum(s.count(ch) for s in RAW)))
print("姓名带 ^ %d 行,带空格 %d 行"
% (sum(1 for s in RAW if "^" in parts(s)[0]),
sum(1 for s in RAW if " " in parts(s)[0])))
tel = [parts(s)[1] for s in RAW]
print("电话 +86 %d 行,横杠 %d 行,纯数字 %d 行"
% (sum(1 for t in tel if "+86" in t),
sum(1 for t in tel if "-" in t),
sum(1 for t in tel if "+86" not in t and "-" not in t)))
print("城市前后带空格 %d 行" % sum(1 for s in RAW if parts(s)[2] != parts(s)[2].strip()))
print("去重后剩 %d 行,重复 %d 行" % (len(set(RAW)), len(RAW) - len(set(RAW))))
实跑输出:
--------------------------------------------------------------------
先看清楚这份名单长什么样
--------------------------------------------------------------------
① 名单是怎么来的
--------------------------------------------------------------------
一家竞争对手停业了,它的客户名单流了出来。Head First 猎头公司想拿它去打电话。
文件到手:一串文本,一行一条记录,63 行。
没人知道它当初是怎么导出的,也没人保证格式统一。
② 先数三个数
--------------------------------------------------------------------
行数 63,总字符数 2190,平均每行 34.8 个字符。
三个数都在合理范围,说明文件没被截断、没混进二进制垃圾。
第一步永远是看清楚,不是动手改。改完再想看原来的样子,就没了。
③ 数一数里面有哪些分隔符
--------------------------------------------------------------------
| 分隔符 | 字符 | 共出现次数 |
|---|:---:|---:|
| 井号 | # | 172 |
| 斜杠 | / | 35 |
| 中文逗号 | , | 40 |
三种分隔符同时在用。一份文件里出现多种分隔符,说明它是好几个人分批存进来的。
④ 姓名里有脏东西
--------------------------------------------------------------------
姓名里带 ^ 的行:7 行。姓名中间夹空格的行:4 行。
^ 大概是某个系统导出的分隔标记,空格大概是手工录入时按的。
两种都是「原本想表示同一个意思,结果写成了两种样子」。
⑤ 电话和城市也不干净
--------------------------------------------------------------------
电话带 +86 的 13 行,带横杠的 12 行,纯数字的 38 行。
城市前后带空格的行:10 行。
同一个字段有三种写法,这在数据分析里叫「不一致」,不叫「有个性」。
⑥ 还有重复
--------------------------------------------------------------------
63 行里去重后剩 60 行,也就是有 3 行是完整重复的。
重复行不一定是错——可能是同一个人被录了两次,也可能是两个人同名同电话。要先查再删。
那份 parts 函数只是「看清楚」用的:按任意一种分隔符切开,方便逐字段统计。它不代表正式拆列。
注意末尾三行重复:63 行里最后三条是前面原样复制的。
现实里这种重复极其常见,因为导出脚本跑了两次。
30 秒实验
- 如果平均行长从 34.8 变成 200,最可能发生了什么?
- 为什么「三种分隔符」比「一种分隔符但格式错」更值得警惕?
- 想一想:为什么清点阶段一个字符都不该改?
常见误区
- 拿到就上手改:改完原始样子就没了,出问题无法回溯。
- 只看前几行就下结论:脏数据往往在后面几十行才露出真面目。
- 把「人能看懂」当成「数据没问题」:机器匹配不上的,全算脏。
- 看到重复就删:先确认是录入重复还是真有两个人同名同号。
自测三问(点开对答案)
1. 清点阶段要看哪几样东西? —— 行数、字符数、分隔符种类与次数、各字段的脏字符、取值写法是否统一、有没有整行重复。 2. 为什么「一种字段有多种写法」是问题? —— 因为程序按字符串匹配,同一个电话号码写成三种样子就是三个不同的值,统计和去重都会出错。 3. 为什么清点时不要改数据? —— 改了就没有基准了。后面每一步清洗都要能和原始数据对比,才知道自己改了什么。清点完了,接下来做最重要的那一步:让所有行长得一样。这就是准备与组织。
- ☐ (本篇完,下一篇 13.2 准备与组织)