pandas numpy处理缺失值,none与nan比较

本文详细对比了Python原生的None和pandas、numpy中的NaN在不同场景下的差异。包括数据类型、作为dict的key、在Series函数中的表现、对函数的支持、对容器数据类型的影响、等值性判断、在groupby中的表现以及写入数据库等方面,最后给出了实践中的处理建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文链接:https://junjiecai.github.io/posts/2016/Oct/20/none_vs_nan/

建议从这里下载这篇文章对应的.ipynb文件和相关资源。这样你就能在Jupyter中边阅读,边测试文中的代码。

python原生的None和pandas, numpy中的numpy.NaN尽管在功能上都是用来标示空缺数据。但它们的行为在很多场景下确有一些相当大的差异。由于不熟悉这些差异,曾经给我的工作带来过不少麻烦。 特此整理了一份详细的实验,比较None和NaN在不同场景下的差异。

实验的结果有些在意料之内,有些则让我大跌眼镜。希望读者看过此文后会None和NaN这对“小妖精”有更深的理解。

为了理解本文的内容,希望本文的读者需要对pandas的Series使用有一定的经验。

首先,导入所需的库

 

from numpy import NaN
from pandas import Series, DataFrame
import numpy as np

数据类型?

None是一个python特殊的数据类型, 但是NaN却是用一个特殊的float

 

type(None)

 

NoneType

 

type(NaN)

 

float

能作为dict的key?

 

{None:1}

 

{None: 1}

 

{NaN:1}

 

{nan: 1}

 

{None:1, NaN:2}

 

{nan: 2, None: 1}

都可以,而且会被认为是不同的key

Series函数中的表现

Series.map

 

s = Series([None, NaN, 'a'])
s

 

0    None
1     NaN
2       a
dtype: object

 

s.map({None:1,'a':'a'})

 

0    1
1    1
2    a
dtype: object

可以看到None和NaN都会替换成了1

 

s.map({NaN:1,'a':'a'})

 

0    1
1    1
2    a
dtype: object

同样None和NaN都会替换成了1

 

s.map({NaN:2,'None':1,'a':'a'})

 

0    2
1    2
2    a
dtype: object

将None替换成1的要求被忽略了

 

s.map({'None':1,NaN:2,'a':'a'})

 

0    2
1    2
2    a
dtype: object

将NaN替换成1的要求被忽略了

总结: 用Series.map对None进行替换时,会“顺便”把NaN也一起替换掉;NaN也会顺便把None替换掉。

如果None和NaN分别定义了不同的映射数值,那么只有一个会生效。

Series.replace中的表现

 

s = Series([None, NaN, 'a'])
s

 

0    None
1     NaN
2       a
dtype: object

 

s.replace([NaN],9)

 

0    9
1    9
2    a
dtype: object

 

s.replace([None],9)

 

0    9
1    9
2    a
dtype: object

和Series.map的情况类似,指定了None的替换值后,NaN会被替换掉;反之亦然。

对函数的支持

numpy有不少函数可以自动处理NaN。

 

np.nansum([1,2,NaN])

 

3.0

但是None不能享受这些函数的便利,如果数据包含的None的话会报错

 

try:
    np.nansum([1,2,None])
except Exception as e:
    print(type(e),e)

 

 unsupported operand type(s) for +: 'int' and 'NoneType'

pandas中也有不少函数支持NaN却不支持None。(毕竟pandas的底层是numpy)

 

import pandas as pd
pd.cut(Series([NaN]),[1,2])

 

0    NaN
dtype: category
Categories (1, object): [(1, 2]]

 

import pandas as pd
try:
    pd.cut(Series([None]),[1,2])
except Exception as e:
    print(type(e),e)

 

 unorderable types: int() > NoneType()

对容器数据类型的影响

混入numpy.array的影响

如果数据中含有None,会导致整个array的类型变成object。

 

np.array([1, None]).dtype

 

dtype('O')

而np.NaN尽管会将原本用int类型就能保存的数据转型成float,但不会带来上面这个问题。

 

np.array([1, NaN]).dtype

 

dtype('float64')

混入Series的影响

下面的结果估计大家能猜到

 

