前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数据科学 IPython 笔记本 7.7 处理缺失数据

数据科学 IPython 笔记本 7.7 处理缺失数据

作者头像
ApacheCN_飞龙
发布2022-06-03 17:20:05
4K0
发布2022-06-03 17:20:05
举报
文章被收录于专栏:信数据得永生信数据得永生

7.7 处理缺失数据

原文:Handling Missing Data 译者:飞龙 协议:CC BY-NC-SA 4.0 本节是《Python 数据科学手册》(Python Data Science Handbook)的摘录。

许多教程中的数据与现实世界中的数据之间的差异在于,真实世界的数据很少是干净和同构的。特别是,许多有趣的数据集缺少一些数据。为了使事情变得更复杂,不同的数据源可能以不同的方式标记缺失数据。

在本节中,我们将讨论缺失数据的一些一般注意事项,讨论 Pandas 如何选择来表示它,并演示一些处理 Python 中的缺失数据的 Pandas 内置工具。在整本书中,我们将缺失数据称为空值或NaN值。

缺失数据惯例中的权衡

许多方案已经开发出来,来指示表格或DataFrame中是否存在缺失数据。通常,它们围绕两种策略中的一种:使用在全局表示缺失值的掩码,或选择表示缺失条目的标记值。

在掩码方法中,掩码可以是完全独立的布尔数组,或者它可以在数据表示中占用一个比特,在本地表示值的空状态。

在标记方法中,标记值可能是某些特定于数据的惯例,例如例如使用-9999或某些少见的位组合来表示缺失整数值,或者它可能是更全局的惯例,例如使用NaN(非数字)表示缺失浮点值,这是一个特殊值,它是 IEEE 浮点规范的一部分。

这些方法都没有权衡:使用单独的掩码数组需要分配额外的布尔数组,这会增加存储和计算的开销。标记值减少了可以表示的有效值的范围,并且可能需要 CPU 和 GPU 算法中的额外(通常是非最优的)逻辑。 像NaN这样的常见特殊值不适用于所有数据类型。

在大多数情况下,不存在普遍最佳选择,不同的语言和系统使用不同的惯例。例如,R 语言使用每种数据类型中的保留位组合,作为表示缺失数据的标记值,而 SciDB 系统使用表示 NA 状态的额外字节,附加到每个单元。

Pandas 中的缺失数据

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

Pandas 可以遵循 R 的指导,为每个单独的数据类型指定位组合来表示缺失值,但这种方法结果相当笨拙。虽然 R 包含四种基本数据类型,但 NumPy 支持更多:例如,R 具有单个整数类型,但是一旦考虑到编码的可用精度,签名和字节顺序,NumPy 支持十四个基本整数类型。

在所有可用的 NumPy 类型中保留特定的位组合,将产生各种类型的各种操作的大量开销,甚至可能需要 NumPy 包的新分支。 此外,对于较小的数据类型(例如 8 位整数),牺牲一个位用作掩码,将显着减小它可以表示的值的范围。

NumPy 确实支持掩码数组吗?也就是说,附加了一个独立的布尔掩码数组的数组,用于将数据标记为“好”或“坏”。Pandas 可能源于此,但是存储,计算和代码维护的开销,使得这个选择变得没有吸引力。

考虑到这些约束,Pandas 选择使用标记来丢失数据,并进一步选择使用两个已经存在的 Python 空值:特殊浮点值NaN和 Python None对象。我们将要看到,这种选择有一些副作用,但实际上在大多数相关情况下,最终都是很好的妥协。

None:Python 风格的缺失数据

Pandas 使用的第一个标记值是None,这是一个 Python 单例对象,通常用于 Python 代码中的缺失数据。因为它是一个 Python 对象,所以None不能用于任何 NumPy/Pandas 数组,只能用于数据类型为'object'的数组(即 Python 对象数组):

代码语言:javascript
复制
import numpy as np
import pandas as pd

vals1 = np.array([1, None, 3, 4])
vals1

# array([1, None, 3, 4], dtype=object)

这个dtype = object意味着,它是最好的公共类型表示。NumPy 可以推断出,数组的内容是 Python 对象。虽然这种对象数组对于某些目的很有用,但是对数据的任何操作都将在 Python 层面完成,与具有原生类型的数组的常见快速操作相比,其开销要大得多:

代码语言:javascript
复制
for dtype in ['object', 'int']:
    print("dtype =", dtype)
    %timeit np.arange(1E6, dtype=dtype).sum()
    print()
    
