python中pandas如何处理缺失值(NaN)?

穿越時空
发布: 2025-09-22 23:35:01
原创
566人浏览过

python中pandas如何处理缺失值(nan)?

Python中处理Pandas的缺失值(NaN)是数据清洗的关键一步,核心在于根据数据特性和分析目标,选择删除、填充或更复杂的插值策略,以确保数据质量和分析的准确性。这并非一个一劳永逸的方案,而是需要结合实际业务场景深思熟虑的决策过程。

解决方案

处理Pandas中的NaN值,通常涉及识别、删除、填充和插值这几个主要步骤。

1. 识别缺失值 在动手处理之前,我们得先知道缺失值在哪儿,有多少。

import pandas as pd
import numpy as np

# 示例数据
data = {
    'A': [1, 2, np.nan, 4, 5],
    'B': [np.nan, 2, 3, 4, np.nan],
    'C': [1, 2, 3, 4, 5],
    'D': [np.nan, np.nan, np.nan, 4, 5]
}
df = pd.DataFrame(data)

print("原始DataFrame:\n", df)

# 检查每个元素是否为NaN
print("\n缺失值布尔矩阵:\n", df.isnull())

# 统计每列的缺失值数量
print("\n每列缺失值数量:\n", df.isnull().sum())

# 统计总缺失值数量
print("\n总缺失值数量:", df.isnull().sum().sum())

# 检查非缺失值
print("\n非缺失值布尔矩阵:\n", df.notnull())
登录后复制

2. 删除缺失值 (

dropna()
登录后复制
) 当缺失值数量很少,或者缺失值所在的行/列对分析不重要时,直接删除是最省事的办法。

# 删除包含任何NaN的行
df_dropped_rows = df.dropna()
print("\n删除包含任何NaN的行:\n", df_dropped_rows)

# 删除包含任何NaN的列
df_dropped_cols = df.dropna(axis=1)
print("\n删除包含任何NaN的列:\n", df_dropped_cols)

# 删除所有值都为NaN的行
df_dropped_all_nan_rows = df.dropna(how='all')
print("\n删除所有值都为NaN的行:\n", df_dropped_all_nan_rows)

# 删除指定列中存在NaN的行 (例如,只关心'A'列和'B'列的完整性)
df_dropped_subset = df.dropna(subset=['A', 'B'])
print("\n删除'A'或'B'列有NaN的行:\n", df_dropped_subset)

# 删除至少有N个非NaN值的行 (例如,至少有3个非NaN值才保留该行)
df_thresh = df.dropna(thresh=3)
print("\n保留至少有3个非NaN值的行:\n", df_thresh)
登录后复制

3. 填充缺失值 (

fillna()
登录后复制
) 删除数据有时会损失宝贵信息,这时候填充就显得尤为重要。填充策略的选择,直接影响后续分析的准确性。

# 用一个常数填充所有NaN
df_filled_const = df.fillna(0)
print("\n用0填充所有NaN:\n", df_filled_const)

# 用每列的均值填充NaN
df_filled_mean = df.fillna(df.mean(numeric_only=True))
print("\n用每列均值填充NaN:\n", df_filled_mean)

# 用每列的中位数填充NaN
df_filled_median = df.fillna(df.median(numeric_only=True))
print("\n用每列中位数填充NaN:\n", df_filled_median)

# 用每列的众数填充NaN (注意众数可能不止一个,这里取第一个)
df_filled_mode = df.fillna(df.mode().iloc[0])
print("\n用每列众数填充NaN:\n", df_filled_mode)

# 前向填充 (用前一个有效值填充)
df_ffill = df.fillna(method='ffill')
print("\n前向填充:\n", df_ffill)

# 后向填充 (用后一个有效值填充)
df_bfill = df.fillna(method='bfill')
print("\n后向填充:\n", df_bfill)

# 限制填充次数 (例如,最多填充1个NaN)
df_ffill_limit = df.fillna(method='ffill', limit=1)
print("\n前向填充,限制一次:\n", df_ffill_limit)

# 对特定列进行填充
df_specific_fill = df.copy()
df_specific_fill['A'] = df_specific_fill['A'].fillna(df_specific_fill['A'].mean())
df_specific_fill['B'] = df_specific_fill['B'].fillna('未知') # 假设B是分类数据
print("\n对特定列进行填充:\n", df_specific_fill)
登录后复制

