Pandas处理缺失数据的方式汇总

2025-09-24 12:50

本文主要是介绍Pandas处理缺失数据的方式汇总,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Pandas处理缺失数据的方式汇总》许多教程中的数据与现实世界中的数据有很大不同,现实世界中的数据很少是干净且同质的,本文我们将讨论处理缺失数据的一些常规注意事项,了解Pandas如何表示缺失数据,...

Pandas处理缺失数据的方式汇总

许多教程中的数据与现实世界中的数据有很大不同,现实世界中的数据很少是干净且同质的。

尤其是,许多有趣的数据集都会存在一定程度的数据缺失。

更复杂的是,不同的数据来源可能会用不同的方式表示缺失数据。

我们将讨论处理缺失数据的一些常规注意事项,了解 Pandas 如何表示缺失数据,并探索 Pandas 在 python 中处理缺失数据的一些内置工具。

在本书中及整个过程中,我会将缺失数据统称为 null、NaN 或 NA 值。

缺失数据约定的权衡

为了在表格或 DataFrame 中跟踪缺失数据的存在,已经开发了多种方法。通常,这些方法围绕两种策略展开:使用全局指示缺失值的 掩码,或选择一个表示缺失项的 哨兵值。

在掩码方法中,掩码可以是一个完全独立的布尔数组,也可以通过占用数据表示中的某一位来局部指示值的缺失状态。

在哨兵值方法中,哨兵值可以是某种特定于数据的约定,比如用 –9999 或某个罕见的位模式表示缺失的整数值,或者采用更通用的约定,比如用 NaN(非数字 Not a Number)表示缺失的浮点值,这是 IEEE 浮点规范中的特殊值。

这两种方法都存在权衡。使用单独的掩码数组需要分配额外的布尔数组,这会增加存储和计算的开销。哨兵值则减少了可表示的有效值范围,并且可能需要额外(通常是非优化的)CPU 和 GPU 算术逻辑,因为像 NaN 这样的常用特殊值并不适用于所有数据类型。

正如大多数没有普遍最优选择的情况一样,不同的语言和系统采用了不同的约定。例如,R 语言在每种数据类型中使用保留的位模式作为指示缺失数据的哨兵值,而 SciDB 系统则为每个单元格附加一个额外的字节来指示 NA 状态。

Pandas 中的缺失数据

Pandas 处理缺失值的方式受到其对 NumPy 包的依赖的限制,而 NumPy 对于非浮点数据类型并没有内置的 NA(缺失值)概念。

或许 Pandas 可以像 R 一样,为每种数据类型指定位模式来表示空值,但这种方法实际上非常繁琐。R 只有 4 种主要数据类型,而 NumPy 支持的类型远远多于此:例如,R 只有一种整数类型,而 NumPy 在考虑不同的位宽、符号和字节序后,基本整数类型就有 14 种。如果要在所有可用的 NumPy 类型中保留特定的位模式,将导致在各种类型的操作中python需要大量特殊处理,甚至可能需要为 NumPy 包开发新的分支。此外,对于较小的数据类型(如 8 位整数),牺牲一位作为掩码会显著减少其可表示的数值范围。

由于这些限制和权衡,Pandas 在存储和处理空值时有两种“模式”:

  • 默认模式是使用哨兵值(sentinel)来表示缺失数据,根据数据类型使用 NaNNone 作为哨兵值。
  • 另外,你可以选择使用 Pandas 提供的可空数据类型(nullable dtypes,后文会详细介绍),这会创建一个额外的掩码数组来跟踪缺失项,并将这些缺失项以特殊的 pd.NA 值呈现给用户。

无论采用哪种方式,Pandas API 提供的数据操作和处理方法都会以可预测的方式处理和传播这些缺失项。但为了更好地理解这些选择背后的原因,我们需要快速了解一下 NoneNaNNA 的权衡。像往常一样,我们先导入 NumPy 和 Pandas:

import numpChina编程y as np
import pandas as pd

None 作为哨兵值

对于某些数据类型,Pandas 使用 None 作为哨兵值。None 是一个 Python 对象,这意味着任何包含 None 的数组都必须具有 dtype=object,即它必须是一个 Python 对象的序列。

例如,观察如果你将 None 传递给 NumPy 数组会发生什么:

vals1 = np.array([1, None, 2, 3])
vals1
array([1, None, 2, 3], dtype=object)

这种 dtype=object 表示 NumPy 能为数组内容推断出的最佳通用类型是 Python 对象。
以这种方式使用 None 的缺点是,对数据的操作会在 Python 层面进行,开销远大于对原生类型数组的高效操作:

