在numpy数组中有效插入未对齐的元素

时间:2014-10-07 03:30:19

标签: python arrays performance numpy matrix

我正在使用numpy 1.9 来处理一组数组。假设我有类似的东西,我有两个2d数组AB以及1-d数组C,看起来像这样:

>>> A
array([[ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.]])
>>> B
array([[-1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1.]])
>>> C
array([1, 3, 2, 4, 0])

我的目标是根据C在A中插入所有元素。更具体地说,如果位置0处的C具有1,则应在A [0,1]之后插入B [0,1]。

这是预期的结果:

array([[ 1,  1, -1,  1,  1,  1],
       [ 1,  1,  1,  1, -1,  1],
       [ 1,  1,  1, -1,  1,  1],
       [ 1,  1,  1,  1,  1, -1],
       [ 1, -1,  1,  1,  1,  1]])

我试图像这样实现它,但速度不是很快:

for i in xrange(size(C, 0)):
    j = C[i]
    A[i, :] = numpy.insert(A[i], j, B[i, j])

有一种方法可以让它更快吗? (使用单个numpy操作,如面具或类似的东西)

3 个答案:

答案 0 :(得分:5)

一个令人讨厌的单行怎么样?

首先,数据;数组与你的形状具有相同的形状,但我使用了整数来使示例更容易阅读。

In [81]: A
Out[81]: 
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24]])

In [82]: B
Out[82]: 
array([[   0,  100,  200,  300,  400],
       [ 500,  600,  700,  800,  900],
       [1000, 1100, 1200, 1300, 1400],
       [1500, 1600, 1700, 1800, 1900],
       [2000, 2100, 2200, 2300, 2400]])

In [83]: C
Out[83]: array([1, 3, 2, 4, 0])

这是令人讨厌的单行:

In [84]: np.insert(A.ravel(), np.ravel_multi_index((range(A.shape[0]), C), A.shape) + 1, B[range(B.shape[0]), C]).reshape(A.shape[0], A.shape[1]+1)
Out[84]: 
array([[   0,    1,  100,    2,    3,    4],
       [   5,    6,    7,    8,  800,    9],
       [  10,   11,   12, 1200,   13,   14],
       [  15,   16,   17,   18,   19, 1900],
       [  20, 2000,   21,   22,   23,   24]])

以下是分解版本:

A.ravel()A展平为一维数组,我称之为F

In [87]: F = A.ravel()

In [88]: F
Out[88]: 
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19, 20, 21, 22, 23, 24])

(编辑:事实证明,第一步 - 展平A - 没有必要。正如@hpaulj在答案中指出的那样,np.insert默认会使数组变平。)

np.ravel_multi_index用于将所需的2-d位置转换为平坦数组中的索引。最后的+ 1是必要的,因为你想在 {/ 1}}中给出的索引之后插入元素:

C

In [89]: insert_indices = np.ravel_multi_index((range(A.shape[0]), C), A.shape) + 1 In [90]: insert_indices Out[90]: array([ 2, 9, 13, 20, 21]) B[range(B.shape[0]), C]中提取所需的值:

B

In [91]: values = B[range(B.shape[0]), C] In [92]: values Out[92]: array([ 100, 800, 1200, 1900, 2000]) 执行实际插入并创建一个新数组:

np.insert

现在只需重塑一下即可获得最终结果:

In [93]: np.insert(F, insert_indices, values)
Out[93]: 
array([   0,    1,  100,    2,    3,    4,    5,    6,    7,    8,  800,
          9,   10,   11,   12, 1200,   13,   14,   15,   16,   17,   18,
         19, 1900,   20, 2000,   21,   22,   23,   24])

答案 1 :(得分:3)

首先,一些稍微清晰易读的数组:

>>> A
array([[ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.]])
>>> B
array([[-1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1.],
       [-1., -1., -1., -1., -1.]])
>>> C
array([1, 3, 2, 4, 0])

接下来,一些面具恶作剧:

>>> ge_mask = C.reshape(-1, 1) >= numpy.arange(5)
>>> eq_mask = C.reshape(-1, 1) == numpy.arange(5)
>>> lt_mask = C.reshape(-1, 1) < numpy.arange(5)

政变:

>>> result = numpy.zeros((A.shape[0], A.shape[1] + 1))
>>> result[:,0:5][ge_mask] = A[ge_mask]
>>> result[:,1:6][eq_mask] = B[eq_mask]
>>> result[:,1:6][lt_mask] = A[lt_mask]
>>> result
array([[ 1.,  1., -1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1., -1.,  1.],
       [ 1.,  1.,  1., -1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1., -1.],
       [ 1., -1.,  1.,  1.,  1.,  1.]])
从记忆的角度来看,

Warren's公布的答案似乎更好。关于速度不确定。 (我确实认为以上内容更为清晰!)

答案 2 :(得分:3)

我相信这是经过纠正的迭代:

A=np.arange(25).reshape(5,5)
B=np.arange(25).reshape(5,5)*-1
C=np.array([1,3,2,4,0])

A2=np.zeros((5,6),dtype=int)
for i,c in enumerate(C):
    A2[i,:]=np.insert(A[i],c+1,B[i,c])
制造

array([[  0,   1,  -1,   2,   3,   4],
       [  5,   6,   7,   8,  -8,   9],
       [ 10,  11,  12, -12,  13,  14],
       [ 15,  16,  17,  18,  19, -19],
       [ 20, -20,  21,  22,  23,  24]])

这可以变为单线:

 np.array([np.insert(a, c+1, b[c]) for  a,b,c in zip(A,B,C)])

Warren的回答中的等价术语是:

c <=> c = np.ravel_multi_index((range(5), C), (5,5))
b <=> B.ravel()[c]
np.insert(A, c+1, B.ravel()[c]).reshape(5,6)

np.insert ravels A作为默认值。对于这个小例子,这个ravel_multi_index比行迭代快2倍。