Передача массива объектов в качестве индекса в 2d массив - PullRequest
1 голос
/ 29 апреля 2020

Имеется 2d массив:

a = np.array([[10,0,30,10],[40,50,60,10],[70,80,90,10]])

Массив индекса как массив объектов:

i = np.array([[0,1],[0,2],[0,1,2]])  #Note different lengths

Ожидаемый результат:

e = [[10,0,30,10,40,50,60,10],[10,0,30,10,70,80,90,10],[10,0,30,10,40,50,60,10,70,80,90,10]]   

Что работает:

e = [np.hstack(a[i[j]]) for j in range(len(i))]

Есть ли способ сделать это чисто векторизованным способом?

Я обнаружил, что numpy .where () не работает, так как элементы в массиве индекса (т.е. i) должны иметь одинаковую длину, что не в моем случае. Может кто-нибудь направить меня в правильном направлении?

РЕДАКТИРОВАТЬ: добавляя к вышеупомянутому вопросу, мне также интересно знать, как сделать ту же самую операцию, когда массив 'a' изменяется на:

a = np.array([[10,0,30,10],[40,50,60,10],[70,80,90,10,30]])#NOTE:Jagged array

Массив индекса 'i', однако, остается тем же!

Ответы [ 3 ]

1 голос
/ 29 апреля 2020

Если a является неровным, но i является многомерным, мы можем использовать i для индексации a:

In [78]: a = np.array([[10,0,30,10],[40,50,60,10],[70,80,90,10,30]])#NOTE:Jagged array                 
In [79]: i = np.array([[0,1],[0,2],[1,2]])                                                             

In [80]: a.shape    #  an array of list objects                                                                                       
Out[80]: (3,)

In [81]: a[i]                                                                                          
Out[81]: 
array([[list([10, 0, 30, 10]), list([40, 50, 60, 10])],
       [list([10, 0, 30, 10]), list([70, 80, 90, 10, 30])],
       [list([40, 50, 60, 10]), list([70, 80, 90, 10, 30])]], dtype=object)

Поскольку это объекты списка, мы можем использовать sum чтобы "объединить" их:

In [82]: a[i].sum(axis=1)                                                                              
Out[82]: 
array([list([10, 0, 30, 10, 40, 50, 60, 10]),
       list([10, 0, 30, 10, 70, 80, 90, 10, 30]),
       list([40, 50, 60, 10, 70, 80, 90, 10, 30])], dtype=object)

понимание вашего списка:

In [83]: e = [np.hstack(a[i[j]]) for j in range(len(i))]                                               
In [84]: e                                                                                             
Out[84]: 
[array([10,  0, 30, 10, 40, 50, 60, 10]),
 array([10,  0, 30, 10, 70, 80, 90, 10, 30]),
 array([40, 50, 60, 10, 70, 80, 90, 10, 30])]

некоторые моменты времени:

In [85]: timeit a[i].sum(axis=1)                                                                       
8.64 µs ± 17.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [86]: timeit e = [np.hstack(a[i[j]]) for j in range(len(i))]                                        
63.3 µs ± 168 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Ваш hstack может быть медленнее, потому что конвертируется a списки для массивов. Давайте пройдем мимо этого:

In [89]: [sum(a[i[j]],[]) for j in range(len(i))]                                                      
Out[89]: 
[[10, 0, 30, 10, 40, 50, 60, 10],
 [10, 0, 30, 10, 70, 80, 90, 10, 30],
 [40, 50, 60, 10, 70, 80, 90, 10, 30]]
In [90]: timeit [sum(a[i[j]],[]) for j in range(len(i))]                                               
8.41 µs ± 109 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Иногда чистые списочные решения быстрее. Преобразование списков массивов занимает время.

===

Если оба массива выровнены и многомерны, мы можем использовать чисто «векторизованное» решение:

In [104]: aa = np.array([[10,0,30,10],[40,50,60,10],[70,80,90,10]])                                    
In [105]: i                                                                                            
Out[105]: 
array([[0, 1],
       [0, 2],
       [1, 2]])
In [106]: aa[i]                                                                                        
Out[106]: 
array([[[10,  0, 30, 10],
        [40, 50, 60, 10]],

       [[10,  0, 30, 10],
        [70, 80, 90, 10]],

       [[40, 50, 60, 10],
        [70, 80, 90, 10]]])
In [107]: aa[i].reshape(3,-1)                                                                          
Out[107]: 
array([[10,  0, 30, 10, 40, 50, 60, 10],
       [10,  0, 30, 10, 70, 80, 90, 10],
       [40, 50, 60, 10, 70, 80, 90, 10]])
In [108]: timeit aa[i].reshape(3,-1)                                                                   
5.07 µs ± 57.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Но когда один или несколько массивов / списков разорваны, вы теряете эту опцию и должны серьезно рассмотреть альтернативы списков.

0 голосов
/ 29 апреля 2020

Если вы просто хотите скрыть for l oop, вы можете использовать map следующим образом:

In [891]: import numpy as np

In [892]: a = np.array([[10,  0, 30, 10], 
     ...:               [40, 50, 60, 10], 
     ...:               [70, 80, 90, 10]])

In [893]: i = [[0, 1], 
     ...:      [0, 2], 
     ...:      [0, 1, 2]]

In [894]: e = [[10, 0, 30, 10, 40, 50, 60, 10], 
     ...:      [10, 0, 30, 10, 70, 80, 90, 10], 
     ...:      [10, 0, 30, 10, 40, 50, 60, 10, 70, 80, 90, 10]]

In [895]: e == list(map(lambda x: a[x].flatten().tolist(), i))
Out[895]: True

Обратите внимание, что код выше фактически не векторизован (см. Список понимание против карты ).

0 голосов
/ 29 апреля 2020

Numpy массивы обычно бывают одинакового размера. В вашем случае ваш i массив будет работать так же хорошо, как список списков, потому что длины не согласованы.

Ваш ответ работает нормально, у меня есть другое решение, но оно работает аналогично вашему:

import numpy as np

a = np.array([[10,0,30,10],[40,50,60,10],[70,80,90,10]])
# 'i' can also be a list of lists
i = [[0,1],[0,2],[0,1,2]]

[np.concatenate(a[j]) for j in i]
>>> [array([10,  0, 30, 10, 40, 50, 60, 10]),
     array([10,  0, 30, 10, 70, 80, 90, 10]),
     array([10,  0, 30, 10, 40, 50, 60, 10, 70, 80, 90, 10])]

Другой метод с использованием векторов:

# make 'i' a consistent shape by including NaN values 
i = np.array([[0,1,np.nan],[0,2,np.nan],[0,1,2]])

# filter out NaN values and index
a[i[np.isfinite(i)].astype(int)]
>>> array([[10,  0, 30, 10],
           [40, 50, 60, 10],
           [10,  0, 30, 10],
           [70, 80, 90, 10],
           [10,  0, 30, 10],
           [40, 50, 60, 10],
           [70, 80, 90, 10]])

Я думаю, что на этом этапе вам все равно придется использовать al oop для правильной конкатенации, поэтому либо первый метод, либо ваш ответ более просты.

...