NEP 26 — 缺失数据 NEP 摘要和讨论#

作者:

Mark Wiebe <mwwiebe@gmail.com>, Nathaniel J. Smith <njs@pobox.com>

状态:

延迟

类型:

标准跟踪

创建:

2012-04-22

上下文:此 NEP 作为大量讨论和提案的摘要而编写(NEP 12 — NumPy 中的缺失数据功能NEP 24 — 缺失数据功能 - NEP 12 的替代方案 1NEP 25 — 通过特殊 dtype 支持 NA),关于缺失数据功能。

关于 NumPy 如何处理缺失数据的争论,这是一个具有许多现有方法、要求和约定的主题,一直是漫长而有争议的。关于如何在 NumPy 中实现支持,已经提出了不止一个提案,并且有一个可测试的实现已被合并到 NumPy 的当前主分支中。大量电子邮件和不同的观点使得利益相关者难以理解问题并对 NumPy 的方向感到满意。

这是我们(Mark 和 Nathaniel)尝试在一个地方总结问题、提案以及一致点/分歧点,以帮助社区朝着共识迈进。

NumPy 开发人员的问题#

对于此讨论,“缺失数据”是指可以索引的数组元素(例如,形状为 (5,) 的数组 A 中的 A[3]),但在某种意义上没有值。

它不指压缩或稀疏存储技术,在这种技术中,A[3] 的值实际上并未存储在内存中,但仍具有明确定义的值,例如 0。

这仍然很模糊,要创建实际的实现,有必要回答以下问题

  • 在执行元素级 ufunc 时计算什么值。

  • 在执行约简时计算什么值。

  • 当标记值为缺失时,元素的存储是否会被覆盖。

  • 导致 NaN 的计算是否会自动以与缺失值相同的方式处理。

  • 是否使用占位符对象(例如,称为“NA”或“masked”)或通过单独的布尔数组来与缺失值交互。

  • 是否存在无法保存缺失数组元素的数组对象。

  • 如何用 dtype、掩码和其他结构来表达(C 和 Python)API。

  • 如果我们决定以多种方式回答其中一些问题,那么就会产生一个问题,即这是否需要多个系统,如果是,那么它们应该如何交互。

显然,有非常多的缺失数据 API 可以实现。可能至少有一个用户会在某个地方发现任何可能的实现都是他们解决某些问题的完美工具。另一方面,NumPy 的大部分功能和清晰度来自拥有少量正交的概念,例如跨步数组、灵活索引、广播和 ufunc,我们希望保持这种简单性。

NumPy 用户的几个主要群体对现有缺失数据支持的现状表示不满。特别是,numpy.ma 组件或使用浮点 NaN 作为缺失数据信号都不能完全满足这些用户的性能要求和易用性。R 的示例(其中缺失数据通过 NA 占位符处理并深度集成到所有计算中)是这些用户指出他们想要的功能的地方。像 R 中一样深度集成缺失数据必须谨慎考虑,必须明确的是,这不是以牺牲现有性能或功能的方式进行的。

我们的问题是,我们如何选择对 NumPy 的一些增量添加,这些添加将使一大类用户满意,并且合理优雅,补充现有设计,以及我们确信不会在长期内后悔的添加。

现有技术#

因此,一个主要(也许是主要的)问题是弄清楚向 NumPy 添加缺失数据支持的项目应该有多雄心勃勃,以及哪些类型的问题在范围内。让我们从“缺失数据”发挥作用的最容易理解的情况开始

“统计缺失数据”#

在统计学、社会科学等领域,“缺失数据”是一个专业术语,指的是一种特定(但极其常见且重要)的情况:我们尝试根据某种方案收集一些测量值,但其中一些测量值缺失。例如,如果我们有一个表格列出了许多人的身高、年龄和收入,但一个人没有提供他们的收入,那么我们需要某种方法来表示这一点

Person | Height | Age | Income
------------------------------
   1   |   63   | 25  | 15000
   2   |   58   | 32  | <missing>
   3   |   71   | 45  | 30000

