我有一个像
这样的三维数组A=np.array([[[1,1], [1,0]], [[1,2], [1,0]], [[1,0], [0,0]]])
现在我想获得一个在给定位置具有非零值的数组,如果在该位置只出现一个唯一的非零值(或零)。如果在该位置仅出现零或多于一个非零值,则它应该为零。对于上面的例子,我想
[[1,0], [1,0]]
因为
A[:,0,0]
中只有1
s A[:,0,1]
中有0
,1
和2
,因此不止一个非零值
A[:,1,0]
中有0
和1
,因此保留1
A[:,1,1]
中只有0
s 我可以找到np.count_nonzero(A, axis=0)
有多少非零元素,但我想保留1
或2
s,即使它们有多个也是如此。我看了np.unique
,但它似乎不支持我想做的事情。
理想情况下,我喜欢像np.count_unique(A, axis=0)
这样的函数,它会返回原始形状的数组,例如[[1, 3],[2, 1]]
,所以我可以检查是否有3个或更多,然后忽略该位置。
所有我能想到的是一个列表理解迭代我想要获得
[[len(np.unique(A[:, i, j])) for j in range(A.shape[2])] for i in range(A.shape[1])]
还有其他想法吗?
答案 0 :(得分:3)
您可以使用np.diff
保持第二项任务的numpy级别。
def diffcount(A):
B=A.copy()
B.sort(axis=0)
C=np.diff(B,axis=0)>0
D=C.sum(axis=0)+1
return D
# [[1 3]
# [2 1]]
它在大型阵列上似乎要快一点:
In [62]: A=np.random.randint(0,100,(100,100,100))
In [63]: %timeit diffcount(A)
46.8 ms ± 769 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [64]: timeit [[len(np.unique(A[:, i, j])) for j in range(A.shape[2])]\
for i in range(A.shape[1])]
149 ms ± 700 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
最后,计算唯一性比排序简单,ln(A.shape[0])
因子可以获胜。
赢得这个因素的一种方法是使用设置机制:
In [81]: %timeit np.apply_along_axis(lambda a:len(set(a)),axis=0,A)
183 ms ± 1.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
不幸的是,这并不快。
另一种方法是手工完成:
def countunique(A,Amax):
res=np.empty(A.shape[1:],A.dtype)
c=np.empty(Amax+1,A.dtype)
for i in range(A.shape[1]):
for j in range(A.shape[2]):
T=A[:,i,j]
for k in range(c.size): c[k]=0
for x in T:
c[x]=1
res[i,j]= c.sum()
return res
在python级别:
In [70]: %timeit countunique(A,100)
429 ms ± 18.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
纯粹的python方法并没有那么糟糕。然后用numba将这个代码转移到低级别:
import numba
countunique2=numba.jit(countunique)
In [71]: %timeit countunique2(A,100)
3.63 ms ± 70.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
这将很难改善很多。
答案 1 :(得分:2)
一种方法是使用A
作为第一轴索引,用于沿其他两个轴设置相同长度的布尔数组,然后简单地计算沿其第一轴的非零值。可能有两种变体 - 一种是将其保持为3D
,另一种变体可以重塑为2D
以获得某些性能优势,因为对2D
的索引会更快。因此,这两个实现将是 -
def nunique_axis0_maskcount_app1(A):
m,n = A.shape[1:]
mask = np.zeros((A.max()+1,m,n),dtype=bool)
mask[A,np.arange(m)[:,None],np.arange(n)] = 1
return mask.sum(0)
def nunique_axis0_maskcount_app2(A):
m,n = A.shape[1:]
A.shape = (-1,m*n)
maxn = A.max()+1
N = A.shape[1]
mask = np.zeros((maxn,N),dtype=bool)
mask[A,np.arange(N)] = 1
A.shape = (-1,m,n)
return mask.sum(0).reshape(m,n)
运行时测试 -
In [154]: A = np.random.randint(0,100,(100,100,100))
# @B. M.'s soln
In [155]: %timeit f(A)
10 loops, best of 3: 28.3 ms per loop
# @B. M.'s soln using slicing : (B[1:] != B[:-1]).sum(0)+1
In [156]: %timeit f2(A)
10 loops, best of 3: 26.2 ms per loop
In [157]: %timeit nunique_axis0_maskcount_app1(A)
100 loops, best of 3: 12 ms per loop
In [158]: %timeit nunique_axis0_maskcount_app2(A)
100 loops, best of 3: 9.14 ms per loop
Numba方法
使用与nunique_axis0_maskcount_app2
相同的策略,使用numba
直接获取C级别的计数,我们会 -
from numba import njit
@njit
def nunique_loopy_func(mask, N, A, p, count):
for j in range(N):
mask[:] = True
mask[A[0,j]] = False
c = 1
for i in range(1,p):
if mask[A[i,j]]:
c += 1
mask[A[i,j]] = False
count[j] = c
return count
def nunique_axis0_numba(A):
p,m,n = A.shape
A.shape = (-1,m*n)
maxn = A.max()+1
N = A.shape[1]
mask = np.empty(maxn,dtype=bool)
count = np.empty(N,dtype=int)
out = nunique_loopy_func(mask, N, A, p, count).reshape(m,n)
A.shape = (-1,m,n)
return out
运行时测试 -
In [328]: np.random.seed(0)
In [329]: A = np.random.randint(0,100,(100,100,100))
In [330]: %timeit nunique_axis0_maskcount_app2(A)
100 loops, best of 3: 11.1 ms per loop
# @B.M.'s numba soln
In [331]: %timeit countunique2(A,A.max()+1)
100 loops, best of 3: 3.43 ms per loop
# Numba soln posted in this post
In [332]: %timeit nunique_axis0_numba(A)
100 loops, best of 3: 2.76 ms per loop