'''
dtype = object
10 loops, best of 3: 78.2 ms per loop

dtype = int
100 loops, best of 3: 3.06 ms per loop
'''

在数组中使用 Python 对象也意味着,如果你在一个带有None值的数组中执行sum()min()之类的聚合,你通常会得到错误:

代码语言:javascript
复制
vals1.sum()

'''
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-4-749fd8ae6030> in <module>()
----> 1 vals1.sum()


/Users/jakevdp/anaconda/lib/python3.5/site-packages/numpy/core/_methods.py in _sum(a, axis, dtype, out, keepdims)
     30 
     31 def _sum(a, axis=None, dtype=None, out=None, keepdims=False):
---> 32     return umr_sum(a, axis, dtype, out, keepdims)
     33 
     34 def _prod(a, axis=None, dtype=None, out=None, keepdims=False):


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

这反映了一个事实,即整数和None之间的加法是未定义的。

NaN:缺失的数值数据

另一个缺失的数据表示,NaN(“非数字”的首字母缩写)是不同的;它是所有系统都识别的特殊浮点值,使用标准 IEEE 浮点表示:

代码语言:javascript
复制
vals2 = np.array([1, np.nan, 3, 4]) 
vals2.dtype

# dtype('float64')

请注意,NumPy 为此数组选择了一个原生浮点类型:这意味着与之前的对象数组不同,此数组支持推送到编译代码中的快速操作。你应该知道NaN有点像数据病毒 - 它会感染它触及的任何其他对象。无论操作如何,NaN的算术结果都是另一个NaN

代码语言:javascript
复制
1 + np.nan

# nan

0 *  np.nan

# nan

请注意,这意味着值的聚合是定义良好的(即,它们不会导致错误),但并不总是有用:

代码语言:javascript
复制
vals2.sum(), vals2.min(), vals2.max()

# (nan, nan, nan)

NumPy 确实提供了一些忽略这些缺失值的特殊聚合:

代码语言:javascript
复制
np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)

# (8.0, 1.0, 4.0)

请记住,NaN是一个特殊浮点值;整数,字符串或其他类型没有等效的NaN值。

Pandas 中的NaNNone

NaNNone都有它们的位置,并且 Pandas 的构建是为了几乎可以互换地处理这两个值,在适当的时候在它们之间进行转换:

代码语言:javascript
复制
pd.Series([1, np.nan, 2, None])

'''
0    1.0
1    NaN
2    2.0
3    NaN
dtype: float64
'''

对于没有可用标记值的类型,当存在 NA 值时,Pandas 会自动进行类型转换。例如,如果我们将整数数组中的值设置为np.nan,它将自动向上转换为浮点类型来兼容 NA:

代码语言:javascript
复制
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值。(请注意,有人建议未来向 Pandas 添加原生整数 NA;截至本文撰写时,尚未包含此内容。)

虽然与 R 等领域特定语言中,更为统一的 NA 值方法相比,这种黑魔法可能会有些笨拙,但 Pandas 标记值方法在实践中运作良好,根据我的经验,很少会产生问题。

下表列出了引入 NA 值时 Pandas 中的向上转换惯例:

类型

储存 NA 时的惯例

NA 标记值

floating

不变

np.nan

object

不变

None或np.nan

integer

转换为float64

np.nan

boolean

转换为object

None或np.nan

请记住,在 Pandas 中,字符串数据始终与object dtype一起存储。

空值上的操作

正如我们所看到的,Pandas 将NoneNaN视为基本可互换的,用于指示缺失值或空值。为了促进这个惯例,有几种有用的方法可用于检测,删除和替换 Pandas 数据结构中的空值。他们是:

  • isnull(): 生成表示缺失值的布尔掩码
  • notnull(): isnull()的反转
  • dropna(): 返回数据的过滤后版本
  • fillna(): 返回数据的副本,填充了缺失值

我们将结束本节,简要探讨和演示这些例程。

检测控制

Pandas 数据结构有两种有用的方法来检测空数据:isnull()notnull()。任何一个都返回数据上的布尔掩码。例如:

代码语言:javascript
复制
data = pd.Series([1, np.nan, 'hello', None])

data.isnull()

'''
0    False
1     True
2    False
3     True
dtype: bool
'''

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

代码语言:javascript
复制
data[data.notnull()]

'''
0        1
2    hello
dtype: object
'''

isnull()notnull()方法为DataFrame生成类似的布尔结果。

删除空值

除了之前使用的掩码之外,还有一些方便的方法,dropna()(删除 NA 值)和fillna()(填充 NA 值)。 对于Series,结果很简单:

代码语言:javascript
复制
data.dropna()

'''
0        1
2    hello
dtype: object
'''

对于DataFrame,还有更多选项。考虑以下DataFrame

代码语言:javascript
复制
df = pd.DataFrame([[1,      np.nan, 2],
                   [2,      3,      5],
                   [np.nan, 4,      6]])
df

0

1

2

0

1.0

NaN

2

1

2.0

3.0

5

2

NaN

4.0

6

我们不能从DataFrame中删除单个值;我们只能删除完整行或完整列。取决于应用,你可能需要其中一个,因此dropna()DataFrame提供了许多选项。

默认情况下,dropna()将删除包含空值的所有行:

代码语言:javascript
复制
df.dropna()

0

1

2

1

2.0

3.0

5

或者,你可以沿不同的轴删除 NA 值; axis = 1删除包含空值的所有列:

代码语言:javascript
复制
df.dropna(axis='columns')

2

0

2

1

5

2

6

但这也会丢掉一些好的数据; 你可能更愿意删除全部为 NA 值或大多数为 NA 值的行或列。这可以通过howthresh参数来指定,这些参数能够精确控制允许通过的空值数量。

默认值是how ='any',这样任何包含空值的行或列(取决于axis关键字)都将被删除。你也可以指定how ='all',它只会丢弃全部为空值的行/列:

代码语言:javascript
复制
df[3] = np.nan
df

0

1

2

3

0

1.0

NaN

2

NaN

1

2.0

3.0

5

NaN

2

NaN

4.0

6

NaN

代码语言:javascript
复制
df.dropna(axis='columns', how='all')

0

1

2

0

1.0

NaN

2

1

2.0

3.0

5

2

NaN

4.0

6

对于更细粒度的控制,thresh参数允许你为要保留的行/列指定最小数量的非空值:

代码语言:javascript
复制
df.dropna(axis='rows', thresh=3)

0

1

2

3

1

2.0

3.0

5

NaN

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

填充空值

有时比起删除 NA 值,你宁愿用有效值替换它们。这个值可能是单个数字,如零,或者可能是某种良好的替换或插值。你可以将isnull()方法用作掩码,原地执行此操作,但因为它是如此常见的操作,Pandas 提供fillna()方法,该方法返回数组的副本,其中空值已替换。

考虑下面的Series:

代码语言:javascript
复制
data = pd.Series([1, np.nan, 2, None, 3], index=list('abcde'))
data

'''
a    1.0
b    NaN
c    2.0
d    NaN
e    3.0
dtype: float64
'''

我们可以使用单个值填充 NA 条目,例如零:

代码语言:javascript
复制
data.fillna(0)

'''
a    1.0
b    0.0
c    2.0
d    0.0
e    3.0
dtype: float64
'''

我们可以指定前向填充来传播前一个值:

代码语言:javascript
复制
# 向前填充
data.fillna(method='ffill')

'''
a    1.0
b    1.0
c    2.0
d    2.0
e    3.0
dtype: float64
'''

或者我们可以指定反向填充,来向后传播下一个值:

代码语言:javascript
复制
# 向后填充
data.fillna(method='bfill')

'''
a    1.0
b    2.0
c    2.0
d    3.0
e    3.0
dtype: float64
'''

对于DataFrame,选项也类似,但我们也可以指定axis,沿着该轴进行填充:

代码语言:javascript
复制
df

0

1

2

3

0

1.0

NaN

2

NaN

1

2.0

3.0

5

NaN

2

NaN

4.0

6

NaN

代码语言:javascript
复制
df.fillna(method='ffill', axis=1)

0

1

2

3

0

1.0

1.0

2.0

2.0

1

2.0

3.0

5.0

5.0

2

NaN

4.0

6.0

6.0

请注意,如果在前向填充期间前一个值不可用,则 NA 值仍然存在。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 7.7 处理缺失数据
    • 缺失数据惯例中的权衡
      • Pandas 中的缺失数据
        • None:Python 风格的缺失数据
        • NaN:缺失的数值数据
        • Pandas 中的NaN和None
      • 空值上的操作
        • 检测控制
        • 删除空值
        • 填充空值
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档