自学教程 学编程从这里开始

Pandas 缺失值处理

承接前面《Pandas 过滤与条件查询》,本篇系统讲解 Pandas 缺失值识别、筛选、删除、填充、插值、标记缺失、缺失策略选型,区分 np.nan、pd.NA、None,配套业务场景方案,保持系列教程统一风格。
前置导入与示例数据

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "name": ["张三", "李四", None, "赵六", "孙七", "周八"],
    "age": [22, np.nan, 30, 28, None, 32],
    "city": ["杭州", "上海", "杭州", "", "上海", "北京"],
    "salary": [8000, 12000, np.nan, 11000, 18000, 9500],
    "join_date": ["2024-01-01", "2024-02-15", None, "2024-03-20", "2024-04-10", "2024-05-01"]
})
print(df)

一、先搞懂 Pandas 的缺失值

标识说明
np.nan浮点数缺失,最常见,不等于任何值,np.nan == np.nan → False
NonePython原生空值,在object/string列会转为pd.NA
pd.NAPandas新版扩展类型(Int64、string、boolean)专用缺失,推荐优先使用

重点:空字符串 "" 不是缺失值,isna() 识别不到,业务清洗时要单独处理。

二、识别缺失值 isna / notna

# 判断是否缺失,返回布尔矩阵
df.isna()
# 判断是否非缺失
df.notna()

# 统计每列缺失数量
df.isna().sum()
# 缺失占比
df.isna().mean()

# 筛选缺失行、非缺失行
# age为空的行
df.loc[df["age"].isna()]
# age不为空的行
df.loc[df["age"].notna()]

三、删除缺失值 dropna

语法:df.dropna(axis=0, how='any', thresh=None, subset=None)

  • axis=0:按行删除;axis=1:按列删除
  • how='any':任意一个缺失就删;how='all':全部为缺失才删
  • thresh=N:该行至少要有N个非空值才保留
  • subset:只看指定列的缺失
# 任意一列缺失,删除整行(常用)
res = df.dropna()

# 只有指定列name、age为空才删除
res = df.dropna(subset=["name", "age"])

# 一行全部是缺失才删除
res = df.dropna(how="all")

# 至少4个非空字段才保留
res = df.dropna(thresh=4)

# 删除缺失过多的列
res = df.dropna(axis=1, thresh=4)

⚠️ 适合:缺失占比很低、删除后不影响样本;缺失多不要直接dropna,会丢失大量数据。

四、填充缺失值 fillna

语法:df.fillna(value, method=None, limit=None)

4.1 固定值填充

# 全部缺失填充0
df["salary"].fillna(0)
# 文本列填充未知
df["city"].fillna("未知")
# 字典,按列分别填充
fill_rule = {
    "age": 0,
    "city": "未知",
    "salary": 0
}
df.fillna(fill_rule)

4.2 前后向填充(时序数据高频)

  • method="ffill" / pad:前向填充,用上一行的值向下填充
  • method="bfill" / backfill:后向填充,用下一行的值向上填充
# 前向填充
df["age"].fillna(method="ffill")
# 后向填充
df["age"].fillna(method="bfill")
# limit:最多连续填充2个缺失
df["age"].fillna(method="ffill", limit=2)

4.3 统计值填充(均值、中位数、众数)

# 均值填充
df["age"].fillna(df["age"].mean())
# 中位数填充(抗异常值,推荐)
df["age"].fillna(df["age"].median())
# 众数填充(分类字段)
df["city"].fillna(df["city"].mode()[0])

数值字段优先中位数;分类字段优先众数。

五、插值 interpolate(时序数据优选)

fillna 只是简单复制,interpolate 是插值计算,适合连续时间序列

# 默认线性插值
df["salary"].interpolate()
# 时间索引插值(时间不等间隔时很有用)
# df["salary"].interpolate(method="time")

method可选:linear、nearest、spline、polynomial

六、标记缺失(新增标记列,不改动原数据)

很多机器学习场景,除了填充,还要记录这个位置原本是不是缺失

df["age_is_missing"] = df["age"].isna()

七、空字符串伪装缺失处理(业务高频坑)

原始Excel、CSV经常有空字符串 "",isna() 识别不到,需要先替换:

# 把空字符串、空白字符统一转为pd.NA
df["city"] = df["city"].replace("", pd.NA)
# 包含空格的空白值
df["city"] = df["city"].replace(r"^\s*$", pd.NA, regex=True)

八、缺失值替换 replace

# 把-999、-1这类业务标记的异常值转为缺失
df["salary"] = df["salary"].replace(-999, pd.NA)

九、缺失处理策略选型表

方案适用场景缺点
dropna缺失占比很低(<5%),样本充足丢失数据,样本少的时候慎用
固定值填充分类字段,缺失代表“未知”引入人为偏差
均值/中位数填充数值字段,分布稳定压缩方差,破坏分布
ffill/bfill时序、连续采集数据(传感器、流水)连续缺失多时误差大
interpolate连续时序指标不适合离散分类字段
保留缺失+新增标记列机器学习建模数据量变大

十、高频坑清单

  1. 空字符串 "" 不是缺失值,isna() 检测不到,需要手动replace
  2. np.nan 不能用 == np.nan 判断,必须用 isna()
  3. 填充后忘记赋值,df["age"].fillna(0) 不会修改原df,需要 df["age"] = df["age"].fillna(0)
  4. ffill/bfill 遇到开头/结尾缺失,无法填充,依然保留NA
  5. Int64(可空整数类型)和普通int区别:int列不能存pd.NA,会自动转float
  6. 分组场景不要直接全局均值填充,优先分组填充:df.groupby("city")["age"].transform(lambda x: x.fillna(x.median()))

十一、分组填充实战(工业级常用)

按城市分组,用该城市内部年龄中位数填充,比全局填充更合理

df["age"] = df.groupby("city")["age"].transform(lambda s: s.fillna(s.median()))

十二、综合完整清洗模板

def clean_missing(df):
    # 1. 空字符串转为缺失
    df = df.replace(r"^\s*$", pd.NA, regex=True)
    # 2. 新增缺失标记
    df["age_miss"] = df["age"].isna()
    # 3. 数值列分组中位数填充
    df["age"] = df.groupby("city")["age"].transform(lambda s: s.fillna(s.median()))
    df["salary"] = df["salary"].fillna(df["salary"].median())
    # 4. 分类列填充未知
    df["city"] = df["city"].fillna("未知")
    return df

df_clean = clean_missing(df)
print(df_clean)
标签:

0 条笔记