假设我有两个NumPy矩阵(或者Pandas DataFrames,虽然我猜这在NumPy中会更快)。
>>> arr1
array([[3, 1, 4],
[4, 3, 5],
[6, 5, 4],
[6, 5, 4],
[3, 1, 4]])
>>> arr2
array([[3, 1, 4],
[8, 5, 4],
[3, 1, 4],
[6, 5, 4],
[3, 1, 4]])
对于arr1
中的每个行向量,我想计算arr2
中该行向量的出现次数,并生成这些计数的向量。因此,对于此示例,结果将是
[3, 0, 1, 1, 3]
有效的方法是什么?
第一种方法:
在arr1
的行向量上使用循环并在arr2
上生成相应的布尔向量的明显方法似乎非常慢。
np.apply_along_axis(lambda x: (x == arr2).all(1).sum(), axis=1, arr=arr1)
这似乎是一个糟糕的算法,因为我必须多次检查相同的行。
第二种方法:我可以将行计数存储在collections.Counter中,然后只使用apply_along_axis
访问它。
cnter = Counter(tuple(row) for row in arr2)
np.apply_along_axis(lambda x: cnter[tuple(x)], axis=1, arr=arr1)
这似乎有点快,但我觉得还有比这更直接的方法。
答案 0 :(得分:3)
将输入转换为1D等值,然后使用np.searchsorted
和np.bincount
进行排序并进行计数,这是一种NumPy方法 -
def searchsorted_based(a,b):
dims = np.maximum(a.max(0), b.max(0))+1
a1D = np.ravel_multi_index(a.T,dims)
b1D = np.ravel_multi_index(b.T,dims)
unq_a1D, IDs = np.unique(a1D, return_inverse=1)
fidx = np.searchsorted(unq_a1D, b1D)
fidx[fidx==unq_a1D.size] = 0
mask = unq_a1D[fidx] == b1D
count = np.bincount(fidx[mask])
out = count[IDs]
return out
示例运行 -
In [308]: a
Out[308]:
array([[3, 1, 4],
[4, 3, 5],
[6, 5, 4],
[6, 5, 4],
[3, 1, 4]])
In [309]: b
Out[309]:
array([[3, 1, 4],
[8, 5, 4],
[3, 1, 4],
[6, 5, 4],
[3, 1, 4],
[2, 1, 5]])
In [310]: searchsorted_based(a,b)
Out[310]: array([3, 0, 1, 1, 3])
运行时测试 -
In [377]: A = a[np.random.randint(0,a.shape[0],(1000))]
In [378]: B = b[np.random.randint(0,b.shape[0],(1000))]
In [379]: np.allclose(comp2D_vect(A,B), searchsorted_based(A,B))
Out[379]: True
# @Nickil Maveli's soln
In [380]: %timeit comp2D_vect(A,B)
10000 loops, best of 3: 184 µs per loop
In [381]: %timeit searchsorted_based(A,B)
10000 loops, best of 3: 92.6 µs per loop
答案 1 :(得分:2)
<强> numpy的: 强>
从使用a2
收集np.ravel_multi_index
的行和列下标的线性索引等价物开始。添加1以考虑numpy的基于0的索引。获取np.unique()
中存在的唯一行的相应计数。接下来,通过将a2
扩展到朝向右轴的新维度(也称为广播),查找a1
和a1
的唯一行之间的匹配行并为这两个数组提取非零行的索引。
初始化一个零数组,并根据获得的索引切片填充它的值。
def comp2D_vect(a1, a2):
midx = np.ravel_multi_index(a2.T, a2.max(0)+1)
a, idx, cnt = np.unique(midx, return_counts=True, return_index=True)
m1, m2 = (a1[:, None] == a2[idx]).all(-1).nonzero()
out = np.zeros(a1.shape[0], dtype=int)
out[m1] = cnt[m2]
return out
<强> 基准: 强>
对于: a2 = a2.repeat(100000, axis=0)
%%timeit
df = pd.DataFrame(a2, columns=['a', 'b', 'c'])
df_count = df.groupby(df.columns.tolist()).size()
df_count.reindex(a1.T.tolist(), fill_value=0).values
10 loops, best of 3: 67.2 ms per loop # @ Ted Petrou's solution
%timeit comp2D_vect(a1, a2)
10 loops, best of 3: 34 ms per loop # Posted solution
%timeit searchsorted_based(a1,a2)
10 loops, best of 3: 27.6 ms per loop # @ Divakar's solution (winner)
答案 2 :(得分:1)
熊猫将是一个很好的工具。您可以将arr2放入数据框中,并使用groupby
方法计算每行的出现次数,然后使用arr1重新索引结果。
arr1=np.array([[3, 1, 4],
[4, 3, 5],
[6, 5, 4],
[6, 5, 4],
[3, 1, 4]])
arr2 = np.array([[3, 1, 4],
[8, 5, 4],
[3, 1, 4],
[6, 5, 4],
[3, 1, 4]])
df = pd.DataFrame(arr2, columns=['a', 'b', 'c'])
df_count = df.groupby(df.columns.tolist()).size()
df_count.reindex(arr1.T.tolist(), fill_value=0)
输出
a b c
3 1 4 3
4 3 5 0
6 5 4 1
4 1
3 1 4 3
dtype: int64
<强>计时强>
首先创建更多数据
arr2_2 = arr2.repeat(100000, axis=0)
现在时间:
%%timeit
cnter = Counter(tuple(row) for row in arr2_2)
np.apply_along_axis(lambda x: cnter[tuple(x)], axis=1, arr=arr1)
1个循环,每个循环最好为3:704 ms
%%timeit
df = pd.DataFrame(arr2_2, columns=['a', 'b', 'c'])
df_count = df.groupby(df.columns.tolist()).size()
df_count.reindex(arr1.T.tolist(), fill_value=0)
10个循环,最佳3:每循环53.8 ms