传统的方法是将该收入记录为“–99”,并在 README 中与数据集一起记录此信息。然后,您必须记住专门检查和处理此类收入;如果您忘记,您将得到表面上合理但完全不正确的结果,例如将此数据集的平均收入计算为 14967。如果您身处这些领域之一,那么这种缺失是常规且不可避免的,如果您使用“–99”方法,那么它是一个陷阱,您必须在您执行的每个计算中明确检查它。这显然是一种令人不快的生存方式。

让我们将这种情况称为“统计缺失数据”情况,只是为了有一个方便的代号。(如前所述,从业人员只称其为“缺失数据”,以及如何处理它实际上是统计学的整个子领域;如果您搜索“缺失数据”,那么每个参考资料都是关于如何处理它。)NumPy 不会进行自动插补或类似操作,但它可以通过提供某种标准方法来至少表示以这种意义上缺失的数据,从而提供很大的帮助。

这种方法的主要现有技术来自 S/S+/R 系列语言。他们的策略是,对于他们支持的每种类型,定义一个特殊的值,称为“NA”。(对于 int,这是 INT_MAX,对于 float,它是一个特殊的 NaN 值,可以与其他 NaN 区分开来,……)然后,他们安排在计算中,该值具有我们称之为“NA 语义”的特殊语义。

NA 语义#

NA 语义的概念是,任何涉及 NA 值的计算都应该与我们知道正确值时会发生的情况一致。

例如,假设我们要计算平均收入,我们该怎么做?一种方法是直接忽略缺失的条目,并计算剩余条目的平均值。这使我们得到 (15000 + 30000)/2,即 22500。

此结果是否与发现第 2 人的收入一致?假设我们发现第 2 人的收入为 50000。这意味着正确答案是 (15000 + 50000 + 30000)/3,即 31666.67,清楚地表明它不一致。因此,平均收入为 NA,即我们无法计算其值的特定数字。

这促使了以下规则,即 R 如何实现 NA

赋值

NA 值被理解为表示特定的未知值,因此在赋值和其他基本数据操作方面应该具有值语义。不实际查看相关值的代码应该无论其中一些值是否缺失都以相同的方式工作。例如,人们可能会编写

income[:] = income[np.argsort(height)]

income 数组执行就地排序,并知道最矮的人的收入最终会排在第一位。事实证明,最矮的人的收入未知,因此数组最终会变成 [NA, 15000, 30000],但这与 NA 本身无关。

传播

在上面的示例中,我们得出结论,当其数据值之一为 NA 时,mean 之类的操作应该生成 NA。如果您问我,“3 加 x 等于多少?”,那么我唯一可能的答案是“我不知道 x 是多少,所以也不知道 3 + x 等于多少”。NA 表示“我不知道”,所以 3 + NA 为 NA。

这对于分析数据时的安全性很重要:缺失数据通常需要特殊处理才能保证正确性 - 您缺失信息的事实可能意味着您想要计算的东西实际上无法计算,并且有整本书籍专门讨论如何在各种情况下进行补偿。此外,您可能没有意识到自己有缺失数据,并编写代码假设您拥有所有数据。此类代码不应静默地生成错误答案。

在布尔值的情况下,有一个重要的例外来描述这种情况为传播。考虑以下计算

v = np.any([False, False, NA, True])

如果我们严格传播,v 将变为 NA。但是,无论我们在第三个数组位置放置 True 还是 False,v 都会获得值 True。问题“结果 True 是否与随后发现缺失的值一致?”的答案是肯定的,因此可以合理地不在这里进行传播,而是返回 True 值。这是 R 的做法

> any(c(F, F, NA, T))
[1] TRUE
> any(c(F, F, NA, F))
[1] NA
其他

NaN 和 NA 在概念上是不同的。0.0/0.0 不是一个神秘的未知值 - 它根据 IEEE 浮点数定义为 NaN,即非数字。NA 是数字(或字符串,或任何其他东西),只是未知的数字。另一个小的但重要的区别是,在 Python 中,if NaN: ... 将 NaN 视为 True(NaN 是“真值”);但 if NA: ... 将是一个错误。

在 R 中,所有约简操作都实现了一种替代语义,通过传递一个特殊参数 (na.rm=TRUE 在 R 中) 来激活它。sum(a) 表示“给我所有值的总和”(如果某些值是 NA,则为 NA);sum(a, na.rm=True) 表示“给我所有非 NA 值的总和”。

