为什么' groupby(x,np.isnan)'如果键是nan'则行为与' groupby(x)的行为不同

时间:2017-01-18 16:06:19

标签: python arrays list numpy nan

Since we're on the topic of peculiarities surrounding numpy's nan,我发现了一些我也不了解的东西。我发布这个问题主要是作为MSeifert的扩展,因为我们的观察结果似乎有一个共同的原因。

早些时候,I posted a solution涉及在包含itertools.groupby值的序列上使用nan

return max((sum(1 for _ in group) for key, group in groupby(sequence) if key is nan), default=0)

但是,我在上面链接的MSeifert问题上看到了this answer,这显示了我可能制定此算法的另一种方法:

return max((sum(1 for _ in group) for key, group in groupby(sequence, np.isnan)), default=0)

实验

我已经使用列表和numpy数组测试了这两种变体。代码和结果包含在下面:

from itertools import groupby

from numpy import nan
import numpy as np


def longest_nan_run(sequence):
    return max((sum(1 for _ in group) for key, group in groupby(sequence) if key is nan), default=0)


def longest_nan_run_2(sequence):
    return max((sum(1 for _ in group) for key, group in groupby(sequence, np.isnan)), default=0)


if __name__ == '__main__':
    nan_list = [nan, nan, nan, 0.16, 1, 0.16, 0.9999, 0.0001, 0.16, 0.101, nan, 0.16]
    nan_array = np.array(nan_list)

    print(longest_nan_run(nan_list))  # 3 - correct
    print(longest_nan_run_2(nan_list))  # 7 - incorrect
    print(longest_nan_run(nan_array))  # 0 - incorrect
    print(longest_nan_run_2(nan_array))  # 7 - incorrect

分析

  • 在所有四种组合中,只有使用原始功能对列表进行检查才能正常工作。
  • 修改后的功能(使用np.isnan)似乎对列表和数组都采用相同的方式
  • 检查数组时,原始功能似乎找不到任何nan值。

任何人都可以解释这些结果吗?同样,由于这个问题与MSeifert有关,因此对他的结果的解释也可能解释我的(或反之亦然)。

进一步调查

为了更好地了解发生了什么,我尝试打印出由groupby生成的群组:

def longest_nan_run(sequence):
    print(list(list(group) for key, group in groupby(sequence) if key is nan))
    return max((sum(1 for _ in group) for key, group in groupby(sequence) if key is nan), default=0)


def longest_nan_run_2(sequence):
    print(list(list(group) for _, group in groupby(sequence, np.isnan)))
    return max((sum(1 for _ in group) for key, group in groupby(sequence, np.isnan)), default=0)

一个基本的区别(回想起来有意义)是原始功能(使用if key is nan)将过滤掉除 nan之外的所有值,因此所有生成的组将仅包含nan值,如下所示:

[[nan, nan, nan], [nan]]

另一方面,已修改功能会将所有非nan值组合到各自的组中,如下所示:

[[nan, nan, nan], [0.16, 1.0, 0.16, 0.99990000000000001, 0.0001, 0.16, 0.10100000000000001], [nan], [0.16]]

这解释了为什么修改后的函数在两种情况下都返回7 - 它将值视为" nan"或者"不是nan"并返回最长的连续系列。

这也意味着我对groupby(sequence, keyfunc)如何运作的假设是错误的,并且修改后的函数不是原始函数的可行替代。

我仍然不确定在列表和数组上运行原始函数时结果的差异。

2 个答案:

答案 0 :(得分:3)

numpy数组中的项访问行为与列表中的不同:

nan_list[0] == nan_list[1]
# False
nan_list[0] is nan_list[1]
# True

nan_array[0] == nan_array[1]
# False
nan_array[0] is nan_array[1]
# False

x = np.array([1])
x[0] == x[0]
# True
x[0] is x[0]
# False

虽然列表包含对同一对象的引用,但是numpy数组'只包含'内存区域,并且每次访问元素时都会动态创建新的Python对象。 (谢谢user2357112,指出措词不准确。)

有道理,对吗?列表返回相同的对象,数组返回的不同对象 - 显然groupby内部使用is进行比较......但是等等,这并不容易!为什么groupby(np.array([1, 1, 1, 2, 3]))正常工作?

答案隐藏在itertools C source中,第90行显示函数PyObject_RichCompareBool用于比较两个键。

