2015-10-28 34 views
1

我有一個包含一系列值的二維numpy數組和一個標識該單元所屬組的匹配數組。我希望在每個組的第一個數組中運行一些簡單的操作。爲了說明這一點,我可能有一個看起來像這樣的數據和ID陣列:根據另一個ID值有效處理一個數組中的值

data = np.arange(9).reshape(3,3) 
IDs = np.array([ 
    ["A", "A", "B"], 
    ["A", "B", "C"], 
    ["B", "C", "C"] 
]) 

我可能要採取的平均值和標準偏差爲所有A,B和C值,有一個看起來輸出像這樣:

[['A', 1.333333333333333, 1.247219128924647], 
['B', 4.0, 1.6329931618554521], 
['C', 6.666666666666667, 1.247219128924647]] 

現在,我解決了這個問題,下面的代碼:

unique_IDs = np.unique(IDs) 
results = [] 
for ID in unique_IDs: 
    group_data = data[IDs == ID] 
    mean = np.mean(group_data) 
    stdev = np.std(group_data) 
    results.append([ID, mean, stdev]) 

我認爲這是簡單易懂,但它不是非常快。當使用我的真實數據時,數組通常是〜200 MB(5,000 * 10,000而不是3 * 3),並且有數百個唯一ID。每次迭代需要幾秒鐘,整個操作可能需要超過半小時。

我的「直覺本能」是,如果我不必迭代每個唯一的ID值並重複查找和操作,就可以更快地完成這項工作,但我不知道這是真的還是我會怎麼做關於它。

回答

1

如果你打算做了很多這些類型的分組操作的我會強烈建議你看一看pandas

import numpy as np 
import pandas as pd 

data = np.arange(9).reshape(3,3) 
IDs = np.array([["A", "A", "B"], 
       ["A", "B", "C"], 
       ["B", "C", "C"]]) 
df = pd.DataFrame({'ID':IDs.ravel(), 'data':data.ravel()}) 
print(df) 
# ID data 
# 0 A  0 
# 1 A  1 
# 2 B  2 
# 3 A  3 
# 4 B  4 
# 5 C  5 
# 6 B  6 
# 7 C  7 
# 8 C  8 

print(df.groupby('ID').agg([np.mean, np.std])) 
#   data   
#   mean  std 
# ID      
# A 1.333333 1.527525 
# B 4.000000 2.000000 
# C 6.666667 1.527525 

here獲取更多示例。

+0

我發現這種方法清晰明瞭,當使用5000 * 5000陣列和500個唯一ID進行測試時,其速度提高了40倍。 – Joe

2

您可以使用np.unique來獲得對應於字符串IDs的數字IDs。這些數字ID在計算meanstd時都會有用。

現在,平均計算,這些數字ID可作爲"weights"np.bincount分檔,給我們對應於每個ID數據元素的總和。接下來。使用每個ID的元素計數,然後可以得到平均值。這裏的執行 -

_,idx,counts = np.unique(IDs,return_inverse=True,return_counts=True) 
mean_vals = np.bincount(idx,data.ravel())/counts 

對於std計算,一個辦法是進行排序的ID,使得相同的ID被連續放置。然後,重新排列數據以形成另一個2D陣列,使得對應於相同ID的所有數據元素在相同行中,並且將未填充位置設置爲NaNs。這裏的想法是以列向量化的方式執行np.std。將要填補的地方需要掩蔽。請注意,這可能是一個飢餓的方法,如果有一個ID相對較高的計數。實施將重新使用idxcounts從代碼更早,會是這個樣子 -

data_RO = np.empty((counts.size,counts.max())) 
data_RO[:] = np.nan 
data_RO[np.arange(counts.max()) < counts[:,None]] = data.ravel()[idx.argsort()] 
std_vals = np.nanstd(data_RO,axis=1) 

採樣運行和驗證輸出 -

1)輸入:

In [51]: data 
Out[51]: 
array([[0, 1, 6], 
     [2, 5, 0], 
     [6, 3, 6]]) 

In [52]: IDs 
Out[52]: 
array([['A', 'A', 'B'], 
     ['A', 'B', 'C'], 
     ['B', 'A', 'C']], 
     dtype='|S1') 

2)代碼中列出的代碼輸出:

In [53]: unique_IDs = np.unique(IDs) 

In [54]: results = [] 

In [55]: for ID in unique_IDs: 
    ....:   group_data = data[IDs == ID] 
    ....:   mean = np.mean(group_data) 
    ....:   stdev = np.std(group_data) 
    ....:   results.append([ID, mean, stdev]) 
    ....:  

In [56]: results 
Out[56]: 
[['A', 1.5, 1.1180339887498949], 
['B', 5.666666666666667, 0.47140452079103168], 
['C', 3.0, 3.0]] 

3)提出的解決方案輸出:

In [57]: _,idx,counts = np.unique(IDs,return_inverse=True,return_counts=True) 

In [58]: np.bincount(idx,data.ravel())/counts 
Out[58]: array([ 1.5  , 5.66666667, 3.  ]) 

In [59]: data_RO = np.empty((counts.size,counts.max())) 

In [60]: data_RO[:] = np.nan 

In [61]: mask = np.arange(counts.max()) < counts[:,None] 

In [62]: data_RO[mask] = data.ravel()[idx.argsort()] 

In [63]: np.nanstd(data_RO,axis=1) 
Out[63]: array([ 1.11803399, 0.47140452, 3.  ]) 
+0

您的std方法(可以推廣到其他np函數)可能會過於耗費內存,因爲這些ID有可能是非常不對稱的。但是,我會給它一個機會 - 如果結果比其他建議的解決方案快得多,這可能是值得的。謝謝! – Joe

相關問題