其他现有技术#

一旦我们超越“统计缺失数据”案例,缺失数据的正确行为就变得不那么明确了。在许多情况下,特定的元素会被单独挑选出来进行特殊处理或从计算中排除,这些情况通常可以被概念化为在某种意义上涉及“缺失数据”。

在图像处理中,通常使用单个图像以及一个或多个布尔掩码来例如合成图像的子集。正如 Joe Harrington 在邮件列表中指出的那样,在处理天文图像的背景下,也很常见将浮点值掩码或 alpha 通道泛化,以指示“缺失”的程度。我们认为这超出了当前设计的范围,但它是一个重要的用例,理想情况下,NumPy 应该支持操作此类数据的自然方法。

在 R 之后,numpy.ma 可能是关于缺失数据相关 API 的最成熟的经验来源。它的设计与 R 相当不同;它使用不同的语义——默认情况下,约简会跳过掩码值,而 NaN 会转换为掩码值——并且它使用不同的存储策略,通过单独的掩码。虽然它似乎通常被认为不适合一般用途,但很难确定这是因为 API 不成熟但基本上很好,还是 API 从根本上存在问题,或者 API 很好,但代码应该更快,或者是什么。我们查看了一些这些用户,以尝试更好地了解情况。

Matplotlib 可能是最依赖 numpy.ma 的知名软件包。它似乎以两种方式使用它。一种是作为一种方式,让用户在将数据传递给图表时指示哪些数据缺失。(也支持其他方式,例如,传递 NaN 值会产生相同的结果。)在这方面,matplotlib 对 np.ma.masked 和 NaN 值的处理方式与 R 的绘图例程处理 NA 和 NaN 值的方式相同。

在内部,matplotlib 使用 numpy.ma 数组以廉价且非破坏性的方式存储和传递包含每个输入数组的“有效性”信息的单独计算的布尔掩码。马克从一些浅层的代码审查中得到的印象是,它主要直接使用掩码数组的数据和掩码属性,而不是广泛使用 numpy.ma 的特定计算语义。因此,对于这种用法,它们确实依赖于非破坏性的基于掩码的存储,但这并不能说明需要什么语义。

Paul Hobson 在列表中发布了一些代码,这些代码使用 numpy.ma 存储污染物浓度测量的数组。在这里,掩码指示相应的数字是否代表实际测量值,或者仅仅代表无法检测到的浓度的估计检测限。纳撒尼尔从阅读这段代码中得到的印象是,它也主要使用 .data 和 .mask 属性,而不是直接对 MaskedArray 进行操作。

因此,这些例子清楚地表明,人们需要一种方便的方法来将数据数组和掩码数组(甚至浮点数组)捆绑在一起并“对齐”。但它们并没有告诉我们,关于 ufunc 和朋友,结果对象应该具有什么语义。

语义、存储、API,哦,我的天!#

我们认为,在用例、语义和存储之间划清界线是有用的。用例是用户遇到的情况,无论 NumPy 做什么;它们是上一节的重点。当我们说语义时,我们的意思是不同操作的结果,从 Python 层面来看,而不考虑底层实现。

NA 语义是上面描述的语义,并由 R 使用

1 + NA = NA
sum([1, 2, NA]) = NA
NA | False = NA
NA | True = True

使用 na.rm=TRUEskipNA=True,这将切换到

1 + NA = illegal # in R, only reductions take na.rm argument
sum([1, 2, NA], skipNA=True) = 3

也有人讨论过我们将称之为忽略语义的内容。这些语义在一定程度上是未定义的

sum([1, 2, IGNORED]) = 3
# Several options here:
1 + IGNORED = 1
#  or
1 + IGNORED = <leaves output array untouched>
#  or
1 + IGNORED = IGNORED

numpy.ma 语义是

sum([1, 2, masked]) = 3
1 + masked = masked

如果 NA 或忽略语义是用掩码实现的,那么对于被分配缺失值的数组元素,需要对存储中的值进行什么操作,这是一个选择。三种可能性是

  • 保持该内存不变(NEP 中做出的选择)。

  • 独立于掩码对值进行计算(对于上面 Paul Hobson 的用例来说,这可能是最有用的选项)。

  • 将存储在输入缺失值后面的任何值复制到输出中(这是 numpy.ma 所做的。即使在 masked + masked 的情况下,这也模棱两可——在这种情况下,numpy.ma 会复制存储在最左侧掩码值后面的值)。

