NEP 24 — 缺失数据功能 - NEP 12 的替代方案 1#
- 作者:
Nathaniel J. Smith <njs@pobox.com>, Matthew Brett <matthew.brett@gmail.com>
- 状态:
延迟
- 类型:
标准轨道
- 创建:
2011-06-30
摘要#
上下文:此 NEP 作为 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)
版权#
本文档已进入公共领域。