%timeit np.arange(1E6, dtype=int).sum()
2.89 ms  24.6 s per loop (mean  std. dev. of 7 runs, 100 loops each)
%timeit np.arange(1E6, dtype=object).sum()
26.7 ms  454 s per loop (mean  std. dev. of 7 runs, 10 loops each)

进一步,由于 Python 不支持使用 None 进行算术运算,因此诸如 summin 之类的聚合操作通常会导致错误:

vals1.sum()
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[5], line 1
----> 1 vals1.sum()


File d:\Source\Repos\Visual Studio Code\MAChineLearning-notes\.venv\Lib\site-packages\numpy\_core\_methods.py:53, in _sum(a, axis, dtype, out, keepdims, initial, where)
     51 def _sum(a, axis=None, dtype=None, out=None, keepdims=False,
     52          initial=_NoValue, where=True):
---> 53     return umr_sum(a, axis, dtype, out, keepdims, initial, where)


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

鉴于此,Pandas 不会在其数值数组中使用 None 作为哨兵值。

NaN:缺失的数值数据

另一种缺失数据的哨兵值是 NaN,它有所不同;NaN 是一种特殊的浮点值,被所有采用标准 IEEE 浮点表示法的系统所识别:

vals2 = np.array([1, np.nan, 3, 4]) 
vals2
array([ 1., nan,  3.php,  4.])

请注意,NumPy 为该数组选择了本地浮点类型:这意味着与之前的 object 数组不同,这个数组支持被编译代码加速的快速操作。
需要记住的是,NaN 有点像数据病毒——它会“感染”它接触到的任何其他对象。
无论进行何种运算,与 NaN 进行算术运算的结果仍然是另一个 NaN

1 + np.nan
nan
0 * npythonp.nan
nan

这意味着对这些值进行聚合操作是有定义的(即不会导致错误),但结果并不总是有用:

vals2.sum(), vals2.min(), vals2.max()
(np.float64(nan), np.float64(nan), np.float64(nan))

也就是说,NumPy 提供了能够识别 NaN 并忽略这些缺失值的聚合函数版本:

np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)
(np.float64(8.0), np.float64(1.0), np.float64(4.0))

NaN 的主要缺点在于它仅适用于浮点类型;对于整数、字符串或其他类型,并没有等价的 NaN 值。

Pandas 中的 NaN 和 None

NaNNone 各有其用途,Pandas 能够几乎无缝地处理这两者,并在适当的时候自动进行转换:

pd.Series([1, np.nan, 2, None])
0    1.0
1    NaN
2    2.0
3    NaN
dtype: float64

对于没有可用哨兵值的数据类型,当存在 NA 值时,Pandas 会自动进行类型转换。
例如,如果我们在一个整数数组中设置某个值为 np.nan,它会自动被提升为浮点类型以容纳 NA:

x = pd.Series(range(2), dtype=int)
x
0    0
1    1
dtype: int64
x[0] = None
x
0    NaN
1    1.0
dtype: float64

请注意,除了将整数数组转换为浮点型之外,Pandas 还会自动将 None 转换为 NaN 值。

虽然与 R 这类领域专用语言中对 NA 值更统一的处理方式相比,这种哨兵值/类型转换的“魔法”看起来有些取巧,但实际上 Pandas 的这种做法在实践中效果很好,而且据我的经验,只有极少数情况下会引发问题。

下表列出了在 Pandas 中引入 NA 值时的类型提升约定:

类型类别存储 NA 时的转换方式NA 哨兵值
floating无变化np.nan
object无变化None 或 np.nan
integer转换为 float64np.nan
boolean转换为 objectNone 或 np.nan

请记住,在 Pandas 中,字符串数据始终以 object 类型存储。

Pandas 可空数据类型

在早期版本的 Pandas 中,NaNNone 作为哨兵值是唯一可用的缺失数据表示方法。这带来的主要问题在于隐式类型转换:例如,无法表示带有缺失数据的真正整数数组。

为了解决这个问题,Pandas 后来引入了可空数据类型(nullable dtypes),它们与常规数据类型的区别在于名称的大小写(例如,pd.Int32np.int32)。为了向后兼容,只有在明确指定时才会使用这些可空数据类型。

例如,下面是一个包含缺失数据的整数序列 Series,它由一个包含三种可用缺失值标记的列表创建:

pd.Series([1, np.nan, 2, None, pd.NA], dtype='Int32')
0       1
1    <NA>
2       2
3    <NA>
4    <NA>
dtype: Int32

这种表示方法在本文后续讨论的所有操作中都可以与其他表示方法互换使用。

对缺失值的操作