当我们谈论存储时,我们的意思是关于缺失值是否应该通过指定底层数据类型的特定值(位模式数据类型选项,如 R 中所用)来表示,还是通过使用与数据本身一起存储的单独掩码来表示。

对于基于掩码的存储,还有一个重要的问题,即访问掩码、修改掩码和“窥视掩码”的 API 应该是什么样子。

已经提出的设计#

一种选择是直接复制 R,通过实现一种机制,让数据类型可以安排将某些位模式赋予 NA 语义。

一种选择是紧密地复制 numpy.ma,但使用更优化的实现。(或者,简单地优化现有的实现。)

一种选择是在 NEP12 中描述的选项,该选项存在基于掩码的缺失数据的实现。这个系统大致是

  • 既有位模式,也有基于掩码的缺失数据,两者都具有相同的可互操作的 NA 语义。

  • 通过将 np.NA 或值分配给数组元素来修改掩码。窥视掩码或取消掩码值的方法是保留数组的视图,该视图共享数据指针,但不共享掩码指针。

  • 马克想添加一种方法,可以更直接地访问和操作掩码,以便与这种基于视图的 API 结合使用。

  • 如果一个数组既有位模式数据类型,又有掩码,那么分配 np.NA 会写入掩码,而不是写入数组本身。将位模式 NA 写入同时支持二者的数组需要通过“窥视掩码”来访问数据。

另一种选择是在 NEP24 中描述的选项,即实现具有 NA 语义的位模式数据类型,用于“统计缺失数据”用例,以及实现一个完全独立的 API,用于具有忽略语义的掩码数组,所有掩码操作都通过 .mask 属性显式完成。

另一种选择是定义一个极简的对齐数组容器,该容器包含多个数组,可用于将它们一起传递。它将支持索引(有助于解决希望同时对多个数组进行子集而不会使其不对齐的常见问题),但所有算术等操作都将通过属性直接访问底层数组来完成。上面的“现有技术”讨论表明,类似于这种包含 .data 和 .mask 数组的东西实际上可以解决许多人的问题,而无需对 NumPy 进行任何重大架构更改。这类似于结构化数组,但每个字段都在单独存储的数组中,而不是打包在一起。

有几个人建议应该有一个系统,该系统具有多个缺失值,每个缺失值具有不同的语义,例如,具有 NA 语义的 MISSING 值,以及具有忽略语义的单独的 IGNORED 值。

这些选项并不一定相互排斥。

争论#

我们都对使用忽略语义作为缺失数据的默认行为持怀疑态度。**纳撒尼尔**喜欢 NA 语义,因为他最感兴趣的是“统计缺失数据”用例,而 NA 语义正好适合这种情况。**马克**对该特定用例并不感兴趣,但他喜欢 NA 计算抽象,因为它在所有情况下都是明确且定义良好的,并且具有大量现有的经验可以借鉴。

