![](/img/trans.png)
[英]Counting non-zero elements within each row and within each column of a 2D NumPy array
[英]Shuffling non-zero elements of each row in an array - Python / NumPy
我有一個相對稀疏的數組,我想遍歷每一行並只對非零元素進行隨機排序。
輸入示例:
[2,3,1,0]
[0,0,2,1]
示例輸出:
[2,1,3,0]
[0,0,1,2]
注意零點如何保持不變。
要隨機播放每一行中的所有元素(包括零),我可以這樣做:
for i in range(len(X)):
np.random.shuffle(X[i, :])
我當時想做的是:
for i in range(len(X)):
np.random.shuffle(X[i, np.nonzero(X[i, :])])
但這沒有效果。 我注意到X[i, np.nonzero(X[i, :])]
的返回類型不同於X[i, np.nonzero(X[i, :])]
X[i, :]
,這可能是原因所在。
In[30]: X[i, np.nonzero(X[i, :])]
Out[30]: array([[23, 5, 29, 11, 17]])
In[31]: X[i, :]
Out[31]: array([23, 5, 29, 11, 17])
您可以將非numpy.random.permutation
與顯式非零索引一起使用:
>>> X = np.array([[2,3,1,0], [0,0,2,1]])
>>> for i in range(len(X)):
... idx = np.nonzero(X[i])
... X[i][idx] = np.random.permutation(X[i][idx])
...
>>> X
array([[3, 2, 1, 0],
[0, 0, 2, 1]])
我想我找到了三班輪?
i, j = np.nonzero(a.astype(bool))
k = np.argsort(i + np.random.rand(i.size))
a[i,j] = a[i,j[k]]
如所承諾的那樣,這是賞金期的第四天,這是我對向量化解決方案的嘗試。 下面詳細介紹了其中涉及的步驟:
為了便於參考,我們稱之為輸入數組作為a
。 每行生成唯一的索引,該索引涵蓋行長度的范圍。 為此,我們可以簡單地生成與輸入數組相同形狀的隨機數,並沿每一行獲取argsort
索引,這將是那些唯一索引。 this post
之前已經探討過這個想法。
使用這些索引作為列索引來索引輸入數組的每一行。 因此,我們將需要在此處進行advanced-indexing
。 現在,這為我們提供了一個數組,其中每一行都被隨機排列。 我們稱它為b
。
由於改組僅限於每行,因此,如果我們僅使用boolean-indexing: b[b!=0]
,我們將獲得改組的非零元素,並且還將其限制為每行非零的長度。 這是因為NumPy數組中的元素是以行優先的順序存儲的,因此使用布爾索引時,它會先選擇每行的改組后的非零元素,然后再移至下一行。 同樣,如果我們對a
相似地使用布爾索引,即a[a!=0]
,則在移至下一行之前,我們將先在每一行上獲得非零元素,並且它們將保持原始順序。 因此,最后一步是僅獲取蒙版元素b[b!=0]
並將其分配給蒙版位置a[a!=0]
。
因此,涵蓋以上三個步驟的實施方案將是-
m,n = a.shape
rand_idx = np.random.rand(m,n).argsort(axis=1) #step1
b = a[np.arange(m)[:,None], rand_idx] #step2
a[a!=0] = b[b!=0] #step3
逐步進行示例操作可能會使情況更清楚-
In [50]: a # Input array
Out[50]:
array([[ 8, 5, 0, -4],
[ 0, 6, 0, 3],
[ 8, 5, 0, -4]])
In [51]: m,n = a.shape # Store shape information
# Unique indices per row that covers the range for row length
In [52]: rand_idx = np.random.rand(m,n).argsort(axis=1)
In [53]: rand_idx
Out[53]:
array([[0, 2, 3, 1],
[1, 0, 3, 2],
[2, 3, 0, 1]])
# Get corresponding indexed array
In [54]: b = a[np.arange(m)[:,None], rand_idx]
# Do a check on the shuffling being restricted to per row
In [55]: a[a!=0]
Out[55]: array([ 8, 5, -4, 6, 3, 8, 5, -4])
In [56]: b[b!=0]
Out[56]: array([ 8, -4, 5, 6, 3, -4, 8, 5])
# Finally do the assignment based on masking on a and b
In [57]: a[a!=0] = b[b!=0]
In [58]: a # Final verification on desired result
Out[58]:
array([[ 8, -4, 0, 5],
[ 0, 6, 0, 3],
[-4, 8, 0, 5]])
我們希望在本文中對矢量化解決方案進行基准測試。 現在,矢量化試圖避免循環,我們將循環遍歷每一行並進行改組。 因此,輸入數組的設置涉及更多的行。
方法-
def app1(a): # @Daniel F's soln
i, j = np.nonzero(a.astype(bool))
k = np.argsort(i + np.random.rand(i.size))
a[i,j] = a[i,j[k]]
return a
def app2(x): # @kazemakase's soln
r, c = np.where(x != 0)
n = c.size
perm = np.random.permutation(n)
i = np.argsort(perm + r * n)
x[r, c] = x[r, c[i]]
return x
def app3(a): # @Divakar's soln
m,n = a.shape
rand_idx = np.random.rand(m,n).argsort(axis=1)
b = a[np.arange(m)[:,None], rand_idx]
a[a!=0] = b[b!=0]
return a
from scipy.ndimage.measurements import labeled_comprehension
def app4(a): # @FabienP's soln
def func(array, idx):
r[idx] = np.random.permutation(array)
return True
labels, idx = nz = a.nonzero()
r = a[nz]
labeled_comprehension(a[nz], labels + 1, np.unique(labels + 1),\
func, int, 0, pass_positions=True)
a[nz] = r
return a
基准程序#1
為了公平地進行基准測試,使用OP的示例並將其簡單地堆疊為更多的行以獲得更大的數據集似乎是合理的。 因此,通過該設置,我們可以創建具有200萬行和2000萬行數據集的兩個案例。
案例1:具有2*1000,000
行的大型數據集
In [174]: a = np.array([[2,3,1,0],[0,0,2,1]])
In [175]: a = np.vstack([a]*1000000)
In [176]: %timeit app1(a)
...: %timeit app2(a)
...: %timeit app3(a)
...: %timeit app4(a)
...:
1 loop, best of 3: 264 ms per loop
1 loop, best of 3: 422 ms per loop
1 loop, best of 3: 254 ms per loop
1 loop, best of 3: 14.3 s per loop
情況2:具有2*10,000,000
行的較大數據集
In [177]: a = np.array([[2,3,1,0],[0,0,2,1]])
In [178]: a = np.vstack([a]*10000000)
# app4 skipped here as it was slower on the previous smaller dataset
In [179]: %timeit app1(a)
...: %timeit app2(a)
...: %timeit app3(a)
...:
1 loop, best of 3: 2.86 s per loop
1 loop, best of 3: 4.62 s per loop
1 loop, best of 3: 2.55 s per loop
基准測試程序2:范圍廣泛
為了涵蓋輸入數組中非零百分比變化的所有情況,我們涵蓋了一些廣泛的基准測試場景。 另外,由於app4
似乎比其他應用慢很多,因此為了更仔細地檢查,我們在本節中跳過了該應用。
輔助函數來設置輸入數組:
def in_data(n_col, nnz_ratio):
# max no. of elems that my system can handle, i.e. stretching it to limits.
# The idea is to use this to decide the number of rows and always use
# max. possible dataset size
num_elem = 10000000
n_row = num_elem//n_col
a = np.zeros((n_row, n_col),dtype=int)
L = int(round(a.size*nnz_ratio))
a.ravel()[np.random.choice(a.size, L, replace=0)] = np.random.randint(1,6,L)
return a
主要的計時和繪圖腳本(使用IPython魔術功能。因此,需要運行opon復制並將其粘貼到IPython控制台上)-
import matplotlib.pyplot as plt
# Setup input params
nnz_ratios = np.array([0.2, 0.4, 0.6, 0.8])
n_cols = np.array([4, 5, 8, 10, 15, 20, 25, 50])
init_arr1 = np.zeros((len(nnz_ratios), len(n_cols) ))
init_arr2 = np.zeros((len(nnz_ratios), len(n_cols) ))
init_arr3 = np.zeros((len(nnz_ratios), len(n_cols) ))
timings = {app1:init_arr1, app2:init_arr2, app3:init_arr3}
for i,nnz_ratio in enumerate(nnz_ratios):
for j,n_col in enumerate(n_cols):
a = in_data(n_col, nnz_ratio=nnz_ratio)
for func in timings:
res = %timeit -oq func(a)
timings[func][i,j] = res.best
print func.__name__, i, j, res.best
fig = plt.figure(1)
colors = ['b','k','r']
for i in range(len(nnz_ratios)):
ax = plt.subplot(2,2,i+1)
for f,func in enumerate(timings):
ax.plot(n_cols,
[time for time in timings[func][i]],
label=str(func.__name__), color=colors[f])
ax.set_xlabel('No. of cols')
ax.set_ylabel('time [seconds]')
ax.grid(which='both')
ax.legend()
plt.tight_layout()
plt.title('Percentage non-zeros : '+str(int(100*nnz_ratios[i])) + '%')
plt.subplots_adjust(wspace=0.2, hspace=0.2)
時序輸出-
觀察結果:
方法#1,#2對整個輸入數組中的非零元素進行argsort
。 因此,它在非零百分比較小的情況下表現更好。
方法3創建與輸入數組形狀相同的隨機數,然后每行獲取argsort
索引。 因此,對於輸入中給定數量的非零,其定時比前兩種方法更加陡峭。
結論:
直到60%的非零標記為止,方法1的表現似乎都不錯。 對於更多的非零值,並且如果行長很小,則方法3似乎表現不錯。
我想到了:
nz = a.nonzero() # Get nonzero indexes
a[nz] = np.random.permutation(a[nz]) # Shuffle nonzero values with mask
哪個比其他建議的解決方案看起來更簡單(快一點?)。
編輯:不混合行的新版本
labels, *idx = nz = a.nonzero() # get masks
a[nz] = np.concatenate([np.random.permutation(a[nz][labels == i]) # permute values
for i in np.unique(labels)]) # for each label
其中a.nonzero()
的第一個數組( a.nonzero()
中非零值的索引)用作標簽。 這是不混合行的技巧。
然后,將np.random.permutation
應用於每個“標簽”的a[a.nonzero()]
。
可以在此處使用scipy.ndimage.measurements.labeled_comprehension
,因為它似乎因np.random.permutation
而失敗。
我終於看到它看起來很像@randomir提出的內容。 無論如何,這只是為了使其工作而面臨的挑戰。
編輯2:
最后使用scipy.ndimage.measurements.labeled_comprehension
def shuffle_rows(a):
def func(array, idx):
r[idx] = np.random.permutation(array)
return True
labels, *idx = nz = a.nonzero()
r = a[nz]
labeled_comprehension(a[nz], labels + 1, np.unique(labels + 1), func, int, 0, pass_positions=True)
a[nz] = r
return a
哪里:
func()
改組非零值 labeled_comprehension
標簽方式應用func()
這將替換先前的for循環,並且在具有許多行的陣列上會更快。
對於矢量化解決方案,這是一種可能性:
r, c = np.where(x > 0)
n = c.size
perm = np.random.permutation(n)
i = np.argsort(perm + r * n)
x[r, c] = x[r, c[i]]
向量化此問題的挑戰在於np.random.permutation
僅給出平面索引,這將使行中的數組元素np.random.permutation
。 對添加了偏移量的排列后的值進行排序可確保不會在行之間發生混洗。
這是您的兩個襯板,無需安裝numpy。
from random import random
def shuffle_nonzeros(input_list):
''' returns a list with the non-zero values shuffled '''
shuffled_nonzero = iter(sorted((i for i in input_list if i!=0), key=lambda k: random()))
print([i for i in (i if i==0 else next(shuffled_nonzero) for i in input_list)])
如果您不喜歡這種內膽,可以用
def shuffle_nonzeros(input_list):
''' generator that yields a list with the non-zero values shuffled '''
random_nonzero_values = iter(sorted((i for i in input_list if i!=0), key=lambda k: random()))
for i in iterable:
if i==0:
yield i
else:
yield next(random_nonzero_values)
或者如果您想要列表作為輸出,並且不喜歡一行理解
def shuffle_nonzeros(input_list):
''' returns a list with the non-zero values shuffled '''
out = []
random_nonzero_values = iter(sorted((i for i in input_list if i!=0), key=lambda k: random()))
for i in iterable:
if i==0:
out.append(i)
else:
out.append(next(random_nonzero_values))
return out
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.