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 的原则是根据以下内容将掩码(masking)和缺失值(missing values)的 API 分开:
掩码数组的当前实现 (NEP 12)
本提案。
本讨论仅涉及 API,不涉及实现。
详细描述#
基本原理#
本 NEP 的目的是定义两种接口——一种用于处理“缺失值”,另一种用于处理“掩码数组”。
普通值是指整数或浮点数之类的东西。*缺失*值是由于某种原因不可用的普通值的占位符。例如,在处理统计数据时,我们经常构建表格,其中每行代表一个条目,每列代表该条目的属性。例如,我们可能会选择一群人,并记录每个人的身高、年龄、教育水平和收入,然后将这些值放入表格中。但后来我们发现我们的研究助理搞砸了,忘记记录其中一个人的年龄。我们也可以丢弃他们其余的数据,但这会造成浪费;即使是不完整的一行,对于某些分析(例如,我们可以计算身高和收入的相关性)也仍然完全可用。处理这种情况的传统方法是为缺失数据填充一些特定的无意义值,例如,将此人的年龄记录为 0。但这很容易出错;我们以后在进行其他分析时可能会忘记这些特殊值,并惊讶地发现婴儿的收入比青少年高。(在这种情况下,解决方案是简单地排除所有未记录年龄的条目,但这并非通用解决方案;许多分析需要更巧妙的方法来处理缺失值。)因此,我们不使用像 0 这样的普通值,而是定义一个特殊的“缺失”值,写为“NA”,表示“不可用”(“not available”)。
因此,缺失值具有以下特性:像任何其他值一样,它们必须由数组的 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.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.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)
UFuncs#
默认情况下,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)
版权#
本文档已进入公共领域。