**纳撒尼尔**总体上的想法

  • “统计缺失数据”用例清晰且引人注目;其他用例当然值得我们关注,但很难说它们到底是什么,或者说,支持它们的最佳方式是否通过扩展 ndarray 对象。

  • “统计缺失数据”用例最好通过使用位模式存储来实现 NA 语义的 R 风格系统来满足。对于这种用例,位模式存储的主要优势是它避免了存储和检查掩码的额外内存和速度开销(尤其是在浮点数据的情况下,其中一些使用 NaN 的技巧使我们能够有效地硬件加速大多数 NA 操作)。这些顾虑本身似乎让许多 NA 用户无法接受基于掩码的实现,尤其是在神经科学(内存紧张)或金融建模(毫秒至关重要)等领域。此外,位模式方法在概念上不太令人困惑(例如,赋值实际上只是赋值,没有幕后发生的魔法),并且可以通过 rpy2 对跨语言调用实现与 R 的内存兼容性。位模式方法的主要缺点是需要放弃一个值来表示 NA,但这对于最重要的数据类型(浮点、布尔、字符串、枚举、对象)来说不是问题;实际上,只有整数受到影响。即使对于整数,放弃一个值对于统计问题来说也不重要。(尽管占领华尔街,但没有人收入为 2**63 - 1。如果真是这样,我们也会切换到浮点数以避免溢出。)

  • 添加新的数据类型需要与 ufunc 和强制转换机制进行一些合作,但不需要任何架构更改或违反 NumPy 的当前正交性。

  • 他从邮件列表讨论中得到的印象,尤其是从 “我们能达成什么共识?”主题 中得到的印象是,许多 numpy.ma 用户特别喜欢基于掩码的存储、通过 API 轻松访问掩码以及忽略语义的组合。他当然可能是错的。但他不记得除了马克之外还有人主张将基于掩码的存储和 NA 语义组合在一起,这让他很紧张。

  • 此外,他个人对在 Python 层面上几乎相同但并不完全相同的两种存储实现的想法并不感到满意。虽然可能有些人想要暂时假装某些数据是“统计缺失数据”,而无需复制他们的数组,但并不清楚他们是否比那些想要同时使用位模式和掩码来实现不同目的的人多。坦白地说,他希望能够根据自己的意愿忽略掩码,并坚持使用位模式,如果它们在 API 中紧密地结合在一起,这是不可能的。所以他会说,关于 NEP 设计的这一方面是优势还是劣势,目前还没有定论。(当然,他从未听说过任何 R 用户抱怨说他们真的希望在这里有一个做出不同权衡的选项。)

  • R 的 NA 支持是一个 主要功能,其目标受众认为它与 Matlab 或 Python 等其他平台相比具有显著优势。如果没有平台支持,处理统计缺失数据将非常痛苦。

  • 相比之下,我们对需要基于掩码实现的用例的确定性要低得多,而且如果人们被迫暂时使用 NumPy 优秀的基于掩码的索引、新的 where= 支持,甚至 numpy.ma,他们似乎也不会遭受太大的损失。

  • 因此,具有 NA 语义的位模式似乎满足了以下标准:以优雅的方式让一大类用户满意,符合原始设计,并且我们可以合理确定,我们对问题和用例的理解足够好,以至于我们对它们在长期的满意度有把握。但到目前为止,还没有任何基于掩码的存储提案做到这一点。

**马克**总体上的想法

  • 以缺失数据的默认行为来使用 NA 语义的想法,其灵感来自“统计缺失数据”问题,比之前考虑的所有其他默认行为都要好。这同样适用于位模式和基于掩码的方法。

  • 为了让 NA 风格的功能得到所有 NumPy 功能,最终得到所有第三方库的正确支持,它需要位于核心位置。如何正确有效地处理缺失数据因算法而异,如果需要考虑它才能完全支持 NumPy,那么 NA 支持将更广泛,质量更高。

  • 同时,提供两种不同的缺失数据接口,一种用于掩码,另一种用于位模式,这要求 NumPy 开发人员和第三方 NumPy 插件开发人员分别考虑在每种情况下该怎么做,以及分别执行他们代码的两个额外实现。这会使他们的工作复杂化,并可能导致对缺失数据的支持不一致。

  • 通过相同的 C 和 Python 编程接口提供使用掩码和位模式的能力,使缺失数据支持与所有其他 NumPy 功能完全正交。

  • 掩码和位模式之间在内存使用、性能、正确性和灵活性方面存在许多权衡。对这两种方法的支持使 NumPy 用户能够选择最符合他们思维方式的方法,或者选择最符合他们用例的特征。通过相同的接口提供它们,进一步使他们能够以最小的努力尝试这两种方法,并选择最有效或最节省他们程序内存的方法。

  • 内存使用

    • 对于位模式,使用更少的内存来存储包含一些 NA 的单个数组。

    • 对于掩码,使用更少的内存来存储多个数组,这些数组除了 NA 的位置之外都相同。(在这种情况下,可以使用单个数据数组与多个掩码数组一起使用;位模式 NA 需要复制整个数据数组。)

  • 性能

    • 对于位模式,浮点类型可以使用本机硬件操作,行为几乎是正确的。对于完全正确的浮点行为以及其他类型,必须编写专门测试与缺失数据位模式相等的代码。

    • 对于掩码,始终存在访问掩码内存并测试其真值的开销。当前存在的实现没有进行性能调整,因此它只能用于判断最低性能水平。最佳的基于掩码的代码通常比最佳的基于位模式的代码慢。

  • 正确性

    • 位模式整数类型必须牺牲一个有效的值来表示 NA。对于更大的整数类型,有些人认为这是可以的,但对于 8 位类型,没有合理的选择。在浮点情况下,如果选择了本机浮点操作的性能,则存在一个小的不一致之处,即 NaN+NA 和 NA+NaN 不同。

    • 对于掩码,它在所有情况下都能正常工作。

  • 通用性

    • 位模式方法只能在数据类型可以放弃特定值时以完全通用的方式工作。对于 IEEE 浮点数,NaN 是一个明显的选择,对于以字节表示的布尔值,有很多选择。对于整数,必须牺牲一个有效的值才能使用这种方法。插入 NumPy 的第三方数据类型也必须做出位模式选择来支持此系统,这可能并不总是可能的。

    • 掩码方法适用于所有数据类型。

