在NumPy

时间:2017-03-21 17:54:14

标签: python performance pandas numpy matrix

假设我有两个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)

这似乎有点快,但我觉得还有比这更直接的方法。

3 个答案:

答案 0 :(得分:3)

将输入转换为1D等值,然后使用np.searchsortednp.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扩展到朝向右轴的新维度(也称为广播),查找a1a1的唯一行之间的匹配行并为这两个数组提取非零行的索引。

初始化一个零数组,并根据获得的索引切片填充它的值。

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