首页 / Pandas 入门教程 / 缺失值识别与处理

Pandas 入门教程

缺失值识别与处理

本教程共 54 篇 · 第 24 篇 · 更新于 2026-08-11 · 约 5 分钟阅读

PandasPandas 入门教程缺失值isnadropnaNaN

本节目标:认识 pandas 里几种”缺失”的长相,学会用 isna/notna 找出它们,并用 dropna 删除含缺失的行或列。

缺失值有四种长相

pandas 根据列的类型,用不同的”哨兵值”表示缺失:

数据类型缺失值说明
numpy 数值类型NaNNot a Number,浮点标准里的”非数”
日期 / 时间NaTNot a Time,时间类型的缺失
Nullable 类型(Int64 等)NApandas 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()。

缺失值从哪来

  1. 文件里就是空的:read_csv 读空单元格得到 NaN;”—”、“unknown” 这类占位符默认不认,需要手动指定(注意 “n/a” 在默认认识列表里,会自动变成 NaN)
  2. 两个表合并或对齐时,一方没有的键变成 NaN
  3. reindex 补了新标签(§23)
  4. 运算传播:NaN 参与运算,结果还是 NaN
  5. 手动录入

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 讲。

缺失值处理四步走

不管删还是填,建议按固定流程走,避免凭感觉:

  1. 识别:isna() 找出缺失位置
  2. 统计:isna().sum() / isna().mean() 看数量和比例
  3. 判断:为什么缺?是录入遗漏、系统没采集,还是合并造成的?
  4. 处理:比例低就删(dropna),比例高或行重要就填(§25)

缺失值本身也是信息

处理缺失前,值得多想一步:缺失不是”没有数据”那么简单。某个字段系统性缺失(比如只有某一类客户没填收入),可能本身就代表一种情况。先看缺失的分布和规律,再决定删还是填,比上来就 dropna 稳妥得多。

小结

缺失值有四种长相(NaN、NaT、NA、None),判断一律用 isna/notna,别用 ==。删行删列用 dropna,how、thresh、subset 三个参数控制删的力度。动手前先想清楚:缺失是”没有数据”,还是”没配上”——后者往往本身是信息。