rcmp = PyObject_RichCompareBool(gbo->tgtkey, gbo->currkey, Py_EQ);

虽然这基本上等同于在Python中使用==,但文档还是注意到了一个专长:

  

注意如果o1和o2是同一个对象,则PyObject_RichCompareBool()将始终返回1的{​​{1}}和Py_EQ的{​​{1}} }。

这意味着实际进行了这种比较(等效代码):

0

因此,对于列表,我们有相同的Py_NE个对象,它们被标识为相等。相反,数组为我们提供了值为if o1 is o2: return True else: return o1 == o2 的不同对象,并与nan进行了比较 - 但nan始终评估为==

答案 1 :(得分:1)

好吧,我想我已经为自己画了一幅清晰的图片。

这里有两个因素:

  • 我自己对keyfunc论证对groupby所做的事情的误解。
  • 关于Python如何在数组和列表中表示nan值的(更有趣的)故事,最好在this answer中解释。

解释keyfunc因素

来自documentation on groupby

  

每次键功能的值发生变化时,它都会生成一个中断或新组

来自documentation on np.isnan

  

对于标量输入,如果输入为NaN,则结果为新的布尔值,其值为True;否则该值为False。

基于这两件事,我们推断当我们将keyfunc设置为np.isnan时,传递给groupyby的序列中的每个元素都将映射到TrueFalse,取决于它是否为nan。这意味着关键函数只会在nan元素和非nan元素之间的边界处发生变化,因此groupby只会将序列拆分为{{1}的连续块}和非nan元素。

相反,原始函数(使用nan)将使用标识函数作为groupby(sequence) if key is nan(其默认值)。这自然会导致keyfunc身份的细微差别,这将在下面解释(以及上面的链接答案),但重要的一点是,nan将过滤掉所有关键字非if key is nan 1}}元素。

nan身份

解释细微差别

正如我在上面链接的答案中更好地解释的那样,在Python的内置列表中出现的nan的所有实例似乎都是同一个实例。换句话说,列表中nan的所有出现都指向内存中的相同位置。与此相反,使用numpy数组时会动态生成nan元素,因此所有单独的对象都是如此。

使用以下代码演示:

nan

当我使用原始问题中定义的列表运行时,我获得此输出(突出显示相同的元素):

4436731128
[4436731128, 44436731128, 44436731128, 4436730432, 4435753536, 4436730432, 4436730192, 4436730048, 4436730432, 4436730552, 44436731128, 4436730432]

另一方面,数组元素似乎在内存中的处理方式非常不同:

4343850232
[4357386696, 4357386720, 4357386696, 4357386720, 4357386696, 4357386720, 4357386696, 4357386720, 4357386696,, 4357386720, 4357386696, 4357386720]

该函数似乎在内存中的两个不同位置之间交替以存储这些值。请注意,没有一个元素与过滤条件中使用的def longest_nan_run(sequence): print(id(nan)) print([id(x) for x in sequence]) return max((sum(1 for _ in group) for key, group in groupby(sequence) if key is nan), default=0) 相同。

案例研究

我们现在可以将我们收集到的所有这些信息应用于实验中使用的四个不同案例来解释我们的观察结果。

带列表的原始功能

在这种情况下,我们使用默认的nan函数作为identity,我们已经看到列表中每次出现keyfunc实际上都是相同的实例。过滤条件nan中使用的nan 与列表中的if key is nan元素相同,导致nan在适当的时候打破列表放置并仅保留包含groupby的组。这就是为什么这个变体有效,我们得到nan的正确结果。

带数组的原始功能

同样,我们使用默认的3函数作为identity,但这次所有keyfunc次出现 - 包括过滤条件中的出现 - 都指向不同的对象。这意味着所有组的条件过滤器nan将失败。由于我们无法找到空集合的最大值,因此我们将返回默认值if key is nan

包含列表和数组的修改函数

在这两种情况下,我们都使用0作为np.isnan。这将导致keyfunc将序列拆分为groupby和非nan元素的连续序列。

对于我们用于实验的列表/数组,nan元素的最长序列为nan,其中包含三个元素,非[nan, nan, nan]元素的最长序列为nan,有7个元素。

[0.16, 1, 0.16, 0.9999, 0.0001, 0.16, 0.101]将选择这两个序列中较长的序列,并在两种情况下都返回max