pandas 进阶:清洗、分组与合并
约 14 分钟
真实数据从来不干净。数据科学里公认的规律:清洗数据的时间通常占整个项目的一半以上。
一、处理缺失值
df.isna().sum() # 先看清楚缺在哪
df.dropna() # 删掉有缺失的行
df.dropna(subset=["语文"]) # 只看这一列有没有缺
df["语文"].fillna(0) # 填 0
df["语文"].fillna(df["语文"].mean()) # 填均值
df["语文"].ffill() # 用前一个值填(时间序列常用)
怎么选?取决于缺失的原因:
| 情况 | 建议 |
|---|---|
| 缺得很少(<5%) | 删掉行最省事 |
| 缺失本身有含义(如「未填写」) | 单独标记成一类,别乱填 |
| 数值型、随机缺失 | 填均值或中位数 |
| 时间序列 | 前向填充 |
一个重要提醒:填均值会人为降低方差,让数据看起来比实际更集中。填之前想清楚代价。
二、处理重复和类型
df.duplicated().sum() # 有多少完全重复的行
df = df.drop_duplicates()
df["分数"] = pd.to_numeric(df["分数"], errors="coerce") # 转数字,转不了的变 NaN
df["日期"] = pd.to_datetime(df["日期"])
errors="coerce" 很实用:数据里混进了「暂无」「-」这类脏值时,它会把它们变成 NaN 而不是直接报错,然后你再统一处理缺失。
三、分组聚合:pandas 最强大的功能
「按某个列分组,对每组算一个统计量」——这是数据分析里最高频的操作。
df.groupby("班级")["成绩"].mean() # 每个班的平均分
df.groupby("班级")["成绩"].agg(["mean", "max", "count"]) # 多个统计量
df.groupby(["班级", "性别"])["成绩"].mean() # 按两个维度分组
分组的思维模型:拆分 → 应用 → 合并(split-apply-combine)
原表 → 按班级拆成几张小表 → 每张算平均 → 结果拼回一张表
理解了这个模型,groupby 的各种用法就都通了。
透视表(本质是 groupby 的表格化展示):
df.pivot_table(values="成绩", index="班级", columns="科目", aggfunc="mean")
四、合并多张表
pd.concat([df1, df2]) # 纵向堆叠(行拼接)
pd.merge(df1, df2, on="学号", how="left") # 按键关联(类似 SQL JOIN)
how 的四种取值:
| how | 保留 |
|---|---|
inner |
只保留两边都有的(默认) |
left |
保留左表全部 |
right |
保留右表全部 |
outer |
两边全保留 |
最常见的错误:默认用了 inner,结果数据悄悄变少了。
养成习惯:merge 之后立刻检查行数
print(len(df1), len(merged)) # 数量对不上就要查原因
五、一个高频警告:SettingWithCopyWarning
sub = df[df["语文"] > 85]
sub["等级"] = "优秀" # ⚠️ 可能改不动原数据,也可能意外改了
原因:sub 可能是原表的视图也可能是副本,pandas 自己也不确定,所以警告你。
正确写法:
sub = df[df["语文"] > 85].copy() # 明确要副本
sub["等级"] = "优秀"
# 或者直接在原表上改
df.loc[df["语文"] > 85, "等级"] = "优秀"
记住:看到这个警告不要忽略它,它经常对应着「我以为改了但其实没改」的 bug。
练习:给一份含班级、科目、成绩的数据,用 groupby 算出「每个班每科的平均分」,再用 pivot_table 展示成班级为行、科目为列的表格。
小纸条
groupby 的思维模型是什么?merge 之后为什么要立刻检查行数?SettingWithCopyWarning 怎么正确处理?
登录 后可看答案