簡體   English   中英

為什么'groupby(x,np.isnan)'與'groupby(x)如果key是nan'的行為不同?

[英]Why does 'groupby(x, np.isnan)' behave differently to 'groupby(x) if key is nan'?

由於我們是圍繞numpy的nan進行特殊處理的 ,所以我發現了一些我也不理解的東西。 我發布這個問題主要是作為MSeifert的擴展,因為看來我們兩個觀察都可能有一個共同的原因。

早些時候, 我發布了一個解決方案 ,其中涉及在包含nan值的序列上使用itertools.groupby

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

但是,我在上面鏈接的MSeifert問題上看到了這個答案 ,它顯示了我可能制定此算法的另一種方法:

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 ”或“ not nan ”,並返回最長的連續序列。

這也意味着我對groupby(sequence, keyfunc)工作原理的假設是錯誤的,並且修改后的函數不是原始函數的可行替代方案。

但是,我仍然不確定在列表和數組上運行原始函數時結果的差異。

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源代碼中 ,第90行顯示函數PyObject_RichCompareBool用於比較兩個鍵。

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

盡管這基本上等效於在Python中使用== ,但是文檔注意到一種特殊性:

注意:如果O1和O2是同一個對象, PyObject_RichCompareBool()將始終返回1Py_EQ0Py_NE

這意味着實際上執行了此比較(等效代碼):

if o1 is o2:
    return True
else:
    return o1 == o2

因此,對於列表,我們具有相同的nan對象,它們被標識為相等。 相反,數組為我們提供了值nan不同對象,它們與== -進行比較,但nan == nan總是求值為False

好吧,我想我已經為自己畫了足夠清楚的圖畫。

這里有兩個因素在起作用:

  • 我自己對keyfunc參數對groupby所做的誤解。
  • 關於Python如何表示數組和列表中的nan值的(非常有趣的)故事,在此答案中得到了最好的解釋。

解釋keyfunc

groupby文檔中

每當關鍵功能的值改變時,它都會產生一個中斷或新的組

np.isnan文檔中

對於標量輸入,如果輸入為NaN,則結果為值為True的新布爾值;否則為false。 否則,值為False。

基於這兩件事,我們推斷出,當將keyfunc設置為np.isnan ,傳遞給groupyby的序列中的每個元素groupyby將被映射為TrueFalse ,這取決於它是否為nan 這意味着鍵函數將僅在nan元素和非nan元素之間的邊界處發生變化,因此groupby僅會將序列分為nan和non- nan元素的連續塊。

相反, 原始函數( groupby(sequence) if key is nan ,則使用groupby(sequence) if key is nan )將對keyfunc (其默認值)使用identity函數 這自然會導致nan身份的細微差別,這將在下面進行解釋(以及在上面的鏈接的答案中),但是這里的if key is nan會過濾掉所有以non- nan元素為鍵的組。

解釋nan身份的細微差別

正如我在上面鏈接的答案中更好地解釋的那樣,出現在Python內置列表中的所有nan實例似乎都是一個實例 換句話說,列表中所有出現的nan都指向內存中的同一位置。 與此相反,使用numpy數組時,會即時生成nan元素,所有單獨的對象也是如此。

使用以下代碼對此進行了演示:

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)

使用原始問題定義的列表運行此命令時,將獲得以下輸出(突出顯示相同的元素):

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]

該功能似乎在內存中兩個單獨的位置之間交替以存儲這些值。 請注意,沒有任何元素與過濾條件中使用的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和non- nan元素的連續序列。

對於我們用於實驗的列表/數組, nan元素的最長序列為[nan, nan, nan] ,它具有三個元素,非nan元素的最長序列為[0.16, 1, 0.16, 0.9999, 0.0001, 0.16, 0.101] ,其中包含7個元素。

max將選擇這兩個序列中的較長者,在兩種情況下均返回7

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM