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 的替代方案 1,NEP 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 空间,这些 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_MAX,对于浮点数,这是一个与其他 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时,像
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 中,所有约简操作都实现了另一种语义,通过传递一个特殊参数(在 R 中为
na.rm=TRUE
)来激活。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 以与 R 的绘图例程处理 NA 和 NaN 值相同的方式处理 np.ma.masked 和 NaN 值。出于这些目的,matplotlib 并不真正关心用于缺失数据的语义或存储策略。
在内部,matplotlib 使用 numpy.ma 数组以廉价且非破坏性的方式存储和传递周围单独计算的布尔蒙版,其中包含每个输入数组的“有效性”信息。Mark 从一些浅层代码审查中得到的印象是,它主要直接使用掩码数组的数据和蒙版属性,而不是广泛使用 numpy.ma 的特定计算语义。因此,对于此用法,它们确实依赖于基于非破坏性蒙版的存储,但这并没有说明需要什么语义。
Paul Hobson 在列表上发布了一些代码,这些代码使用 numpy.ma 来存储污染物浓度测量的数组。此处,蒙版指示相应数字是否代表实际测量值,或者只是对太小而无法检测到的浓度的估计检测限。Nathaniel 从阅读这段代码中得到的印象是,它也主要使用 .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=TRUE
或skipNA=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 中使用的位模式 dtype 选项)来表示缺失值,或者通过使用与数据本身一起存储的单独蒙版来表示缺失值的争论。
对于基于蒙版的存储,还有一个关于访问蒙版、修改蒙版和“窥视蒙版后面”的 API 看起来像什么的重要问题。
已提出的设计#
一种选择是简单地复制 R,通过实现一种机制,使 dtype 可以安排将某些位模式赋予 NA 语义。
一种选择是密切复制 numpy.ma,但使用更优化的实现。(或者只是优化现有实现。)
一种选择是在NEP12中描述的选择,为此存在基于蒙版的缺失数据的实现。该系统大致如下:
既有位模式和基于蒙版的缺失数据,两者都具有相同的可互操作 NA 语义。
通过将 np.NA 或值赋值给数组元素来修改蒙版。窥视蒙版后面或取消蒙版值的方法是保留数组的视图,该视图共享数据指针但不共享蒙版指针。
Mark 想要添加一种更直接地访问和操作蒙版的方法,以补充这种基于视图的 API。
如果数组同时具有位模式 dtype 和蒙版,则赋值 np.NA 将写入蒙版,而不是写入数组本身。将位模式 NA 写入同时支持两者的数组需要通过“窥视蒙版下方”来访问数据。
另一个选项是在NEP24中描述的选项,即为“统计缺失数据”用例实现具有 NA 语义的位模式 dtype,并为具有忽略语义的掩码数组实现完全独立的 API,所有蒙版操作都通过 .mask 属性显式完成。
另一种选择是定义一个极简的对齐数组容器,该容器保存多个数组,并可用于将它们一起传递。它将支持索引(有助于解决想要一起子集多个数组而不使其不对齐的常见问题),但所有算术运算等都将通过直接访问底层数组(通过属性)来完成。“现有技术”讨论表明,持有 .data 和 .mask 数组的类似内容实际上可能会解决许多人的问题,而无需对 NumPy 进行任何重大的架构更改。这类似于结构化数组,但每个字段都在单独存储的数组中,而不是打包在一起。
一些人建议应该有一个具有多个缺失值的单个系统,每个缺失值都具有不同的语义,例如,具有 NA 语义的 MISSING 值和具有忽略语义的单独 IGNORED 值。
这些选项并非相互排斥。
辩论#
我们俩都对使用忽略语义作为缺失数据默认行为持怀疑态度。Nathaniel 喜欢 NA 语义,因为他最感兴趣的是“统计缺失数据”的用例,而 NA 语义恰好适用于此。Mark 对这个具体的用例不太感兴趣,但他喜欢 NA 计算抽象,因为它在所有情况下都是明确且定义良好的,并且有很多现有的经验可以借鉴。
Nathaniel 的总体看法
“统计缺失数据”的用例清晰且令人信服;其他用例当然值得我们关注,但目前很难确切地说出它们是什么,甚至最佳支持方式是否应该通过扩展 ndarray 对象。
“统计缺失数据”的用例最好由一个使用位模式存储来实现 NA 语义的 R 风格系统来服务。对于此用例,位模式存储的主要优势在于它避免了存储和检查掩码的额外内存和速度开销(特别是对于常见的浮点数据情况,其中一些 NaN 的技巧允许我们有效地通过硬件加速大多数 NA 操作)。仅这些问题似乎就使得基于掩码的实现对许多 NA 用户来说是不可接受的,尤其是在神经科学(内存紧张)或金融建模(毫秒至关重要)等领域。此外,位模式方法在概念上不太令人困惑(例如,赋值实际上就是赋值,幕后没有魔法),并且可以通过 rpy2 与 R 具有内存兼容性,以便进行跨语言调用。位模式方法的主要缺点是需要放弃一个值来表示 NA,但这对于最重要的数据类型(浮点型、布尔型、字符串、枚举、对象)来说不是问题;实际上,只有整数受到影响。即使对于整数,放弃一个值对于统计问题来说也不是什么大问题。(尽管占领华尔街的人除外,没有人的收入是 2**63 - 1。如果真是这样,我们会切换到浮点数以避免溢出。)
添加新的 dtype 需要与 ufunc 和转换机制进行一些合作,但不需要任何架构更改或违反 NumPy 当前的正交性。
他从邮件列表讨论中得到的印象,特别是“我们能达成共识吗?”主题,是许多 numpy.ma 用户特别喜欢掩码存储、掩码可通过 API 轻松访问以及忽略语义的组合。当然,他可能错了。但他记不起除了 Mark 之外还有谁提倡掩码存储和 NA 语义的特定组合,这让他感到不安。
此外,他个人不太喜欢在 Python 层面上几乎相同但又不完全相同的想法。虽然可能有些人希望暂时假装某些数据是“统计缺失数据”而无需复制其数组,但他们是否超过了希望同时出于不同目的使用位模式和掩码的人数,这一点根本不清楚。老实说,如果他想忽略掩码并坚持使用位模式,他希望能够做到这一点,如果它们在 API 中紧密耦合在一起,这是不可能的。所以他会说,这方面 NEP 设计是优势还是劣势,目前还很难判断。(当然,他从未听说过任何 R 用户抱怨他们真的希望在这里做出不同的权衡。)
R 的 NA 支持是一个主要功能,其目标受众认为它比 Matlab 或 Python 等其他平台具有显著优势。在没有平台支持的情况下,处理统计缺失数据非常痛苦。
相比之下,我们对需要基于掩码的实现的用例显然存在更多的不确定性,如果人们现在被迫使用 NumPy 出色的基于掩码的索引、新的 where= 支持,甚至 numpy.ma,他们似乎也不会遭受太大的损失。
因此,具有 NA 语义的位模式似乎符合让一大类用户以优雅的方式感到满意、符合原始设计以及我们可以合理确定我们足够了解问题和用例,从而在长期内对它们感到满意的标准。但是,目前还没有基于掩码的存储方案能够做到这一点。
Mark 的总体看法
与所有其他考虑过的默认行为相比,使用 NA 语义作为缺失数据的默认行为(受“统计缺失数据”问题的启发)更好。这同样适用于位模式和掩码方法。
为了让所有 NumPy 功能以及最终所有第三方库都能正确有效地处理 NA 风格的功能,它需要在核心部分。如何正确有效地处理缺失数据因算法而异,如果需要考虑这一点才能完全支持 NumPy,则 NA 支持将更广泛且质量更高。
同时,提供两种不同的缺失数据接口,一种用于掩码,一种用于位模式,这要求 NumPy 开发人员和第三方 NumPy 插件开发人员分别考虑在每种情况下该做什么,并执行两项额外的代码实现。这会使他们的工作复杂化,并可能导致对缺失数据支持不一致。
通过相同的 C 和 Python 编程接口提供使用掩码和位模式的能力,使缺失数据支持与所有其他 NumPy 功能干净地正交。
掩码和位模式之间存在许多内存使用、性能、正确性和灵活性的权衡。支持这两种方法允许 NumPy 用户选择最符合其思维方式的方法,或其特性最符合其用例的方法。通过相同的接口提供它们,还可以让他们轻松尝试两者,并选择在其程序中性能更好或内存使用更少的方法。
内存使用
使用位模式,存储包含一些 NA 的单个数组时,使用的内存更少。
使用掩码,存储多个除了 NA 位置不同的数组时,使用的内存更少。(在这种情况下,可以使用多个掩码数组重用单个数据数组;位模式 NA 需要复制整个数据数组。)
性能
使用位模式,浮点类型可以使用本地硬件操作,行为几乎正确。为了获得完全正确的浮点行为以及其他类型,必须编写专门测试与缺失数据位模式相等的代码。
使用掩码,始终存在访问掩码内存和测试其真值的开销。当前存在的实现没有性能调整,因此只能判断最低性能水平。通常,最佳的基于掩码的代码将比最佳的基于位模式的代码慢。
正确性
位模式整数类型必须牺牲一个有效值来表示 NA。对于较大的整数类型,有人认为这是可以的,但是对于 8 位类型,没有合理的选择。在浮点情况下,如果选择本地浮点运算的性能,则存在一个小小的不一致性,即 NaN+NA 和 NA+NaN 不同。
使用掩码,它在所有情况下都能正确工作。
通用性
只有当可以从数据类型中放弃特定值时,位模式方法才能以完全通用的方式工作。对于 IEEE 浮点型,NaN 是一个明显的选择,对于表示为字节的布尔值,有很多选择。对于整数,必须牺牲一个有效值才能使用这种方法。插入 NumPy 的第三方 dtype 也必须做出位模式选择才能支持此系统,这并非总是可能的。
掩码方法适用于所有数据类型。
前进的建议#
Nathaniel 认为我们应该
继续实现位模式 NA。
不要在核心代码中实现掩码数组——或者至少现在不要。相反,我们应该专注于弄清楚如何在核心代码之外实现它们,这样人们就可以尝试不同的方法,而我们无需承诺任何一种方法。这样,新的原型就可以比 NumPy 发布周期更快地发布。无论如何,如果 NumPy 要继续发展而不进行分支,我们将不得不弄清楚如何在核心代码之外试验此类更改——不妨现在就开始做。现有代码可以保留在主分支中、被禁用或保留在其自己的分支中——一旦我们知道自己在做什么,它仍然会存在。
Mark 认为我们应该
现有代码应保持不变,并添加一个全局运行时实验标志,该标志默认禁用 NA 支持。
此建议的更详细理由是
当前在 NumPy 主分支中有一个可靠的初步 NA 掩码实现。此实现已针对 scipy 和其他第三方软件包进行了广泛测试,并且已在主分支中处于稳定状态很长时间。
此实现与核心代码深度集成,提供了一个与 R 的 NA 支持相同的方式可用的接口。它为 R 的 NA 支持提供了一个令人信服且用户友好的答案。
缺失数据 NEP 提供了一个计划,用于添加基于位模式的 NA 的 dtype 支持,这将通过相同的接口运行,但允许进行与 R 所做的相同的性能/正确性权衡。
让用户非常容易尝试此实现(具有合理的特性覆盖率和性能特性)是获得关于 NumPy 缺失数据支持应如何设计的更具体反馈的最佳方法。
由于其初步状态,现有实现在 NumPy 文档中被标记为实验性。最好将其标记为实验性,直到它得到更充分的完善,例如支持 struct 和 array dtype 以及更全面的 NumPy 操作集。
我认为代码应该保持不变,只是添加一个运行时全局 NumPy 标志,例如 numpy.experimental.maskna,默认为 False,可以切换到 True。在其默认状态下,任何 NA 特性使用都会引发“ExperimentalError”异常,此措施将阻止其意外使用,并非常清楚地传达其实验状态。
在 1.x 系列版本中,ABI 问题 似乎非常难以有效处理,但我相信,通过在 2.0 版本中进行适当的实现隐藏,将软件发展到支持各种其他已讨论的 ABI 想法是可行的。这是我最喜欢的方法。
Nathaniel 回应道,如果我们小心谨慎,确保实验性 API 不会以一种让我们难以摆脱的方式“泄露”,他其实并不反对在主 NumPy 发行版中包含这些 API。原则上,某种“这违反了您的保修”的全局标志可以做到这一点。(事实上,这对于他所青睐的那些更改类型(添加最小的挂钩以便更容易构建原型)也可能是一种有用的策略——我们可以添加一些“仅限快速原型设计”的挂钩,让原型程序能够比我们原本准备支持的更深入地访问 NumPy 的内部结构。)
但是,他想指出两点。首先,我们似乎仍然有一些关于 NEP 设计的基本问题需要解答,例如掩码应该具有 NA 语义还是忽略语义,并且已经有计划要大幅更改 NEP 掩码的公开和访问方式。因此,他不确定通过请求对其当前状态下的 NEP 代码进行反馈,我们会学到什么。
其次,鉴于对其可能导致(轻微)ABI 问题的担忧,我们并不确定能否真正阻止它们泄露。(他也期待着 2.0 版本,但我们还没到那一步。)因此,也许最好根本不在 C API 中包含它们,而测试人员所需的操作类似于,“我们包含了一个简陋的纯 Python 原型,可以通过键入“import numpy.experimental.donttrythisathome.NEP”来访问,并且欢迎反馈”?
如果是这样,那么他应该提到他确实实现了一个非常笨拙的纯 Python 版本的 NEP API,该 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
版权#
本文件已进入公有领域。