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 单独保护
dropna() 行/列删除决策流程 原始 DataFrame axis=0/1? axis=0 按行检查缺失 axis=1 按列检查缺失 how='any'/'all' thresh 阈值判断 删除行 删除列 thresh 比 how 更精细,推荐优先使用

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 使用插补而非删除
缺失值删除实战流程(客户流失数据集) ① 计算缺失率 isnull().mean() ② 删除高缺失列 缺失率 > 80% ③ thresh 删行 thresh=3 ④ subset 保护 churn+tenure 最终结果:5000 行 → 3395 行(保留 68%) 核心特征完整,可直接用于建模

3.6 常见陷阱与避坑指南

陷阱 后果 解决方案
直接使用 dropna() 默认参数 删除所有含缺失的行,样本量骤降 先计算缺失率,再设定合理的 thresh
忘记设置 inplace=False 原 DataFrame 未被修改,后续代码仍用旧数据 显式赋值:df = df.dropna(...)
对时间序列数据随意删行 破坏时间连续性,导致时序分析失效 使用插补或前向填充,避免删除中间行
删除列后未更新特征列表 模型训练时特征维度不匹配 维护特征清单,删除列后同步更新
⚠ 黄金法则:删除前先备份原始数据,记录删除的行数和列数,确保清洗过程可追溯、可复现。