NEP 24 — 缺失数据功能 - NEP 12 的替代方案 1#
- 作者:
Nathaniel J. Smith <njs@pobox.com>, Matthew Brett <matthew.brett@gmail.com>
- 状态:
延迟
- 类型:
标准跟踪
- 创建:
2011-06-30
摘要#
背景:此 NEP 是作为 NEP 12 的替代方案编写的,在编写时,NEP 12 的实现已合并到 NumPy 主分支中。
此 NEP 的原则是根据以下内容将掩码和缺失值的 API 分开:
掩码数组的当前实现(NEP 12)
此提案。
此讨论仅涉及 API,不涉及实现。
详细说明#
基本原理#
此 NEP 的目的是定义两个接口 — 一个用于处理“缺失值”,另一个用于处理“掩码数组”。
普通值类似于整数或浮点数。缺失值是不可用普通值的占位符。例如,在处理统计数据时,我们经常构建表格,其中每一行代表一个项目,每一列代表该项目的属性。例如,我们可以对一群人进行调查,并记录每个人的身高、年龄、教育程度和收入,然后将这些值放入表格中。但后来我们发现,我们的研究助理弄错了,忘记记录我们其中一个人的年龄。我们可以将他们剩余的数据也丢弃,但这将是浪费;即使是不完整的一行对于某些分析来说仍然是完全可用的(例如,我们可以计算身高和收入之间的相关性)。处理此问题的传统方法是将一些特定无意义的值作为缺失数据放入,例如,将此人的年龄记录为 0。但这很容易出错;我们以后可能在运行其他分析时忘记这些特殊值,并惊讶地发现婴儿的收入比青少年高。(在这种情况下,解决方案是只排除所有我们没有年龄记录的项目,但这并不是通用的解决方案;许多分析需要更巧妙的方法来处理缺失值。)因此,我们不使用 0 这样的普通值,而是定义一个特殊的“缺失”值,用“NA”表示“不可用”。
因此,缺失值具有以下属性:与任何其他值一样,它们必须由数组的 dtype 支持 — 你不能在 dtype=int32 的数组中存储浮点数,也不能在其中存储 NA。你需要一个 dtype=NAint32 或其他内容的数组(确切语法待定)。否则,它们的行为与任何其他值完全相同。特别地,你可以对它们应用算术函数等。默认情况下,任何以 NA 作为参数的函数总是返回 NA,而不管其他参数的值如何。这确保了如果我们尝试计算收入与年龄的相关性,我们将得到“NA”,表示“鉴于某些条目可能是任何内容,答案也可能是任何内容”。这提醒我们花点时间思考如何重新表述我们的问题使其更有意义。为了方便那些你决定只想要已知年龄和收入之间的相关性的情况,你可以在函数调用中添加一个参数来启用此行为。
对于浮点计算,NA 和 NaN 的行为(几乎?)相同。但它们代表不同的东西 — NaN 代表无效的计算(如 0/0),NA 代表不可用的值 — 区分它们很有用,因为在某些情况下,应该对它们进行不同的处理。(例如,插补过程应该用插补值替换 NA,但可能应该保留 NaN。)无论如何,我们不能对整数、字符串、布尔值使用 NaN,因此我们需要 NA,一旦我们对所有这些类型都支持 NA,我们不妨出于一致性考虑对浮点数也支持它。
掩码数组从概念上来说是一个普通的矩形 numpy 数组,在上面放置了一个任意形状的掩码。结果本质上是一个矩形数组的非矩形视图。原则上,任何你可以用掩码数组完成的事情也可以通过显式地保留一个正则数组和一个布尔掩码数组并使用 numpy 索引将它们组合起来为每个操作完成,但是当你需要对数组的掩码视图执行复杂操作时,将它们组合成单个结构会方便得多,同时仍然能够以通常的方式操作掩码。(也许这是一种很好的启发式方法:一个长度为 4 的数组,其中最后一个值已被掩码,只要你不更改掩码,它就表现得像一个普通的长度为 3 的数组。)当然,你可以随时随地以任意方式操作掩码;它只是一个标准的 numpy 数组。
在某些简单情况下,你可以使用这些工具中的任何一个来完成工作 — 或完全使用其他工具,例如使用指定的代理值(age=0)、单独的掩码数组等。但缺失值旨在在缺失性是数据的内在特征的情况下特别有用 — 在这种情况下,存在一个应该存在的值,如果它存在,它将具有特定的意义,但它不存在。掩码数组旨在在以下情况下特别有用:我们只想暂时忽略一些存在的数据,或者通常当我们需要处理具有非矩形形状的数据时(例如,如果你在覆盖圆形琼脂培养皿的网格上的每个点进行一些测量,那么落在培养皿之外的点不是缺失的测量值,而是没有意义的)。
初始化#
首先,缺失值可以设置为 np.NA, NA
并显示为 np.NA, NA
>>> np.array([1.0, 2.0, np.NA, 7.0], dtype='NA[f8]')
array([1., 2., NA, 7.], dtype='NA[<f8]')
由于初始化并不模糊,因此可以在没有 NA dtype 的情况下编写它
>>> np.array([1.0, 2.0, np.NA, 7.0])
array([1., 2., NA, 7.], dtype='NA[<f8]')
掩码值可以设置为 np.IGNORE, IGNORE
并显示为 np.IGNORE, IGNORE
>>> np.array([1.0, 2.0, np.IGNORE, 7.0], masked=True)
array([1., 2., IGNORE, 7.], masked=True)
由于初始化并不模糊,因此可以在没有 masked=True
的情况下编写它
>>> np.array([1.0, 2.0, np.IGNORE, 7.0])
array([1., 2., IGNORE, 7.], masked=True)
UFunc#
默认情况下,NA 值会传播
>>> na_arr = np.array([1.0, 2.0, np.NA, 7.0])
>>> np.sum(na_arr)
NA('float64')
除非设置了 skipna
标志
>>> np.sum(na_arr, skipna=True)
10.0
默认情况下,掩码不会传播
>>> masked_arr = np.array([1.0, 2.0, np.IGNORE, 7.0])
>>> np.sum(masked_arr)
10.0
除非设置了 propmask
标志
>>> np.sum(masked_arr, propmask=True)
IGNORE
数组可以被掩码,并且可以包含 NA 值
>>> both_arr = np.array([1.0, 2.0, np.IGNORE, np.NA, 7.0])
在默认情况下,行为是显而易见的
>>> np.sum(both_arr)
NA('float64')
对于 skipna=True
,该怎么做也是显而易见的
>>> np.sum(both_arr, skipna=True)
10.0
>>> np.sum(both_arr, skipna=True, propmask=True)
IGNORE
为了打破 NA 和 MSK 之间的联系,NA 传播得更厉害
>>> np.sum(both_arr, propmask=True)
NA('float64')
赋值#
在 NA 情况下是显而易见的
>>> arr = np.array([1.0, 2.0, 7.0])
>>> arr[2] = np.NA
TypeError('dtype does not support NA')
>>> na_arr = np.array([1.0, 2.0, 7.0], dtype='NA[f8]')
>>> na_arr[2] = np.NA
>>> na_arr
array([1., 2., NA], dtype='NA[<f8]')
掩码情况下的直接赋值很神奇且令人困惑,因此只通过掩码进行
>>> masked_array = np.array([1.0, 2.0, 7.0], masked=True)
>>> masked_arr[2] = np.NA
TypeError('dtype does not support NA')
>>> masked_arr[2] = np.IGNORE
TypeError('float() argument must be a string or a number')
>>> masked_arr.visible[2] = False
>>> masked_arr
array([1., 2., IGNORE], masked=True)
版权#
本文档已进入公共领域。