4. 插值处理 (

interpolate()
登录后复制
) 插值是一种更智能的填充方式,它会根据缺失值周围的有效数据点来估计缺失值。尤其适用于时间序列或有明显趋势的数据。

# 线性插值
df_interpolated_linear = df.interpolate(method='linear')
print("\n线性插值:\n", df_interpolated_linear)

# 限制插值方向和次数
df_interpolated_limit = df.interpolate(method='linear', limit_direction='forward', limit=1)
print("\n线性插值,向前限制一次:\n", df_interpolated_limit)

# 多项式插值 (需要安装scipy)
# df_interpolated_poly = df.interpolate(method='polynomial', order=2)
# print("\n多项式插值 (order=2):\n", df_interpolated_poly)

# 时间序列插值 (需要索引是时间戳)
df_ts = pd.DataFrame({'value': [1, 2, np.nan, 4, 5, np.nan, 7]},
                     index=pd.to_datetime(['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04', '2023-01-05', '2023-01-07', '2023-01-08']))
df_ts_interpolated = df_ts.interpolate(method='time')
print("\n时间序列插值:\n", df_ts_interpolated)
登录后复制

为什么我的数据里会有这么多NaN?

说实话,每次看到数据框里一大堆

NaN
登录后复制
,我都会有点头疼。这些缺失值可不是凭空出现的,它们背后往往藏着数据采集、存储或处理过程中的各种“坑”。在我看来,理解这些缺失值的来源,比简单粗暴地处理它们要重要得多,因为这能帮助我们从源头改进数据质量。

最常见的几种情况:

立即学习Python免费学习笔记(深入)”;

  • 数据采集不完整或失败: 传感器没读到数据、用户没填写某个表单字段、网络请求超时导致部分数据丢失。这简直是家常便饭,尤其是在处理真实世界的数据时。
  • 数据合并(Merge/Join)时没有匹配项: 当你把两个数据集基于某个键(比如用户ID)进行合并时,如果某个键只存在于一个数据集中,那么另一个数据集对应的列就会出现
    NaN
    登录后复制
    。这简直是数据分析师的“日常”。
  • 数据转换或计算过程中的产物: 比如,你尝试将一个非数字字符串转换成数字,失败了,结果就是
    NaN
    登录后复制
    。或者,你进行了一个除法运算,分母是零,那结果自然就是
    NaN
    登录后复制
    (或者
    inf
    登录后复制
    ,后面可能转成
    NaN
    登录后复制
    )。
  • 人为录入错误或遗漏: 有些数据是人工输入的,人嘛,总会有犯错的时候,或者直接就忘了填。这种情况下,
    NaN
    登录后复制
    就成了遗漏的标记。
  • 特定值被替换为NaN: 有时候,数据源会用一些特殊值(比如-9999,或者空字符串)来表示缺失,我们在导入Pandas后,通常会把这些特殊值显式地转换成
    np.nan
    登录后复制
    ,方便统一处理。

这些原因决定了我们后续处理

NaN
登录后复制
的策略。如果知道是采集错误,可能需要联系数据源;如果是合并问题,可能需要调整合并策略;如果是计算问题,可能需要检查业务逻辑。光看
NaN
登录后复制
本身是无法知道这些的,得“深挖”一下。

dropna()
登录后复制
fillna()
登录后复制
,我该怎么选?

这真是一个“世纪难题”,没有哪个方法是绝对的好或绝对的坏,它完全取决于你的数据、你的分析目标以及你对“真实性”的容忍度。在我多年的数据处理经验中,我发现这更像是一场权衡利弊的博弈。

什么时候我会倾向于

dropna()
登录后复制

  • 缺失值占比极小: 如果某一列或某几行只有零星的几个
    NaN
    登录后复制
    ,删除它们对整体数据分布和分析结果的影响微乎其微。这时候,
    dropna()
    登录后复制
    简直是效率优先的完美选择。删除这些“小瑕疵”能让数据看起来更整洁。
  • 缺失值是随机的,且无法合理推断: 有些缺失值就是纯粹的随机事件,你找不到任何合理的模式去填充它们。与其瞎猜引入偏差,不如直接删除,保持数据的“纯粹性”。
  • 数据量足够大: 当你的数据集规模庞大时,删除少量含有缺失值的行或列,对你的模型训练或统计分析来说,就像大海里的一滴水,几乎可以忽略不计。
  • 缺失值本身就意味着“无效”: 比如,一个用户没有填写手机号,那么这行数据在涉及手机号的分析中可能就是无效的,直接删除反而更符合业务逻辑。

