[英]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()
將始終返回1
的Py_EQ
和0
的Py_NE
。
這意味着實際上執行了此比較(等效代碼):
if o1 is o2:
return True
else:
return o1 == o2
因此,對於列表,我們具有相同的nan
對象,它們被標識為相等。 相反,數組為我們提供了值nan
不同對象,它們與==
-進行比較,但nan == nan
總是求值為False
。
好吧,我想我已經為自己畫了足夠清楚的圖畫。
這里有兩個因素在起作用:
keyfunc
參數對groupby
所做的誤解。 nan
值的(非常有趣的)故事,在此答案中得到了最好的解釋。 解釋keyfunc
每當關鍵功能的值改變時,它都會產生一個中斷或新的組
對於標量輸入,如果輸入為NaN,則結果為值為True的新布爾值;否則為false。 否則,值為False。
基於這兩件事,我們推斷出,當將keyfunc
設置為np.isnan
,傳遞給groupyby
的序列中的每個元素groupyby
將被映射為True
或False
,這取決於它是否為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.