5 个案例由易到难,全部代码已在 pandas 3.0.5 上实跑,输出为真实结果。统一使用这张成绩表:
importpandasaspdimportnumpyasnp df=pd.DataFrame({'姓名':['张三','李四','王五','赵六','钱七'],'语文':[88.0,np.nan,76.0,90.0,85.0],'数学':[95.0,81.0,np.nan,np.nan,78.0],'英语':[79.0,90.0,85.0,88.0,np.nan],})print(df)姓名 语文 数学 英语 0 张三 88.0 95.0 79.0 1 李四 NaN 81.0 90.0 2 王五 76.0 NaN 85.0 3 赵六 90.0 NaN 88.0 4 钱七 85.0 78.0 NaN案例 1 |看清缺失情况(先诊断)
情境:拿到数据不要急着填或删,先看清缺多少。
print(df.isna().sum())# 每列缺失个数print((df.isna().mean()*100).round(1))# 每列缺失率print(df.notna().sum())# 每列有效个数每列缺失个数 缺失率 有效个数 姓名 0 姓名 0.0% 姓名 5 语文 1 语文 20.0% 语文 4 数学 2 数学 40.0% 数学 3 英语 1 英语 20.0% 英语 4教学要点:结论是"数学缺得最多(40%)",后续策略要围绕它定。提醒学生NaN != NaN,判断缺失只能用isna()。
案例 2 |填充固定值(最简单)
情境:区分两种缺失——未录入用 60 分兜底,缺考记 0 分。
print(df.fillna(0))# 全部填 0print(df.fillna({'语文':60.0,'数学':0.0,'英语':0.0}))# 各列填不同值各列填不同值的结果: 姓名 语文 数学 英语 0 张三 88.0 95.0 79.0 1 李四 60.0 81.0 90.0 ← 语文未录入,按 60 2 王五 76.0 0.0 85.0 ← 数学缺考,按 0 3 赵六 90.0 0.0 88.0 4 钱七 85.0 78.0 0.0 ← 英语缺考,按 0教学要点:用字典可以给每一列指定不同填充值——这是业务中最常见的写法。
案例 3 |用统计量填充(均值 / 中位数)
情境:缺考之外的空白,用本列统计量估计。
print(df.fillna(df.mean(numeric_only=True)).round(2))# 各列均值填充d=df.copy()# 只补数学,用中位数d['数学']=d['数学'].fillna(d['数学'].median())print(d)用各列均值填充: 只补数学(中位数=81): 姓名 语文 数学 英语 姓名 语文 数学 英语 0 张三 88.00 95.00 79.0 0 张三 88.0 95.0 79.0 1 李四 84.75 81.00 90.0 1 李四 NaN 81.0 90.0 2 王五 76.00 84.67 85.0 2 王五 76.0 81.0 85.0 3 赵六 90.00 84.67 88.0 3 赵六 90.0 81.0 88.0 4 钱七 85.00 78.00 85.5 4 钱七 85.0 78.0 NaN教学要点:①numeric_only=True必须加,否则算"姓名"列会报错;② 均值受极端值影响,成绩类数据常用中位数(此处数学中位数 81 比均值 84.67 更保守)。
案例 4 |删除缺失数据(subset / thresh)
情境:缺失太多、又无法估计时,只能删。
print(df.dropna())# 默认:任缺即删整行print(df.dropna(subset=['语文','数学']))# 只看这两科print(df.dropna(thresh=3))# 至少 3 科有效print(df.dropna(subset=['语文','数学']).reset_index(drop=True))# 重置索引默认(只剩 1 行) 只看语文数学(剩 2 行) 至少3科有效(5 行全留) 姓名 语文 数学 英语 姓名 语文 数学 英语 全部保留(每行都≥3个有效值) 0 张三 88.0 95.0 79.0 0 张三 88.0 95.0 79.0 4 钱七 85.0 78.0 NaN教学要点:默认dropna()最狠,一行里只要有一个空就删掉,本例 5 行只剩 1 行——这是学生最容易"删过头"的地方;教学要强调先想清楚"哪些字段缺了才算无效",再用subset收窄。
案例 5 |顺序数据的填充(ffill / interpolate)
情境:每周测验成绩,中间两周请假——有先后顺序的数据,前后值有参考意义。
s=pd.Series([80.0,np.nan,np.nan,92.0,88.0],index=['第1周','第2周','第3周','第4周','第5周'])print(s.ffill())# 沿用上一次的成绩print(s.interpolate())# 按趋势插值print(s.ffill(limit=1))# 最多连填一个原值 ffill interpolate ffill(limit=1) 第1周 80.0 第1周 80.0 第1周 80.0 第1周 80.0 第2周 NaN 第2周 80.0 第2周 84.0 第2周 80.0 第3周 NaN 第3周 80.0 第3周 88.0 第3周 NaN 第4周 92.0 第4周 92.0 第4周 92.0 第4周 92.0 第5周 88.0 第5周 88.0 第5周 88.0 第5周 88.0教学要点:三兄弟分工——ffill抄上一个值、bfill抄下一个值、interpolate按趋势推算(本例 80→92 等差,正好推出 84、88);limit用来限制"最多连填几个",防止连续长缺失被无脑补满。
综合演练(把前 4 个案例串起来)
clean=df.dropna(subset=['语文','数学']).fillna(df.mean(numeric_only=True)).round(1)clean['总分']=clean[['语文','数学','英语']].sum(axis=1)print(clean)姓名 语文 数学 英语 总分 0 张三 88.0 95.0 79.0 262.0 4 钱七 85.0 78.0 85.5 248.5思路:先用dropna剔除关键科目缺失的行 → 再用fillna补齐次要科目的空缺 → 最后算总分。这个"先删关键、再填其余"的顺序,就是真实数据清洗的标准流程。
需要的话,我可以把这 5 个案例(或再加 5 道对应练习题+答案)整理成一个可直接上课的.py文件或教案文档。