但话说回来,

dropna()
登录后复制
的缺点也很明显,那就是信息损失。你删掉的每一行或每一列,都可能包含其他有价值的信息。有时候,仅仅因为一个字段缺失,就扔掉整条记录,实在是有点可惜。

如知AI笔记
如知AI笔记

如知笔记——支持markdown的在线笔记,支持ai智能写作、AI搜索,支持DeepseekR1满血大模型

如知AI笔记 27
查看详情 如知AI笔记

那么,什么时候我会更青睐

fillna()
登录后复制

  • 缺失值占比不容忽视: 如果删除会导致大量数据丢失,从而严重影响样本量,甚至改变数据分布,那么
    fillna()
    登录后复制
    就是救命稻草。
  • 可以根据业务逻辑或统计方法合理推断缺失值:
    • 均值/中位数/众数填充: 对于数值型数据,如果缺失值是随机的,且不影响整体分布,用均值或中位数填充是个不错的选择。特别是中位数,它对异常值不敏感。对于分类数据,众数填充通常更合理。
    • 前向/后向填充 (
      ffill
      登录后复制
      /
      bfill
      登录后复制
      ):
      对于时间序列数据,如果缺失值是短暂的,或者数据变化缓慢,用前一个或后一个有效值填充往往是合理的。比如,一个传感器在某个时刻短暂失灵,但前后读数变化不大,那么用最近的有效读数填充就比删除要好得多。
    • 分组填充: 这是我个人非常喜欢的一种方式。比如,我想填充某个产品的销售额缺失值,但我知道不同地区的产品销售额差异很大。这时,我会先按地区分组,然后在每个组内用该地区的平均销售额来填充。这样既保留了数据的完整性,又兼顾了数据的局部特性。
  • 需要保持数据完整性: 某些分析场景(如时间序列分析、面板数据分析)对数据的完整性要求很高,删除行可能会破坏时间序列的连续性或面板数据的结构,这时填充就成了首选。

我的个人观点: 绝大多数情况下,我都会优先考虑

fillna()
登录后复制
dropna()
登录后复制
虽然简单,但带来的信息损失往往是不可逆的。我会花更多时间去思考,这个
NaN
登录后复制
到底应该用什么来填充?是均值?中位数?还是根据业务逻辑来定制?甚至,我会为缺失值创建一个指示变量(一个0/1的列,表示该值是否缺失),让模型自己去学习缺失值可能带来的影响。因为在我看来,缺失本身,有时也是一种信息

缺失值填充的“陷阱”与高级技巧有哪些?

当我们决定用

fillna()
登录后复制
来处理缺失值时,其实就踏入了一个充满“陷阱”的区域。填充并非万能药,不恰当的填充反而可能引入新的问题,甚至误导我们的分析。但同时,Pandas也提供了一些高级技巧,能让我们更智能地应对这些挑战。

填充的“陷阱”:

  1. 简单填充可能改变数据分布:
    • 均值填充: 虽然方便,但它会降低数据的方差。想象一下,你用平均值填充了一堆缺失值,这些新值都挤在平均值附近,使得数据的离散程度看起来比实际要小。这可能导致你的统计检验结果不准确,或者模型对特征的方差估计出现偏差。
    • 中位数填充: 比均值填充对异常值更鲁棒,但同样会减少数据的方差。
    • 众数填充: 主要用于分类数据,如果用于数值数据,可能会引入不自然的“峰值”。
  2. 前向/后向填充可能引入未来信息或不合理推断:
    • 在处理时间序列数据时,使用
      bfill
      登录后复制
      (后向填充)可能会用“未来”的数据来填充“过去”的缺失值,这在某些预测任务中是绝对不允许的,因为它会导致数据泄露。
    • 即使是
      ffill
      登录后复制
      (前向填充),如果缺失的时间跨度太大,用一个很久以前的值来填充,也可能导致数据失真。
  3. 对分类数据使用数值型填充: 如果你把一个分类变量(比如“颜色”:红、绿、蓝)的缺失值用均值或中位数填充,那简直是无稽之谈。这种情况下,应该用众数填充,或者填充一个表示“未知”的新类别。
  4. 填充后没有验证: 很多人填充完就直接进行下一步分析了,却忘了检查填充后的数据是否合理。比如,填充后的数值是否超出了合理的范围?填充后的数据是否引入了新的异常值?

