☰
Python数据分析之缺失值检测与处理详解
2026/10/11 10:35:33 网站建设 项目流程

前言


真实数据几乎不可能干干净净,缺失值(missing value)是分析前必须处理的一步。 但"缺失"在 pandas 里并不是一个单一的东西,它至少有四种面孔:



  • NaN(Not a Number):浮点意义上的"非数",是float;

  • None:Python 里的空对象;

  • NaT(Not a Time):时间类型里的缺失;

  • pd.NA:pandas 为可空类型引入的缺失标记。


很多人以为它们可以随便互换,结果在判等、聚合和类型转换上踩坑。 本文先讲清这四者的区别与判定方法,再讲删除与填充的正确姿势。


需要说明:pandas 是第三方库,本机没有 Python 解释器也没有 pandas, 示例无法运行验证,只能逐行人工推演。参数细节以 Pandas 官方文档为准。


一、NaN 与 None 的区别


这是最核心、也最容易错的一点。它们判等行为完全不同:




表达式结果说明



None == NoneTrue普通对象比较

float("nan") == float("nan")FalseNaN 不等于自身

numpy.nan == numpy.nanFalse同上

pandas.NaT == pandas.NaTFalse同 NaN 语义

pandas.isna(x)是否为缺失统一判定入口



正因为NaN != NaN,不能用==或!=来检测缺失, 必须用isna()/notna()(isnull是isna的别名,notnull是notna的别名)。


另一个关键点是类型:NaN是浮点数,所以当整数列出现缺失时, pandas 会把整列提升为float64,3就显示成3.0。 而None放进object列时会原样保留为None;放进浮点列时会被转成NaN。


# pandas 1.0+
import numpy as np
import pandas as pd

s = pd.Series([1, None, 3], dtype="float64")
print(s) # 1.0 NaN 3.0
print(s.isna().tolist()) # [False, True, False]

obj = pd.Series([1, None, 3], dtype=object)
print(obj.tolist()) # [1, None, 3],这里 None 被保留

关键结论:isna()把None、NaN、NaT、pd.NA都视为缺失, 所以检测一律走isna(),不要手写比较。


二、检测:先看清缺失的分布


# pandas 1.0+
df = pd.DataFrame({"a": [1, None, 3], "b": [None, None, 6]})
print(df.isna()) # 每个格子 True/False
print(df.isna().sum()) # 每列缺失个数
print(df.isna().mean()) # 每列缺失比例

df.isna().sum()给出每列的缺失数,df.isna().sum(axis=1)给出每行的缺失数,df.isna().mean()则把缺失比例直接算出来,判断"这列还能不能用"很直观。isna()返回的全是布尔值,所以它可以直接和布尔索引配合:df[df["a"].isna()]就是"把 a 列缺失的那些行挑出来"。


三、删除:dropna


# pandas 1.0+
df.dropna() # 任一行只要有一个缺失就删
df.dropna(axis="columns") # 一列只要有一个缺失就删该列
df.dropna(how="all") # 只有全为缺失才删
df.dropna(subset=["a"]) # 只看 a 列有没有缺失
df.dropna(thresh=2) # 至少保留有 2 个非缺失的行

签名是dropna(*, axis=0, how=<no_default>, thresh=<no_default>, subset=None, inplace=False, ignore_index=False)。 要点:



  • axis=0删行、axis="columns"(即 1)删列;

  • how="any"(默认)任一是缺失即删,how="all"全部缺失才删;

  • subset限定"只看哪几列";

  • thresh=n表示"非缺失值至少要有 n 个才保留"。


四、填充:fillna


# pandas 3.0 的签名:fillna(value, *, axis=None, inplace=False, limit=None)
df.fillna(0) # 常数填充
df.fillna({"a": 0, "b": "未知"}) # 按列填不同值
df.fillna(method="ffill") # 旧写法,见下方说明

版本差异要特别注意:在 pandas 3.0 里,fillna的method关键字 已经被移除,官方推荐改用专门的方法ffill()(前向填充)和bfill()(后向填充):