如前所述,Pandas 将 NoneNaNNA 视为本质上可以互换的缺失值标记。
为了方便这一约定,Pandas 提供了多种方法来检测、移除和替换 Pandas 数据结构中的缺失值。
这些方法包括:

  • isnull:生成一个布尔掩码,用于指示缺失值
  • notnull:与 isnull 相反
  • dropna:返回过滤后的数据副本,移除缺失值
  • fillna:返回填充或插补缺失值后的数据副本

我们将简要探索并演示这些常用方法的用法。

检测空值

Pandas 数据结构有两个用于检测空数据的有用方法:isnullnotnull
这两个方法都会返回数据上的布尔掩码。例如:

data = pd.Series([1, np.nan, 'hello', None])
data.isnull()
0    False
1     True
2    False
3     True
dtype: bool

如数据索引和选择中所述,布尔掩码可以直接作为 SeriesDataFrame 的索引使用:

data[data.notnull()]
0        1
2    hello
dtype: object

isnull()notnull() 方法对于 DataFrame 对象也会产生类似的布尔结果。

删除空值

除了这些掩码方法外,还有一些便捷方法,如 dropna(用于移除 NA 值)和 fillna(用于填充 NA 值)。对于 Series,其结果很直接:

data.dropna()
0        1
2    hello
dtype: object

对于 DataFrame,有更多的选项。
请看下面的 DataFrame

df = pd.DataFrame([[1,      np.nan, 2],
                   [2,      3,      5],
                   [np.nan, 4,      6]])
df
012
01.0NaN2
12.03.05
2NaN4.06

我们无法从 DataFrame 中删除单个值;只能删除整行或整列。
根据具体应用,你可能需要删除行或列,因此 dropnaDataFrame 提供了多种选项。

默认情况下,dropna 会删除所有包含任意空值的行:

df.dropna()
012
12.03.05

或者,你也可以沿不同的轴删除 NA 值。使用 axis=1axis='columns' 可以删除所有包含空值的列:

df.dropna(axis='columns')
2
02
15
26

但这样也会丢弃一些有用的数据;你可能更希望只删除那些全部为 NA 的行或列,或者 NA 占多数的行或列。
这可以通过 howthresh 参数来指定,从而精细控制允许通过的空值数量。

默认情况下,how='any',即只要某行或某列包含空值就会被删除。
你也可以指定 how='all',这样只有那些全部为空值的行或列才会被删除:

df[3] = np.nan
df
0123
01.0NaN2NaN
12.03.05NaN
2NaN4.06NaN
df.dropna(axis='columns', how='all')
012
01.0NaN2
12.03.05
2NaN4.06
df.dropna(axis='rows', thresh=3)
0123
12.03.05NaN

这里,第一行和最后一行被删除了,因为它们每行只有两个非空值。

填充空值

有时候,与其删除 NA 值,你可能更希望用一个有效值来替换它们。
这个值可以是像零这样的单个数字,也可以是通过已有有效值进行插补或插值得到的某种值。
你可以使用 isnull 方法作为掩码进行原地替换,但由于这是一个非常常见的操作,Pandas 提供了 fillna 方法,它会返回一个用指定值替换空值后的数组副本。

来看下面的 Series 示例:

data = pd.Series([1, np.nan, 2, None, 3], index=list('abcde'), dtype='Int32')
data
a       1
b    <NA>
c       2
d    <NA>
e       3
dtype: Int32

我们可以用单个值(例如零)来填充 NA 项:

data.fillna(0)
a    1
b    0
c    2
d    0
e    3
dtype: Int32

我们可以指定前向填充(forward fill),将前一个有效值向前传播:

# 前向填充,旧方法,在后续Pandas版本中可能会被弃用
data.fillna(method='ffill')
\AppData\Local\Temp\ipykernel_13548\3988156040.py:2: FutureWarning: Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.
  data.fillna(method='ffill')





a    1
b    1
c    2
d    2
e    3
dtype: Int32
# 前向填充
data.ffill()
a    1
b    1
c    2
d    2
e    3
dtype: Int32

或者我们可以指定反向填充(backward fill),将下一个有效值向后传播:

# 后向填充,旧方法,在后续Pandas版本中可能会被弃用
data.fillna(method='bfill')
\AppData\Local\Temp\ipykernel_13548\1439583404.py:2: FutureWarning: Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.
  data.fillna(method='bfilhttp://www.chinasem.cnl')





a    1
b    2
c    2
d    3
e    3
dtype: Int32
# 后向填充
data.bfill()
a    1
b    2
c    2
d    3
e    3
dtype: Int32

DataFrame 的情况下,选项类似,但我们还可以指定填充操作应沿着哪个 axis(轴)进行:

df
0123
01.0NaN2NaN
12.03.05NaN
2NaN4.06NaN
df.ffill(axis=1)
0123
01.01.02.02.0
12.03.05.05.0
2NaN4.06.06.0