Series([1, NaN])

 

0    1.0
1    NaN
dtype: float64

下面的这个就很意外的吧

 

Series([1, None])

 

0    1.0
1    NaN
dtype: float64

pandas将None自动替换成了NaN!

 

Series([1.0, None])

 

0    1.0
1    NaN
dtype: float64

却是Object类型的None被替换成了float类型的NaN。 这么设计可能是因为None无法参与numpy的大多数计算, 而pandas的底层又依赖于numpy,因此做了这样的自动转化。

不过如果本来Series就只能用object类型容纳的话, Series不会做这样的转化工作。

 

Series(['a', None])

 

0       a
1    None
dtype: object

如果Series里面都是None的话也不会做这样的转化

 

Series([None,None])

 

0    None
1    None
dtype: object

其它的数据类型是bool时,也不会做这样的转化。

 

Series([True, False, None])

 

0     True
1    False
2     None
dtype: object

等值性判断

单值的等值性比较

下面的实验中None和NaN的表现会作为后面的等值性判断的基准(后文称为基准)

 

None == None

 

True

 

NaN == NaN

 

False

 

None == NaN

 

False

在tuple中的情况

这个不奇怪

 

(1, None) == (1, None)

 

True

这个也不意外

 

(1, None) == (1, NaN)

 

False

但是下面这个实验NaN的表现和基准不一致

 

(1, NaN) == (1, NaN)

 

True

在numpy.array中的情况

 

np.array([1,None]) == np.array([1,None])

 

array([ True,  True], dtype=bool)

 

np.array([1,NaN]) == np.array([1,NaN])

 

array([ True, False], dtype=bool)

 

np.array([1,NaN]) == np.array([1,None])

 

array([ True, False], dtype=bool)

和基准的表现一致。 

但是大部分情况我们希望上面例子中, 我们希望左右两边的array被判定成一致。这时可以用numpy.testing.assert_equal函数来处理。 注意这个函数的表现同assert, 不会返回True, False, 而是无反应或者raise Exception

 

np.testing.assert_equal(np.array([1,NaN]), np.array([1,NaN]))

它也可以处理两边都是None的情况

 

np.testing.assert_equal(np.array([1,None]), np.array([1,None]))

但是一边是None,一边是NaN时会被认为两边不一致, 导致AssertionError

 

try:
    np.testing.assert_equal(np.array([1,NaN]), np.array([1,None]))
except Exception as e:
    print(type(e),e)

 

 
Arrays are not equal

(mismatch 50.0%)
 x: array([  1.,  nan])
 y: array([1, None], dtype=object)

在Series中的情况

下面两个实验中的表现和基准一致

 

Series([NaN,'a']) == Series([NaN,'a'])

 

0    False
1     True
dtype: bool

 

Series([None,'a']) == Series([NaN,'a'])

 

0    False
1     True
dtype: bool

但是None和基准的表现不一致。

 

Series([None,'a']) == Series([None,'a'])

 

0    False
1     True
dtype: bool

和array类似,Series也有专门的函数equals用于判断两边的Series是否整体看相等

 

Series([None,'a']).equals(Series([NaN,'a']))

 

True

 

Series([None,'a']).equals(Series([None,'a']))

 

True

 

Series([NaN,'a']).equals(Series([NaN,'a']))

 

True

比numpy.testing.assert_equals更智能些, 三种情况下都能恰当的处理

在DataFrame merge中的表现

两边的None会被判为相同

 

a = DataFrame({'A':[None,'a']})
b = DataFrame({'A':[None,'a']})
a.merge(b,on='A', how = 'outer')

 

 A
0None
1a

两边的NaN会被判为相同

 

a = DataFrame({'A':[NaN,'a']})
b = DataFrame({'A':[NaN,'a']})
a.merge(b,on='A', how = 'outer')

 

 A
0NaN
1a

无论两边都是None,都是NaN,还是都有,相关的列都会被正确的匹配。 注意一边是None,一边是NaN的时候。会以左侧的结果为准。

 

a = DataFrame({'A':[None,'a']})
b = DataFrame({'A':[NaN,'a']})
a.merge(b,on='A', how = 'outer')

 

 A
