【问题标题】:Efficiently index 2d numpy array using two 1d arrays使用两个 1d 数组有效索引 2d numpy 数组
【发布时间】:2017-12-01 18:59:36
【问题描述】:

我有一个大型 2d numpy 数组和两个 1d 数组,它们表示 2d 数组中的 x/y 索引。我想使用这些一维数组对二维数组执行操作。 我可以使用 for 循环来做到这一点,但是在处理大型数组时它非常慢。有没有更快的方法?我尝试将一维数组简单地用作索引,但这不起作用。看这个例子:

import numpy as np

# Two example 2d arrays
cnt_a   =   np.zeros((4,4))
cnt_b   =   np.zeros((4,4))

# 1d arrays holding x and y indices
xpos    =   [0,0,1,2,1,2,1,0,0,0,0,1,1,1,2,2,3]
ypos    =   [3,2,1,1,3,0,1,0,0,1,2,1,2,3,3,2,0]

# This method works, but is very slow for a large array
for i in range(0,len(xpos)):
    cnt_a[xpos[i],ypos[i]] = cnt_a[xpos[i],ypos[i]] + 1

# This method is fast, but gives incorrect answer
cnt_b[xpos,ypos] = cnt_b[xpos,ypos]+1


# Print the results
print 'Good:'
print cnt_a
print ''
print 'Bad:'
print cnt_b

由此产生的输出是:

Good:
[[ 2.  1.  2.  1.]
 [ 0.  3.  1.  2.]
 [ 1.  1.  1.  1.]
 [ 1.  0.  0.  0.]]

Bad:
[[ 1.  1.  1.  1.]
 [ 0.  1.  1.  1.]
 [ 1.  1.  1.  1.]
 [ 1.  0.  0.  0.]]

对于 cnt_b 数组,numpy 显然没有正确求和,但我不确定如何在不诉诸用于计算 cnt_a 的(v.低效)for 循环的情况下解决这个问题。

【问题讨论】:

  • 您可以通过将循环中的行更改为cnt_a[xpos[i],ypos[i]] += 1,将第一个 for 循环的速度提高一倍。
  • xy2-column 数组中,这是一个相关的 Q&A

标签: python arrays python-2.7 performance numpy


【解决方案1】:

使用一维索引的另一种方法(@Shai 建议)扩展以回答实际问题:

>>> out = np.zeros((4, 4))
>>> idx = np.ravel_multi_index((xpos, ypos), out.shape) # extract 1D indexes
>>> x = np.bincount(idx, minlength=out.size)
>>> out.flat += x

np.bincount 计算每个索引在xpos, ypos 中出现的次数,并将它们存储在x 中。

或者,正如@Divakar 所建议的那样:

>>> out.flat += np.bincount(idx, minlength=out.size)

【讨论】:

  • 应该比np.add.at快!
  • @Divakar 我不确定。我猜这取决于存在多少索引以及输出数组有多大。如果索引的数量足够大,它可能会更快,但如果目标数组很大并且只需要修改很少的索引np.add.at 应该会更快。
  • 或者可以这样做:out.ravel() = np.binconunt...,以节省一步?平面视图寻求帮助?
  • 是的,out.ravel() = ... 因某些奇怪的原因无法正常工作(python3 中的can't assign to function call 错误),但out.flat = ... 应该可以正常工作。
【解决方案2】:

我们可以计算线性索引,然后累加到使用np.add.at 初始化为零的输出数组。因此,将xposypos 作为数组,这是一种实现方式-

m,n = xpos.max()+1, ypos.max()+1
out = np.zeros((m,n),dtype=int)
np.add.at(out.ravel(), xpos*n+ypos, 1)

示例运行 -

In [95]: # 1d arrays holding x and y indices
    ...: xpos    =   np.array([0,0,1,2,1,2,1,0,0,0,0,1,1,1,2,2,3])
    ...: ypos    =   np.array([3,2,1,1,3,0,1,0,0,1,2,1,2,3,3,2,0])
    ...: 

In [96]: cnt_a   =   np.zeros((4,4))

In [97]: # This method works, but is very slow for a large array
    ...: for i in range(0,len(xpos)):
    ...:     cnt_a[xpos[i],ypos[i]] = cnt_a[xpos[i],ypos[i]] + 1
    ...:     

In [98]: m,n = xpos.max()+1, ypos.max()+1
    ...: out = np.zeros((m,n),dtype=int)
    ...: np.add.at(out.ravel(), xpos*n+ypos, 1)
    ...: 

In [99]: cnt_a
Out[99]: 
array([[ 2.,  1.,  2.,  1.],
       [ 0.,  3.,  1.,  2.],
       [ 1.,  1.,  1.,  1.],
       [ 1.,  0.,  0.,  0.]])

In [100]: out
Out[100]: 
array([[2, 1, 2, 1],
       [0, 3, 1, 2],
       [1, 1, 1, 1],
       [1, 0, 0, 0]])

【讨论】:

  • 谢谢,这比我原来的解决方案快一点。它还具有处理原始数组的任何“添加”的优点,而不是简单地添加 1(通过传递给 np.add.at 的最后一个变量)。
【解决方案3】:

您可以同时迭代两个列表,并为每对增加(如果您不习惯,zip 可以合并列表)

for x, y in zip(xpos, ypos):
    cnt_b[x][y] += 1

但这将与您的解决方案 A 的速度大致相同。 如果您的列表 xpos/ypos 的长度为 n,我看不出如何在 o(n) 内更新矩阵,因为您必须以一种或另一种方式检查每一对.

其他解决方案:您可以计算(可能使用collections.Counter)相似的索引对(例如:(0, 3) 等...)并使用计数值更新矩阵。但我怀疑它会更快,因为更新矩阵所获得的时间会因为计算多次出现而丢失。

也许我完全错了,在这种情况下,我也很想看到一个不是 o(n) 的答案

【讨论】:

    【解决方案4】:

    我想你正在寻找ravel_multi_index函数

    lidx = np.ravel_multi_index((xpos, ypos), cnt_a.shape)
    

    将一维索引“展平”为cnt_acnt_b

    np.add.at( cnt_b, lidx, 1 )
    

    【讨论】:

    • 我认为问题在于两次索引相同的坐标,而不是索引本身(例如索引[0,0] 在列表中出现两次)。
    猜你喜欢
    • 2022-01-09
    • 2021-04-17
    • 2017-06-18
    • 2021-08-21
    • 1970-01-01
    • 2022-01-21
    • 1970-01-01
    • 2023-03-30
    • 1970-01-01
    相关资源
    最近更新 更多