前进的建议#

**纳撒尼尔**认为我们应该

  • 继续实现位模式 NA。

  • 不要在核心代码中实现掩码数组 - 或者至少,现在还不要。相反,我们应该专注于找出如何在核心代码之外实现它们,这样人们就可以尝试不同的方法,而我们不会承诺使用任何一种方法。这样新的原型就可以比 NumPy 的发布周期更快地发布。无论如何,如果 NumPy 要继续发展而不会分叉,我们必须找出如何在核心代码之外进行这种更改的实验 - 最好现在就开始做。现有的代码可以保留在主分支中,被禁用,或存在于它自己的分支中 - 一旦我们知道自己在做什么,它仍然在那里。

Mark 认为我们应该

  • 现有的代码应该保持原样,添加一个全局运行时实验标志,默认情况下禁用 NA 支持。

对此建议的更详细的理由是

  • NumPy 主分支中目前有一个可靠的初步 NA-mask 实现。此实现已针对 scipy 和其他第三方软件包进行了广泛测试,并且在主分支中已经处于稳定状态很长时间了。

  • 此实现与核心代码深度集成,提供了一个接口,该接口的使用方式与 R 的 NA 支持相同。它为 R 的 NA 支持提供了一个引人入胜且用户友好的答案。

  • 缺失数据 NEP 提供了一个计划,用于添加基于位模式的 NA 的 dtype 支持,它将通过相同的接口操作,但允许实现与 R 相同的性能/正确性权衡。

  • 让用户很容易尝试这个实现,它具有合理的特性覆盖范围和性能特征,是获得更多关于 NumPy 缺失数据支持外观的具体反馈的最佳方式。

由于处于初步阶段,现有的实现被标记为 NumPy 文档中的实验性。最好将其标记为实验性,直到它得到更多完善,例如支持结构和数组 dtype,以及更完整的 NumPy 操作集。

我认为代码应该保持原样,除了添加一个运行时全局 NumPy 标志,例如 numpy.experimental.maskna,它默认值为 False,可以切换到 True。在其默认状态下,任何 NA 特性使用都会引发“ExperimentalError”异常,这种措施可以防止意外使用,并非常清楚地传达其实验状态。

在 1.x 系列版本中,ABI 问题 似乎非常难以有效地处理,但我相信,通过在 2.0 版本中正确隐藏实现,将软件演变为支持已讨论的各种其他 ABI 想法是可行的。这是我最喜欢的方案。

Nathaniel 在回复中指出,他实际上并不反对在主 numpy 发行版中发布实验性 API,如果我们小心确保它们不会以让我们陷入困境的方式“泄露”。原则上,某种“这违反了您的保修”全局标志可以成为一种方法。(事实上,对于他喜欢的添加最小挂钩以使我们能够更容易地构建原型的更改类型,这可能也是一个有用的策略 - 我们可能有一些“快速原型设计专用”挂钩,这些挂钩让原型破解能够比我们原本准备支持的更深入地访问 NumPy 的内部。)

