真实世界的数据从来不干净:空格、错格式、缺胳膊少腿的行混在一起。今天学两招——字符串处理和条件筛选,把脏数据洗成能用的数据
一、一份”看着没问题”的表格,坑有多深
先说一个你八成遇到过的场景。
朋友发来一份”聚餐报名表.csv”,说:”帮我算算一共来多少人,按男女分个组。”你打开一看,十几行,有姓名、性别、手机号,挺整齐的嘛。结果一算——人数比实际多了3个;按性别分组,分出来四组:”男”、”男 “(带空格)、”女”、”M”。手机号更离谱,有的带横杠,有的带”+86″,还有人写了”没手机”。
你找朋友对质,朋友说:”我就是让大家自己填的啊。”
问题就出在这——真实世界的数据,是人手填出来的,不是机器吐出来的。人会多打一个空格、会把”男”写成”M”、会在手机号栏里写小作文。数据分析圈有句老话:“数据科学家80%的时间在洗数据,20%在分析”——虽然是吐槽,但说明了一件事:数据不洗,分析结果就是错的。
给数据挑毛病、修毛病的过程,就叫数据清洗。
今天不求洗得多专业,只学最实用的两招:
- 字符串处理——把每个格子里的文本修整干净(去空格、统一大小写、替换错写法)
- 条件筛选——把不合格的行挑出来(手机号不是11位数字的不要、性别没填的单独放)
好消息是:今天也没有全新语法。字符串处理用的是字符串自带的方法(比第14篇玩字符串时多了几个”自带工具”),条件筛选用的是你早就会的if和循环。今天学的是把它们串成一条清洗流水线——和上一篇的”配置套路”一样,是组合拳,不是新招式。
二、第一招:字符串处理——每个格子的”修理工”
先看”脏”文本长什么样。假设这一列是大家填的性别:
性别列 = ["男", "男 ", " 女", "M", "女", "male", "女n"]
坑全在这了:有多余空格("男 ")、有换行符("女n")、有英文写法("M"、"male")。注意 "男" 和 "男 " 在程序眼里是两个不同的字符串——就像”王小明”和”王小明 “在点名册上是两个人。这就是分组分出四组的原因。
字符串身上带一套”修理工工具”,叫字符串方法。今天重点用三个:
text = " 男 n"
text.strip() # '男' —— 剪掉首尾所有空格、换行、制表符
text.strip().lower() # '男' —— 字母全变小写(对付'M'/'Male'/'MALE')
text.replace("M", "男") # 替换:把'M'换成'男'
三个方法各管一段:
| 方法 | 干什么 | 例子 |
|---|
|——|——–|——|
.strip() |
剪掉首尾的空格、换行、制表符 | " 女n".strip() → "女" |
|---|---|---|
.lower() |
字母全变小写 | "Male".lower() → "male" |
.replace(旧, 新) |
把指定内容换掉 | "M".replace("M", "男") → "男" |
关键细节:这些方法不会改动原来的字符串,而是返回一个新的干净字符串。所以要接住它:
text = " 男 "
干净 = text.strip() # 不接住就白修了
print(干净) # 男
print(text) # ' 男 ' —— 原来的没变
现在把”性别列洗干净”写成一个函数——套路就是每来一个值,先strip,再统一写法:
def 洗性别(值):
值 = 值.strip().lower() # 第1步:剪空格 + 统一小写
if 值 in ("男", "m", "male"):
return "男"
elif 值 in ("女", "f", "female"):
return "女"
else:
return "未知" # 认不出来的,老实标"未知",别瞎猜
性别列 = ["男", "男 ", " 女", "M", "女", "male", "女n", "保密"]
print([洗性别(x) for x in 性别列])
# ['男', '男', '女', '男', '女', '女', '女', '未知']
七零八落的8个值,洗完整整齐齐。这就是字符串处理的威力——同一个意思的不同写法,归一成一个标准写法。行话叫”归一化”,你就理解成”统一口径”。
三、第二招:条件筛选——把不合格的挑出来
洗完字段,还得挑行。手机号那列最典型:有的不是11位、有的混着横杠、有的干脆是”没手机”。这种行不该硬着头皮往下算,得挑出来单独处理。
第5篇学的if,配上学过的列表和循环,就能干这活。先看”挑出来”的标准写法:
手机号列 = ["13800138000", "138-0013-8000", "没手机", "13912345678", "1380013800"]
def 是合法手机号(值):
值 = 值.strip().replace("-", "").replace("+86", "") # 先修整
return 值.isdigit() and len(值) == 11 # 再判断:全是数字且11位
干净行 = []
脏行 = []
for 号 in 手机号列:
if 是合法手机号(号):
干净行.append(号)
else:
脏行.append(号)
print("能用的:", 干净行) # 能用的: ['13800138000', '13912345678']
print("要返修:", 脏行) # 要返修: ['138-0013-8000', '没手机', '1380013800']
两个新面孔,都是字符串自带的小工具:
值.isdigit()——判断字符串是不是全是数字("13800138000".isdigit()→True,"没手机".isdigit()→False)。注意它是字符串的方法,整数没有这个方法len(值)——数字符串有多少个字符,老朋友了(第15篇数单词用过)
这里有个容易忽略的好习惯:先修整,再判断。"138-0013-8000"先去掉横杠就是合法的,别一棍子打死;而”没手机”修完还是不合格,才进”脏行”。清洗不是当保安,是当修理工——能修的修好留下,修不了的才请出去。
另外注意我把判断写成了独立函数是合法手机号。这样”什么算合格”只写一处,明天标准变了(比如要11位还必须以1开头)只改这一个函数。上一篇”逻辑和设置分家”的思想又出现了——标准要独立成函数,别散落在代码各处。
四、实战:把聚餐报名表洗干净
两招都学会了,串起来就是一条清洗流水线:读进来 → 每行洗字段 → 挑出坏行 → 输出干净数据 + 问题报告。完整代码(建议存成洗数据.py):
import csv
def 洗性别(值):
值 = 值.strip().lower()
if 值 in ("男", "m", "male"):
return "男"
elif 值 in ("女", "f", "female"):
return "女"
return "未知"
def 是合法手机号(值):
值 = 值.strip().replace("-", "").replace("+86", "")
return 值.isdigit() and len(值) == 11
def 洗一行(行):
"""把一行原始数据洗干净,返回(干净行, 问题清单)"""
问题 = []
干净 = {
"姓名": 行["姓名"].strip(),
"性别": 洗性别(行["性别"]),
"手机号": 行["手机号"].strip().replace("-", "").replace("+86", ""),
}
if not 干净["姓名"]:
问题.append("姓名是空的")
if 干净["性别"] == "未知":
问题.append(f"性别看不懂:{行['性别']!r}")
if not 是合法手机号(干净["手机号"]):
问题.append(f"手机号不对:{行['手机号']!r}")
return 干净, 问题
# ———— 流水线主体 ————
干净行 = []
待返修行 = []
with open("报名表.csv", encoding="utf-8") as f:
for 行 in csv.DictReader(f):
干净, 问题 = 洗一行(行)
if 问题:
待返修行.append((干净["姓名"], 问题))
else:
干净行.append(干净)
# 输出洗好的数据
with open("报名表_干净版.csv", "w", encoding="utf-8", newline="") as f:
写手 = csv.DictWriter(f, fieldnames=["姓名", "性别", "手机号"])
写手.writeheader()
写手.writerows(干净行)
# 输出"问题报告"——脏数据不丢,单独给人看
print(f"洗完:{len(干净行)} 行能用,{len(待返修行)} 行要返修")
for 姓名, 问题 in 待返修行:
print(f" {姓名}:{';'.join(问题)}")
跑之前自己造一份报名表.csv试试(记住第25篇的规矩:UTF-8编码):
姓名,性别,手机号
张三,男,13800138000
李四 ,男 ,138-0013-8000
王五, 女,+8613912345678
赵六,M,没手机
,女,13700137000
钱七,female,1380013800
预期输出:
洗完:3 行能用,3 行要返修
赵六:手机号不对:'没手机'
:姓名是空的
钱七:手机号不对:'1380013800'
(上面这段就是我实际跑出来的结果。注意两个细节,正好都是今天讲的重点:李四的号138-0013-8000去横杠后是合法的,所以他通过了——”先修整再判断”救回来一行;赵六填的"M"被归一成了”男”,性别没问题,他只是手机号栏写了小作文。要是你之前无脑strip()就判断,李四这行就被冤枉删了。)
注意这条流水线的两个”讲究”,比代码本身还值钱:
- 脏数据不直接删,要留”问题报告”。删了就永远不知道出过什么事;留着报告,可以回头找填表的人返修。真实工作里,”这行我删了”和”这行有问题,名单给你”是两种职业水平
- 每行独立洗,一行坏了不连累别行。
洗一行只管自己那行,坏行进”待返修”,好行照常放行——流水线不会因为一个坏零件停产
再把这个套路丢给AI用一次你就明白了。说:”帮我把洗数据.py 改成也检查邮箱格式(有@就行),不合格的加进问题报告”——改动会非常小:洗一行里加一个判断。清洗流水线是可生长的,这是散装if给不了的。
五、四个必踩的坑(先打疫苗)
坑1:strip()以为能去掉所有空格。 strip()只剪首尾的空格,中间的空格它不管——"张 三".strip()还是"张 三"。要收拾中间的空格用replace(" ", "")(把空格全替换掉)或者更稳的一招:先replace(" ", "")再去判断。疫苗:想清楚你要”剪两头”还是”全删”,别无脑strip。
坑2:洗完忘了接住。 值.strip()只是”返回”干净版,不会改动原字符串。写了行["姓名"].strip()但没赋值回去,等于白洗。疫苗:清洗必赋值——x = x.strip().lower()这种”洗完覆盖自己”的写法最不容易忘。
坑3:清洗和判断写成一锅粥。 把”去横杠、判长度、判数字”揉在一大段if里,明天换个手机号规则就得重写。疫苗:修整归修整,判断归判断——先干净值 = 修整(原始值),再是否合格 = 判断(干净值)。今天的是合法手机号故意把修整和判断放一起了(因为它俩太短),但心里要有数这是两件事。
坑4:脏数据直接del,问题没记录。 程序跑完只剩干净数据,看起来很爽,但你不知道丢了多少、丢的都是谁——万一整列手机号格式都错,你删完就剩个位数了还不自知。疫苗:删之前先数、先记。print(f"{len(待返修行)} 行要返修")这一行,就是你的”数据体检单”。
六、跟AI协作的正确姿势(清洗特供)
清洗活是AI的强项,但要指挥好三个要点:
- 把”脏的样子”贴给AI。别说”帮我清洗数据”,说”这列性别里有’男’、’男 ‘、’M’、’male’、’女n’,帮我写函数统一成’男/女’,认不出来的返回’未知’”——把脏数据样本原样贴出来,AI才能对症下药。贴真实数据比描述规则管用十倍
- 要求”问题报告”,不许静默删除。在需求里加一句:”不合格的行不要删,收集到一个列表里,最后打印出来给我看”——这句能防住AI写出”坏行直接跳过”的糊弄代码
- 让它写成函数,别写成一坨。”清洗逻辑写成独立函数,一行一个函数调用”——改规则时只改一个函数,这种结构红利你下一篇马上会再吃到
高频句式送你:”每列写一个清洗函数(输入原始字符串,输出干净值),坏行收集到问题报告,好行存进新文件“——把这句丢给AI,出来的就是今天实战代码的骨架。
七、今日小练习
练习1:给聚餐表加一列”忌口”
在洗一行里加上”忌口”字段的清洗:去掉首尾空格,把顿号、逗号统一成斜杠("辣,香菜" → "辣/香菜"),空的填”无”。(提示:replace(",", "/").replace(",", "/")链式替换;判空用if not 值。改完记得fieldnames里也加上这一列,不然存的时候会丢——第25篇的老坑。)
练习2:手机号体检单
写一个独立小程序:读任意CSV,找出所有”像手机号但不合法”的值,输出每行行号和原因(太短/太长/含字母)。(提示:原因可以用elif分三段判:len < 11、len > 11、not 值.isdigit()。行号用for i, 行 in enumerate(数据, 1)——enumerate自带计数,第15篇数单词时见过它。)
练习3:两份名单对账
手造名单A.csv和名单B.csv(各有姓名、手机号),写程序找出:”A有B没有”的人、”两边都有但手机号不同”的人。(提示:手机号是天然的唯一标识,把两份名单各自读成{手机号: 姓名}的字典——第8篇的字典招。然后对比两个字典的键:键 not in 另一个字典查单边,值不同查冲突。这就是程序员说的”对账/去重”,工作中高频得吓人。)
写在后面:你的数据开始”讲卫生”了
盘点今天:字符串修理工三件套(strip剪两头、lower统一大小写、replace换写法)、条件筛选(isdigit+len验手机号,先修整再判断)、清洗流水线(读进来→逐行洗→好行放行→坏行记录)。
还是没有硬核新语法——第29篇了,你早就发现规律了吧?编程的大部分功夫不在语法,在”讲究”:数据要洗、标准要集中、坏数据要记录。这些讲究,才是让AI给你写的代码从”能跑”变成”能用”的关键。
下一篇把目光从数据挪到文件上。你电脑里是不是有一堆”报告1.csv、报告2.csv、最终版.csv、最终版2.csv”?批量文件处理——让程序一口气收拾一整个文件夹,你只要说一句”把这文件夹里的CSV全都洗一遍”。
下篇预告
第三十篇:批量文件处理——一个文件夹里躺着几十个文件,怎么让程序自动找到它们、逐个处理、批量改名?os和glob两个新朋友登场。下一篇见。
发表回复