簡體   English   中英

函數式編程:Numpy向量化函數以創建期望值數組

[英]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.

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