但是,他希望指出两点。首先,似乎我们仍然需要回答关于 NEP 设计的基本问题,例如掩码是否应该具有 NA 语义或忽略语义,并且已经计划重大更改 NEP 掩码的暴露和访问方式。所以他不确定通过要求对 NEP 代码的当前状态进行反馈,我们将学到什么。

其次,考虑到人们对它们会导致(轻微的)ABI 问题而感到担忧,我们并不清楚我们是否真的能够阻止它们泄露。(他也很期待 2.0,但我们还没有到那里。)所以,如果它们根本不存在于 C API 中,而测试人员所需的步骤是,'我们已经包含了一个笨拙的纯 Python 原型,可以通过输入“import numpy.experimental.donttrythisathome.NEP”来访问,并且欢迎反馈',那么也许会更好?

如果是这样,那么他应该提到他确实实现了一个非常笨拙的,纯 Python 的 NEP API 实现,它适用于 NumPy 1.6.1。这主要是作为一项实验,以了解这种原型设计是否可行,并测试可能的 ufunc 覆盖机制,但如果有兴趣,该模块可以在这里获得:njsmith/numpyNEP

它通过了 maskna 测试套件,但顶部有一个大注释描述了一些小问题。

Mark 回复说

我同意在 NumPy 中添加新功能时,谨慎行事非常重要,但我同时也相信,该项目必须具有向前发展的动力。像 NumPy 这样的项目需要开发人员编写代码才能实现进步,而阻碍代码编写的障碍会让现有的开发人员不愿做出更多贡献,并可能吓跑那些正在考虑加入的开发人员。

所有软件项目,无论是开源还是闭源,都必须在短期实用性和长期规划之间取得平衡。在缺失数据开发方面,有一个短期的资源投入来解决这个问题,这个问题的范围相当大。如果不可能将一个明确地朝着解决方案前进的贡献纳入 NumPy,我预计那些有兴趣进行此类工作的个人和公司在证明其资源投入的合理性方面会更加困难。对于一个对许多其他库都至关重要的项目,仅仅依靠无私志愿者的良好意愿,意味着 NumPy 可能会更容易被另一个项目超越。

对于正在讨论的现有 NA 贡献,我们如何解决这场分歧,这代表着一个关于 NumPy 的开发人员、贡献者和用户应该如何互动的决定。如果我们创建一个文档来描述争议解决流程,我们应该如何设计它,才能避免给开发人员带来沉重的负担和过度的确定性,从而防止他们有效地贡献代码?

如果我们走这条路,写一个包含这种争议解决机制的决策流程,我认为它的核心应该是一份路线图,潜在的贡献者和开发人员可以遵循它来获得对 NumPy 的影响力。NumPy 的开发需要超出代码贡献的广泛支持,将项目中的影响力与贡献联系起来,在我看来,这将是一个鼓励人们承担诸如错误分类/管理、持续集成/构建服务器管理等任务的好方法,以及满足项目需求所需的无数其他任务。没有一个具体的功利主义、民主主义、追求共识的制度能够满足所有人的要求,但围绕治理和流程的讨论的热烈程度表明,至少比现状稍微正式一些的东西是必要的。

总之,我希望 NumPy 项目优先考虑向更灵活和模块化的 ABI/API 转变,同时保持强大的向后兼容性约束和个人、大学和公司希望贡献的功能添加。我不认为在 1.7 中保留 NA 代码,并通过要求启用实验标志来进行少量额外措施,会构成长期 ABI 问题的风险。我看到更大的风险是开发人员持续缺乏对项目的贡献,而且我认为由于这些担忧而撤回此代码将导致开发人员贡献减少的风险。

参考文献和脚注#

NEP 12 — NumPy 中的缺失数据功能 描述了 Mark 的 NA 语义/掩码实现/基于视图的掩码处理 API。

NEP 24 — 缺失数据功能 - NEP 12 的替代方案 1 (“alterNEP”)是 Nathaniel 初步尝试将 MISSING 和 IGNORED 处理分离到位模式与掩码中,虽然现在他会在提案中进行一些更改。

NEP 25 — 通过特殊 dtype 实现 NA 支持 (“miniNEP 2”)是 Nathaniel 后来尝试概述 NA dtype 的实现策略。

可以在以下位置找到进一步的讨论概述页面:njsmith/numpy