0None
1a

 

a = DataFrame({'A':[NaN,'a']})
b = DataFrame({'A':[None,'a']})
a.merge(b,on='A', how = 'outer')

 

 A
0NaN
1a

注意

这和空值在postgresql等sql数据库中的表现不一样, 在数据库中, join时两边的空值会被判定为不同的数值

在groupby中的表现

 

d = DataFrame({'A':[1,1,1,1,2],'B':[None,None,'a','a','b']})
d.groupby(['A','B']).apply(len)

 

A  B
1  a    2
2  b    1
dtype: int64

可以看到(1, NaN)对应的组直接被忽略了

 

d = DataFrame({'A':[1,1,1,1,2],'B':[None,None,'a','a','b']})
d.groupby(['A','B']).apply(len)

 

A  B
1  a    2
2  b    1
dtype: int64

(1,None)的组也被直接忽略了

 

d = DataFrame({'A':[1,1,1,1,2],'B':[None,NaN,'a','a','b']})
d.groupby(['A','B']).apply(len)

 

A  B
1  a    2
2  b    1
dtype: int64

那么上面这个结果应该没啥意外的

总结

DataFrame.groupby会忽略分组列中含有None或者NaN的记录

支持写入数据库?

往数据库中写入时NaN不可处理,需转换成None,否则会报错。这个这里就不演示了。

相信作为pandas老司机, 至少能想出两种替换方法。

 

s = Series([None,NaN,'a'])
s

 

0    None
1     NaN
2       a
dtype: object

方案1

 

s.replace([NaN],None)

 

0    None
1    None
2       a
dtype: object

方案2

 

s[s.isnull()]=None
s

 

0    None
1    None
2       a
dtype: object

然而这么就觉得完事大吉的话就图样图森破了, 看下面的例子

 

s = Series([NaN,1])
s

 

0    NaN
1    1.0
dtype: float64

 

s.replace([NaN], None)

 

0    NaN
1    1.0
dtype: float64

 

s[s.isnull()] = None
s

 

0    NaN
1    1.0
dtype: float64

当其他数据是int或float时,Series又一声不吭的自动把None替换成了NaN。

这时候可以使用第三种方法处理

 

s.where(s.notnull(), None)

 

0    None
1       1
dtype: object

where语句会遍历s中所有的元素,逐一检查条件表达式, 如果成立, 从原来的s取元素; 否则用None填充。 这回没有自动替换成NaN

None vs NaN要点总结

  1. 在pandas中, 如果其他的数据都是数值类型, pandas会把None自动替换成NaN, 甚至能将s[s.isnull()]= None,和s.replace(NaN, None)操作的效果无效化。 这时需要用where函数才能进行替换。

  2. None能够直接被导入数据库作为空值处理, 包含NaN的数据导入时会报错。

  3. numpy和pandas的很多函数能处理NaN,但是如果遇到None就会报错。

  4. None和NaN都不能被pandas的groupby函数处理,包含None或者NaN的组都会被忽略。

等值性比较的总结:(True表示被判定为相等)

 None对NoneNaN对NaNNone对NaN
单值TrueFalseFalse
tuple(整体)TrueTrueFalse
np.array(逐个)TrueFalseFalse
Series(逐个)FalseFalseFalse
assert_equalsTrueTrueFalse
Series.equalsTrueTrueTrue
mergeTrueTrueTrue

由于等值性比较方面,None和NaN在各场景下表现不太一致,相对来说None表现的更稳定。

为了不给自己惹不必要的麻烦和额外的记忆负担。 实践中,建议遵循以下三个原则即可

  • 在用pandas和numpy处理数据阶段将None,NaN统一处理成NaN,以便支持更多的函数。
  • 如果要判断Series,numpy.array整体的等值性,用专门的Series.equals,numpy.array函数去处理,不要自己用==判断 * 如果要将数据导入数据库,将NaN替换成None