# pandas 3.0 起推荐
df.ffill() # 用前一个非缺失值填充
df.bfill() # 用后一个非缺失值填充

数值列的平滑填充还可以用interpolate()(线性插值等), 它适合有序列特征(如时间序列、有序指标)的数据:


# pandas 1.0+
ser = pd.Series([1.0, float("nan"), 3.0])
print(ser.interpolate()) # 中间缺失被插值成 2.0

五、可空类型:Int64/boolean/string


如果既想保留整数外观、又想容纳缺失,用首字母大写的可空类型:


# pandas 1.0+ 可用
s = pd.Series([1, None, 3], dtype="Int64")
print(s) # 1, <NA>, 3;缺失是 pd.NA,不是 NaN

注意Int64(可空)与int64(NumPy 的,不能有缺失)是两个不同类型。 在聚合时,pd.NA的传播规则与传统NaN略有差别,处理逻辑要区分对待, 这一点以官方文档的"nullable integer"章节为准。


六、一个完整流程


# pandas 1.0+;fillna 的用法按你实际安装的版本调整
import pandas as pd


def clean(df: pd.DataFrame) -> pd.DataFrame:
# 1) 看缺失比例
ratio = df.isna().mean()
# 2) 缺失超过一半的列直接丢掉
keep = ratio[ratio <= 0.5].index
df = df[keep].copy()
# 3) 数值列用 0 填,类别列用 "未知" 填
num_cols = df.select_dtypes("number").columns
df[num_cols] = df[num_cols].fillna(0)
return df


if __name__ == "__main__":
print(clean(pd.DataFrame({"a": [1.0, None, 3.0]})))

常见坑点



  1. 用==检测缺失


❌df[df["a"] == np.nan],因为NaN != NaN,结果永远是空 ✅df[df["a"].isna()]



  1. 以为None和NaN完全等价


❌ 在object列里写s.fillna("x")前先s == None判断,结果漏掉真正的NaN✅ 统一用s.isna()判定,它同时覆盖两者



  1. 整数列出现缺失后类型被抬升


❌ 用1, None, 3建列,打印成1.0 NaN 3.0觉得奇怪 ✅ 这是int64容不下NaN的必然结果;要保留整数外观用dtype="Int64"



  1. 在 pandas 3.0 里继续用method="ffill"


❌df.fillna(method="ffill"),新版本已移除该关键字,直接报错 ✅ 用df.ffill()/df.bfill()



  1. dropna()一刀切删掉太多


❌ 直接df.dropna(),某列本来就大面积缺失,结果把绝大部分行也删了 ✅ 先用df.isna().sum()看分布,再决定subset、thresh或改填充



  1. 填充时把类别列也填成0


❌df.fillna(0)无差别填充,本该是"未知"的文本列里混进数字0✅ 按列指定df.fillna({"num": 0, "cat": "未知"})



  1. dropna/fillna忘了返回新对象


❌df.dropna()之后照旧用df,发现没生效 ✅ 这些方法默认返回新对象,要接住返回值,或显式inplace=True(并理解它在新版语义)



  1. inplace与链式调用的配合


❌df.dropna(inplace=True).reset_index(),inplace=True返回None,链式直接崩 ✅ 二者选一:要么接返回值,要么inplace=True后单独一行


总结




主题关键点



缺失的四种面孔NaN(float)、None、NaT、pd.NA

判定一律isna()/notna(),绝不用==

检测isna().sum()数个数,isna().mean()算比例

删除dropna(how=/thresh=/subset=)

填充fillna(value=...);3.0 用ffill()/bfill()取代method

类型整数列有缺失会被抬成float;要保整数用Int64



缺失值处理没有万能公式:先看清(检测分布),再决定(删还是填), 最后验证(处理后isna().sum()是否归零)。把NaN != NaN这条记住, 很多"查不出缺失"的怪问题都会迎刃而解。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询