3、缺失值删除:dropna() 参数详解、按行/列删除阈值设置、实战案例
在数据清洗中,当缺失值比例极低或缺失模式完全随机时,直接删除缺失样本是最简单、最有效的策略。Pandas 的 dropna() 方法提供了灵活的行/列删除控制,但参数误用可能导致数据量骤减或信息泄露。本节从参数拆解到实战阈值设定,系统讲解如何安全使用缺失值删除。
3.1 dropna() 核心参数速查
| 参数 | 类型 | 默认值 | 作用说明 |
|---|---|---|---|
axis |
int / str | 0 | 0 或 'index' 删除行;1 或 'columns' 删除列 |
how |
str | 'any' | 'any' 任一缺失即删;'all' 全部缺失才删 |
thresh |
int | None | 保留非空值数量 ≥ thresh 的行/列 |
subset |
list-like | None | 只在指定列/行中检查缺失 |
inplace |
bool | False | 是否原地修改 DataFrame |
实战建议:日常清洗优先使用
thresh 而非 how,因为阈值删除能保留部分有效信息,避免“一刀切”导致样本量过度损失。
3.2 按行删除 vs 按列删除
按行删除(axis=0):删除包含缺失值的样本记录,适用于缺失率低、样本量充足的场景。
import pandas as pd
import numpy as np
df = pd.DataFrame({
'A': [1, 2, np.nan, 4],
'B': [5, np.nan, np.nan, 8],
'C': [9, 10, 11, 12]
})
# 删除包含任何缺失值的行
df_drop_any = df.dropna(axis=0, how='any')
print(df_drop_any)
# A B C
# 0 1 5 9
# 3 4 8 12
# 删除全部为缺失值的行
df_drop_all = df.dropna(axis=0, how='all')
print(df_drop_all)
# A B C
# 0 1.0 5.0 9
# 1 2.0 NaN 10
# 2 NaN NaN 11
# 3 4.0 8.0 12
按列删除(axis=1):删除缺失率过高的特征列,适用于特征维度大、缺失严重的场景。
# 删除包含任何缺失值的列
df_drop_col = df.dropna(axis=1, how='any')
print(df_drop_col)
# C
# 0 9
# 1 10
# 2 11
# 3 12
# 删除全部为缺失值的列
df_drop_col_all = df.dropna(axis=1, how='all')
print(df_drop_col_all)
# A B C
# 0 1.0 5.0 9
# 1 2.0 NaN 10
# 2 NaN NaN 11
# 3 4.0 8.0 12
⚠ 按列删除风险:删除列会永久丢失该特征的全部信息,务必先计算每列缺失率,仅对缺失率 > 80% 且业务价值低的列执行删除。
3.3 thresh 阈值设置:保留有效信息
thresh 参数定义“保留该行/列所需的最少非空值数量”。当某行非空值数 ≥ thresh 时保留,否则删除。这是比 how 更精细的控制方式。
# 保留至少包含 2 个非空值的行
df_thresh = df.dropna(axis=0, thresh=2)
print(df_thresh)
# A B C
# 0 1.0 5.0 9
# 1 2.0 NaN 10
# 3 4.0 8.0 12
# 索引 2 只有 1 个非空值(C 列),被删除
阈值计算公式:
- 行保留条件:非空值数 ≥ thresh
- 列保留条件:非空值数 ≥ thresh
- 等价于:缺失数 ≤ 总列数 - thresh
阈值设定经验法则:
- 样本量大(>10万行):thresh = 总列数 × 0.7(保留至少 70% 特征完整)
- 样本量小(<1000行):thresh = 总列数 × 0.5(放宽条件,避免样本骤减)
- 关键业务字段:thresh 应包含该字段,或使用 subset 单独保护
3.4 subset 参数:局部缺失检查
subset 限定只在指定列中检查缺失,其他列即使有缺失也不影响该行的保留决策。这在保护关键字段完整性时非常有用。
# 只在列 'A' 和 'B' 中检查缺失,删除这两列中任一缺失的行
df_subset = df.dropna(subset=['A', 'B'])
print(df_subset)
# A B C
# 0 1.0 5.0 9
# 3 4.0 8.0 12
# 索引 1 的 B 列缺失,索引 2 的 A、B 均缺失,都被删除
典型场景:在构建机器学习模型时,对标签列和核心特征列使用
subset 保护,确保删除后保留的样本在这些关键字段上完整无缺。
3.5 实战案例:客户流失数据集清洗
假设我们有一个客户流失数据集 churn.csv,包含 5000 行、12 列特征。清洗目标:在保留足够样本的前提下,删除缺失严重的行和列。
步骤 1:加载数据并计算缺失率
import pandas as pd
import numpy as np
# 模拟数据
np.random.seed(42)
n = 5000
df = pd.DataFrame({
'customer_id': range(n),
'age': np.random.choice([20, 30, 40, 50, np.nan], size=n, p=[0.2,0.3,0.3,0.15,0.05]),
'income': np.random.choice([30000, 50000, 80000, np.nan], size=n, p=[0.3,0.4,0.2,0.1]),
'tenure': np.random.randint(1, 72, size=n).astype(float),
'churn': np.random.choice([0, 1], size=n, p=[0.8, 0.2])
})
df.loc[df.sample(frac=0.3).index, 'tenure'] = np.nan # 30% 缺失
# 计算每列缺失率
missing_rate = df.isnull().mean().sort_values(ascending=False)
print(missing_rate)
# tenure 0.3000
# income 0.1000
# age 0.0500
# customer_id 0.0000
# churn 0.0000
步骤 2:删除缺失率 > 80% 的列(本例无)
# 删除缺失率超过 80% 的列
high_missing_cols = missing_rate[missing_rate > 0.8].index
df_clean = df.drop(columns=high_missing_cols)
print(f"删除 {len(high_missing_cols)} 列,剩余 {df_clean.shape[1]} 列")
步骤 3:使用 thresh 删除行
# 保留至少 3 个非空值的行(总列数 5,thresh=3)
df_clean = df_clean.dropna(axis=0, thresh=3)
print(f"删除后行数: {df_clean.shape[0]} (原始 {df.shape[0]})")
# 删除后行数: 4850 (原始 5000)
# 删除了约 3% 的样本,保留了 97%
步骤 4:对核心特征使用 subset 二次清洗
# 确保 churn 和 tenure 字段完整
df_final = df_clean.dropna(subset=['churn', 'tenure'])
print(f"最终行数: {df_final.shape[0]}")
# 最终行数: 3395
# 因为 tenure 缺失率 30%,subset 删除后样本量降至 68%
案例总结:
- 先删列(高缺失率、低业务价值)→ 再删行(thresh 阈值)→ 最后 subset 保护关键字段
- 最终保留 3395 行(68%),所有核心特征完整,适合直接进入建模流程
- 若样本量不足,可考虑将 thresh 降至 2,或对 tenure 使用插补而非删除
3.6 常见陷阱与避坑指南
| 陷阱 | 后果 | 解决方案 |
|---|---|---|
直接使用 dropna() 默认参数 |
删除所有含缺失的行,样本量骤降 | 先计算缺失率,再设定合理的 thresh |
忘记设置 inplace=False |
原 DataFrame 未被修改,后续代码仍用旧数据 | 显式赋值:df = df.dropna(...) |
| 对时间序列数据随意删行 | 破坏时间连续性,导致时序分析失效 | 使用插补或前向填充,避免删除中间行 |
| 删除列后未更新特征列表 | 模型训练时特征维度不匹配 | 维护特征清单,删除列后同步更新 |
⚠ 黄金法则:删除前先备份原始数据,记录删除的行数和列数,确保清洗过程可追溯、可复现。