请注意,如果在前向填充时没有可用的前一个值,NA 值将保持不变。

总结

本文介绍了现实世界中缺失数据的常见情况及其在 Pandas 中的处理方式。我们讨论了缺失值的两种主要表示方法(掩码和哨兵值),并重点介绍了 Pandas 对 NoneNaNpd.NA 的支持及其背后的权衡。通过示例演示了缺失值的检测(isnullnotnull)、删除(dropna)和填充(fillnaffillbfill)等常用操作。此外,还介绍了 Pandas 的可空数据类型(如 Int32),使得带缺失值的整数数据能够被更好地支持。掌握这些方法有助于在数据分析过程中更高效、灵活地处理缺失数据问题。

以上就是Pandas处理缺失数据的方式汇总的详细内容,更多关于Pandas处理缺失数据的资料请关注China编程(www.chinasem.cn)其它相关文章!

这篇关于Pandas处理缺失数据的方式汇总的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1156006

相关文章

C#使用iText获取PDF的trailer数据的代码示例

《C#使用iText获取PDF的trailer数据的代码示例》开发程序debug的时候,看到了PDF有个trailer数据,挺有意思,于是考虑用代码把它读出来,那么就用到我们常用的iText框架了,所... 目录引言iText 核心概念C# 代码示例步骤 1: 确保已安装 iText步骤 2: C# 代码程

C++中处理文本数据char与string的终极对比指南

《C++中处理文本数据char与string的终极对比指南》在C++编程中char和string是两种用于处理字符数据的类型,但它们在使用方式和功能上有显著的不同,:本文主要介绍C++中处理文本数... 目录1. 基本定义与本质2. 内存管理3. 操作与功能4. 性能特点5. 使用场景6. 相互转换核心区别

java读取excel文件为base64实现方式

《java读取excel文件为base64实现方式》文章介绍使用ApachePOI和EasyExcel处理Excel文件并转换为Base64的方法,强调EasyExcel适合大文件且内存占用低,需注意... 目录使用 Apache POI 读取 Excel 并转换为 Base64使用 EasyExcel 处

Python动态处理文件编码的完整指南

《Python动态处理文件编码的完整指南》在Python文件处理的高级应用中,我们经常会遇到需要动态处理文件编码的场景,本文将深入探讨Python中动态处理文件编码的技术,有需要的小伙伴可以了解下... 目录引言一、理解python的文件编码体系1.1 Python的IO层次结构1.2 编码问题的常见场景二

Spring Boot中获取IOC容器的多种方式

《SpringBoot中获取IOC容器的多种方式》本文主要介绍了SpringBoot中获取IOC容器的多种方式,包括直接注入、实现ApplicationContextAware接口、通过Spring... 目录1. 直接注入ApplicationContext2. 实现ApplicationContextA

python库pydantic数据验证和设置管理库的用途

《python库pydantic数据验证和设置管理库的用途》pydantic是一个用于数据验证和设置管理的Python库,它主要利用Python类型注解来定义数据模型的结构和验证规则,本文给大家介绍p... 目录主要特点和用途:Field数值验证参数总结pydantic 是一个让你能够 confidentl

linux查找java项目日志查找报错信息方式

《linux查找java项目日志查找报错信息方式》日志查找定位步骤:进入项目,用tail-f实时跟踪日志,tail-n1000查看末尾1000行,grep搜索关键词或时间,vim内精准查找并高亮定位,... 目录日志查找定位在当前文件里找到报错消息总结日志查找定位1.cd 进入项目2.正常日志 和错误日

Python函数的基本用法、返回值特性、全局变量修改及异常处理技巧

《Python函数的基本用法、返回值特性、全局变量修改及异常处理技巧》本文将通过实际代码示例,深入讲解Python函数的基本用法、返回值特性、全局变量修改以及异常处理技巧,感兴趣的朋友跟随小编一起看看... 目录一、python函数定义与调用1.1 基本函数定义1.2 函数调用二、函数返回值详解2.1 有返

JAVA实现亿级千万级数据顺序导出的示例代码

《JAVA实现亿级千万级数据顺序导出的示例代码》本文主要介绍了JAVA实现亿级千万级数据顺序导出的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 前提:主要考虑控制内存占用空间,避免出现同时导出,导致主程序OOM问题。实现思路:A.启用线程池

Nginx屏蔽服务器名称与版本信息方式(源码级修改)

《Nginx屏蔽服务器名称与版本信息方式(源码级修改)》本文详解如何通过源码修改Nginx1.25.4,移除Server响应头中的服务类型和版本信息,以增强安全性,需重新配置、编译、安装,升级时需重复... 目录一、背景与目的二、适用版本三、操作步骤修改源码文件四、后续操作提示五、注意事项六、总结一、背景与