[英]Functional Programming: Numpy Vectorizable Function to Create an Expected Values Array
我想對一些分類數據計數進行卡方統計檢驗-但要做到這一點,我需要計算與觀察到的結果數組匹配的數組的每個單元格的期望值。
數組中每個元素e的內容的偽代碼為
e = column_sum * row_sum / total_sum
我編寫了一個函數,該函數會將數組轉換為對應的期望值:
def gen_expected(a_array):
new_array = np.zeros(a_array.shape,dtype=float)
for ri,r in enumerate(a_array):
for ci,c in enumerate(a_array.T):
new_array[ri,ci]=(c.sum()*r.sum()/a_array.sum())
return new_array
這已經足夠好了,但是我真的很想采用一種更具功能性的方法,並在元素級別定義一個可以向量化(使用np.vectorize
)並應用而無需執行任何潛在的昂貴循環的函數。
我的問題是元素級別所需的信息不足以生成所需的輸出-我試圖弄清楚如何從(大概)元素級別的函數中訪問匯總總和值-這根本不可能,還是我尚不知道有一種適合這種依賴於聚合的條件的功能模式?
您可以使用廣播使用numpy內置插件來執行此操作。 廣播使您可以將兩個不同形狀的數組加在一起,而不必過度復制或循環播放。
我們可以通過創建分別代表行和列總和的兩個向量,並將它們“相乘”在一起,然后將它們廣播到正確大小和形狀的數組中,來解決您的問題。
我所知道的關於該主題的最佳介紹是傑克·范德普拉斯(Jake Vanderplass )的演講“ 失去循環:使用Numpy實現快速數值計算”。 它包含一些視覺示例,我發現這些示例對於環繞廣播業至關重要。
這是一個簡單的例子:
在
import numpy as np
a = np.arange(3)
b = np.reshape(np.arange(3), [3, 1])
print('a = ', a)
print('b = ')
print(b)
print('a+b = ')
print(a+b)
出:
a = [0 1 2]
b =
[[0]
[1]
[2]]
a+b =
[[0 1 2]
[1 2 3]
[2 3 4]]
我們可以通過創建分別代表行和列總和的兩個向量來將它們“相乘”在一起,並將它們廣播到正確大小和形狀的數組中,從而解決您的問題。
import numpy as np
def gen_expected(array: np.ndarray):
col_sums = (np.sum(array, axis=0))
row_sums = np.sum(array, axis=1)
np.reshape(row_sums, [len(row_sums), 1])
return (col_sums * row_sums) / np.sum(array)
# NOTE: this result might be transposed! Check it yourself!
函數scipy.stats.chi2_contingency
將為您計算expected
數組。 例如,
In [303]: from scipy.stats import chi2_contingency
In [304]: a = np.array([[3, 5, 10], [2, 4, 16]])
In [305]: chi2, p, dof, expected = chi2_contingency(a)
In [306]: expected
Out[306]:
array([[ 2.25, 4.05, 11.7 ],
[ 2.75, 4.95, 14.3 ]])
如果只需要expected
數組,則可以使用scipy.stats.contingency.expected_freq
:
In [307]: from scipy.stats.contingency import expected_freq
In [308]: expected_freq(a)
Out[308]:
array([[ 2.25, 4.05, 11.7 ],
[ 2.75, 4.95, 14.3 ]])
要了解如何expected_freq
:計算的結果,你可以在這里看到源代碼https://github.com/scipy/scipy/blob/master/scipy/stats/contingency.py
您會看到代碼是矢量化的; 唯一的顯式循環是輸入數組的維數(在margins(a)
函數中)。
因此,使用示例數組:
In [147]: arr = np.arange(16).reshape(4,4)
和您的代碼:
In [148]: def gen_expected(a_array):
...: new_array = np.zeros(a_array.shape,dtype=float)
...: for ri,r in enumerate(a_array):
...: for ci,c in enumerate(a_array.T):
...: new_array[ri,ci]=(c.sum()*r.sum()/a_array.sum())
...: return new_array
...:
In [149]: gen_expected(arr)
Out[149]:
array([[ 1.2 , 1.4 , 1.6 , 1.8 ],
[ 4.4 , 5.13333333, 5.86666667, 6.6 ],
[ 7.6 , 8.86666667, 10.13333333, 11.4 ],
[ 10.8 , 12.6 , 14.4 , 16.2 ]])
函數中唯一的元素級別或標量是3項乘積和除法:
In [151]: def chi0(csum, rsum, asum):
...: return csum*rsum/asum
我們可以在向量化中“包裝”:
In [152]: fchi0 = np.vectorize(chi0, otypes=[float])
vectorize
很好地完成了彼此之間廣播數組並將結果提供給函數的工作。 實際上,它可以執行行和列的枚舉。 我們只需要取相關的和:
In [153]: fchi0(arr.sum(axis=1,keepdims=True), arr.sum(axis=0,keepdims=True), arr.sum())
Out[153]:
array([[ 1.2 , 1.4 , 1.6 , 1.8 ],
[ 4.4 , 5.13333333, 5.86666667, 6.6 ],
[ 7.6 , 8.86666667, 10.13333333, 11.4 ],
[ 10.8 , 12.6 , 14.4 , 16.2 ]])
但是有了這三個總和,我就不需要使用vectorize
中介了:
In [154]: arr.sum(axis=1,keepdims=True)* arr.sum(axis=0,keepdims=True) / arr.sum()
Out[154]:
array([[ 1.2 , 1.4 , 1.6 , 1.8 ],
[ 4.4 , 5.13333333, 5.86666667, 6.6 ],
[ 7.6 , 8.86666667, 10.13333333, 11.4 ],
[ 10.8 , 12.6 , 14.4 , 16.2 ]])
In [155]: arr.sum(axis=1,keepdims=True), arr.sum(axis=0,keepdims=True), arr.sum()
Out[155]:
(array([[ 6],
[22],
[38],
[54]]),
array([[24, 28, 32, 36]]),
120)
廣播的關鍵是創建列向量,行向量和總和(標量)。 一個是形狀(4,1),另一個是形狀(1,4)。 keepdims
參數保持2d形狀。 沒有它,我們必須在第一個維度上添加一個維度。 (4,)廣播到(1,4),但廣播到(4,1)需要明確確定。
當生成索引時,例如np.arange(4)
, np.ix_
是一個方便的工具
In [156]: np.ix_(arr.sum(axis=1), arr.sum(axis=0))
Out[156]:
(array([[ 6],
[22],
[38],
[54]]), array([[24, 28, 32, 36]]))
在添加keepdims
之前,使用np.newaxis
添加尺寸是-現在仍然是-最喜歡的:
In [157]: arr.sum(axis=1)[:,None] * arr.sum(axis=0) / arr.sum()
Out[157]:
array([[ 1.2 , 1.4 , 1.6 , 1.8 ],
[ 4.4 , 5.13333333, 5.86666667, 6.6 ],
[ 7.6 , 8.86666667, 10.13333333, 11.4 ],
[ 10.8 , 12.6 , 14.4 , 16.2 ]])
通常,從范圍生成索引時建議使用ix_
。
In [160]: I,J = np.ix_(range(10,40,10), range(1,5))
In [161]: I+J
Out[161]:
array([[11, 12, 13, 14],
[21, 22, 23, 24],
[31, 32, 33, 34]])
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.