缺失值识别与处理
本教程共 54 篇 · 第 24 篇 · 更新于 2026-08-11 · 约 5 分钟阅读
本节目标:认识 pandas 里几种”缺失”的长相,学会用 isna/notna 找出它们,并用 dropna 删除含缺失的行或列。
缺失值有四种长相
pandas 根据列的类型,用不同的”哨兵值”表示缺失:
| 数据类型 | 缺失值 | 说明 |
|---|---|---|
| numpy 数值类型 | NaN | Not a Number,浮点标准里的”非数” |
| 日期 / 时间 | NaT | Not a Time,时间类型的缺失 |
| Nullable 类型(Int64 等) | NA | pandas 1.0 起引入的统一缺失值 |
| object 列 | None / NaN | 看当时塞进去的是哪种 |
它们长相不同,但 isna 一律认得出。先看最常见的 NaN:
import pandas as pd
import numpy as np
s = pd.Series([1, 2, np.nan, 4, 5])
print(s.isna())
print(s.isna().sum()) # 缺失数量
一个坑:NaN 不等于自己
np.nan == np.nan 的结果是 False。这是浮点标准的约定:缺失值”不知道是什么”,所以不跟任何东西相等。判断缺失千万别用 ==:
print(np.nan == np.nan) # False,别用它判断
print(pd.isna(np.nan)) # True,正确姿势
Warning用
df[df["列"] == np.nan]永远选不中任何行,看起来没报错但结果为空。缺失判断一律用 isna() / notna()。
缺失值从哪来
- 文件里就是空的:read_csv 读空单元格得到 NaN;”—”、“unknown” 这类占位符默认不认,需要手动指定(注意 “n/a” 在默认认识列表里,会自动变成 NaN)
- 两个表合并或对齐时,一方没有的键变成 NaN
- reindex 补了新标签(§23)
- 运算传播:NaN 参与运算,结果还是 NaN
- 手动录入
read_csv 指定缺失标记:
import io
data = io.StringIO("a,b\n1,2\n,5\n3,\nn/a,6\n")
df = pd.read_csv(data, na_values=["n/a", "--"])
print(df)
print(df.isna().sum())
isna / notna:检测与统计
isna 返回同形状的布尔表,notna 是它的反义;isnull / notnull 是等价的老名字,混用没问题:
df2 = pd.DataFrame({
"A": [1.0, np.nan, 3.0],
"B": [np.nan, 2.0, 3.0],
"C": [1.0, 2.0, 3.0],
})
print(df2.isna())
print(df2.isna().sum()) # 每列缺失数量
print(df2.isna().any()) # 每列是否含有缺失
print(df2["A"].notna()) # 单列非缺失标记
三个高频组合:
df.isna().sum():每列缺失数量,最常用df.isna().mean():每列缺失比例(mean 对布尔值求平均)df.info():一行一列地显示非空数量,一眼扫出问题列
print(df2.isna().mean())
时间列的缺失:NaT
日期时间列(datetime64)的缺失值是 NaT。它同样遵循”不相等”规则,判断也要用 isna:
s2 = pd.Series([pd.Timestamp("2024-01-01"), pd.NaT])
print(s2)
print(s2.isna())
整数列被 NaN”拖累”
普通 numpy 整数列一出现缺失,就被迫升级成 float64——NaN 是浮点数,装不进 int64:
s3 = pd.Series([1, 2], dtype=np.int64).reindex([0, 1, 2])
print(s3) # 变成了 float64 + NaN
print(s3.dtype)
学号、身份证号这类”整数但不当数字用”的列最怕这个。想保持整数类型,用 Nullable 整数 Int64(§07 提过,§25 展开):
s4 = pd.Series([1, 2, None], dtype="Int64")
print(s4)
dropna:删除含缺失的行或列
dropna 默认删掉”任何位置有缺失”的行:
df3 = pd.DataFrame({
"A": [1.0, np.nan, np.nan, 4.0],
"B": [1.0, 2.0, np.nan, 4.0],
"C": [1.0, 2.0, 3.0, 4.0],
})
print(df3.dropna()) # 第 1、2 行被删
print(df3.dropna(how="all")) # 只删全缺失的行
print(df3.dropna(thresh=2)) # 保留至少 2 个非缺失值的行
print(df3.dropna(axis=1)) # 删列:C 保留,A、B 被删
print(df3.dropna(subset=["A"])) # 只检查 A 列
参数速记:
- how=“any”(默认):有一个缺失就删;how=“all”:整行/整列全缺失才删
- thresh=n:至少有 n 个非缺失值才保留
- subset=[列名]:只在这些列里找缺失
- axis=1:从删行改成删列
dropna 按”有没有缺失”删;想按其他条件删行(比如值不合法),用 df.drop(行索引) 或布尔筛选(§20),别混用。
Tip删除前先算一笔账:
df3.isna().sum()看每列缺多少。整列缺失率过半,删列往往比删行划算;只有零星几行缺失,直接删行最省事。删完对比一下len(df3)和len(df3.dropna()),心里有数删了多少。
缺失在运算中会传播
两个 Series 相加,任何一方缺失,结果就是缺失:
s5 = pd.Series([1.0, np.nan, 3.0])
s6 = pd.Series([10.0, 20.0, 30.0])
print(s5 + s6) # 中间位变 NaN
所以”先处理缺失,再算数”是基本流程。填充和插值的方法在 §25 讲。
缺失值处理四步走
不管删还是填,建议按固定流程走,避免凭感觉:
- 识别:isna() 找出缺失位置
- 统计:isna().sum() / isna().mean() 看数量和比例
- 判断:为什么缺?是录入遗漏、系统没采集,还是合并造成的?
- 处理:比例低就删(dropna),比例高或行重要就填(§25)
缺失值本身也是信息
处理缺失前,值得多想一步:缺失不是”没有数据”那么简单。某个字段系统性缺失(比如只有某一类客户没填收入),可能本身就代表一种情况。先看缺失的分布和规律,再决定删还是填,比上来就 dropna 稳妥得多。
小结
缺失值有四种长相(NaN、NaT、NA、None),判断一律用 isna/notna,别用 ==。删行删列用 dropna,how、thresh、subset 三个参数控制删的力度。动手前先想清楚:缺失是”没有数据”,还是”没配上”——后者往往本身是信息。