高级技巧:

  1. 分组填充 (

    groupby().transform()
    登录后复制
    ): 这是我个人非常推崇的一种方法。它允许你在数据的子组内进行填充,而不是在整个数据集上。例如,你想填充不同城市居民的收入缺失值,那么用全国平均收入填充可能不合理,但用该城市自己的平均收入填充就更靠谱。

    # 假设我们有一个DataFrame,包含城市和收入,有些收入缺失
    df_group = pd.DataFrame({
        'City': ['A', 'A', 'B', 'B', 'A', 'B', 'A', 'B'],
        'Income': [5000, 6000, 7000, np.nan, np.nan, 8000, 5500, np.nan]
    })
    print("原始分组数据:\n", df_group)
    
    # 按城市分组,然后用每个城市的平均收入填充缺失值
    df_group['Income_filled'] = df_group.groupby('City')['Income'].transform(lambda x: x.fillna(x.mean()))
    print("\n按城市分组填充收入:\n", df_group)
    登录后复制

    transform
    登录后复制
    方法确保了填充后的Series与原始DataFrame的索引对齐,非常方便。

  2. 更复杂的插值方法 (

    interpolate()
    登录后复制
    ): 除了线性插值,
    interpolate()
    登录后复制
    还支持多种方法,比如
    polynomial
    登录后复制
    (多项式插值)、
    spline
    登录后复制
    (样条插值)、
    pchip
    登录后复制
    等。这些方法可以更好地捕捉数据中的非线性趋势。对于时间序列数据,
    method='time'
    登录后复制
    是根据时间戳的间隔来加权插值,非常智能。

    # 示例,更复杂的插值,需要数据有一定趋势
    s = pd.Series([0, 1, np.nan, np.nan, 5, 6])
    print("\n原始Series:\n", s)
    print("多项式插值 (order=2):\n", s.interpolate(method='polynomial', order=2))
    # print("样条插值 (order=2):\n", s.interpolate(method='spline', order=2)) # 需要scipy
    登录后复制

    选择哪种插值方法,需要对数据背后生成机制有一定理解,或者通过交叉验证来选择最优方法。

  3. 基于模型预测缺失值: 这是最“硬核”的缺失值处理方法之一。你可以把缺失值所在的列当作目标变量,用其他列作为特征,训练一个机器学习模型(比如线性回归、决策树、KNN等)来预测缺失值。例如,Scikit-learn库中的

    IterativeImputer
    登录后复制
    (MICE算法的实现)或
    KNNImputer
    登录后复制
    都是很好的工具

    • KNNImputer: 使用K近邻算法,根据与缺失值最相似的K个样本的特征值来填充缺失值。
    • IterativeImputer (MICE): 这是一个更复杂的迭代方法,它会为每个包含缺失值的特征建立一个预测模型,然后循环地用其他特征来预测并填充该特征的缺失值,直到收敛。 这种方法虽然计算成本较高,但往往能提供更准确的填充结果,尤其是在缺失值与其他特征之间存在复杂关系时。
  4. 添加缺失值指示变量: 有时候,缺失本身就是一种信息。例如,用户没有填写年龄,可能意味着他们不想透露,或者年龄不适用。在这种情况下,你可以创建一个新的二元变量(0或1),表示原始数据点是否缺失。然后,即使你用某种方法填充了原始缺失值,这个指示变量也能让你的模型知道哪些数据点最初是缺失的。这可以帮助模型捕捉缺失值可能带来的特殊含义。

处理缺失值,没有银弹。在我看来,最重要的就是理解你的数据你的分析目的。多尝试不同的方法,并评估它们对最终结果的影响,才是王道。

以上就是python中pandas如何处理缺失值(NaN)?的详细内容,更多请关注php中文网其它相关文章!

python速学教程(入门到精通)
python速学教程(入门到精通)

python怎么学习?python怎么入门?python在哪学?python怎么学才快?不用担心,这里为大家提供了python速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号