<think>我们被要求使用中文回答,并且按照给定的格式要求(行内数学表达式用$...$,独立公式用$$...$$)。用户的问题是关于pandas处理数据集中的缺失值,具体提到了fillna, dropna,interpolate三个方法。根据引用内容,我们可以总结如下:1. fillna:用指定的值填充缺失值(引用[1]和引用[3])2. dropna:删除包含缺失值的行或列(引用[4])3.interpolate:插值方法,例如线性插值(引用[3])此外,引用[5]提到了isna和notna用于判断缺失值。回答结构:1.首先介绍缺失值pandas中的表示(NaN, None,NaT等)2.介绍检测缺失值的方法(isna,notna)3.分别详细介绍fillna, dropna,interpolate的使用方法,包括常用参数和示例(根据引用内容提供示例代码)4.总结如何选择这些方法注意:引用内容中的示例代码我们可以借鉴,但需要整合并适当修改以符合问题要求。同时,按照要求,在回答末尾生成相关问题。开始回答:</think>### Pandas 处理数据集缺失值的方法 在 Pandas 中,缺失值通常表示为 `NaN`(Not a Number)、`None` 或 `NaT`(时间缺失)。处理缺失值的主要方法包括填充(`fillna`)、删除(`dropna`)和插值(`interpolate`),具体选择取决于数据特性和分析目标。 #### 1. **检测缺失值** - 使用 `isna()` 或 `isnull()` 检测缺失值(返回布尔矩阵)[^5] - 使用 `notna()` 或 `notnull()` 检测非缺失值 ```python import pandas as pd import numpy as np # 创建示例数据 data = {'A': [1, np.nan, 3, None], 'B': [5, 6, None, 8]} df = pd.DataFrame(data) print("缺失值检测:\n", df.isna()) ``` #### 2. **填充缺失值 (`fillna`)** 用指定值替换缺失值: ```python # 用固定值填充(如0) df_filled = df.fillna(0) print("填充0后的数据:\n", df_filled) # 用统计值填充(如列均值) df_filled_mean = df.fillna(df.mean()) print("填充均值后的数据:\n", df_filled_mean) # 向前填充(ffill)或向后填充(bfill) df_filled_ffill = df.fillna(method='ffill') # 用前一个非空值填充 ``` **适用场景**:数据集较小或缺失率低时,保留数据完整性[^1]。 #### 3. **删除缺失值 (`dropna`)** 直接删除包含缺失值的行或列: ```python # 删除任何包含缺失值的行(默认) df_dropped_rows = df.dropna(axis=0) # 删除任何包含缺失值的列 df_dropped_cols = df.dropna(axis=1) # 仅删除全为缺失值的行 df_dropped_all_na = df.dropna(how='all') # 设置非缺失值数量阈值(thresh=2表示至少2个非空值才保留) df_dropped_thresh = df.dropna(thresh=2) ``` **适用场景**:缺失率较高或数据量充足时[^4]。 #### 4. **插值 (`interpolate`)** 基于现有值计算缺失值: ```python # 线性插值(默认按索引顺序) df_interpolated = df.interpolate() print("线性插值结果:\n", df_interpolated) # 时间序列插值(method='time') time_series = pd.Series([1, None, None, 10], index=pd.date_range('2023', periods=4)) time_series.interpolate(method='time').plot() # 生成插值曲线图 ``` **插值方法**: - `linear`:线性插值(默认) - `polynomial`:多项式插值(需指定`order`) - `spline`:样条插值 - `time`:时间索引插值[^3] **适用场景**:连续型数据(如温度、股价等时间序列)。 #### 5. **方法选择建议** | 方法 | 适用场景 | 优点 | 缺点 | |---------------|-----------------------------------|--------------------------|--------------------| | `fillna` | 缺失率低(<5%),需保留数据量 | 操作简单,保留数据完整性 | 可能引入偏差 | | `dropna` | 缺失率高(>20%),数据量充足 | 完全消除缺失值 | 信息损失 | | `interpolate` | 连续变量,有序数据(如时间序列) | 利用数据趋势 | 对异常值敏感 | > **关键原则**: > - 检查缺失模式:随机缺失(MCAR)可用插值,非随机缺失(MNAR)需谨慎处理 > - 评估影响:通过比较处理前后的统计分布(如 `df.describe()`)判断处理效果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值