Преобразуйте массив numpy (N,M,L) в (N*L,M 1) с дополнительным столбцом, индексированным целым числом [0,…,L-1]

#python #arrays #numpy

Вопрос:

Давайте рассмотрим этот простой пример:

 import numpy as np

a=np.arange(90)
a=a.reshape(6,3,5)

 

Я хотел бы получить массив b формы (6*5,3 1=4) с

 b[0:6,0]=a[:,0,0]
b[0:6,1]=a[:,1,0]
b[0:6,2]=a[:,2,0]
b[0:6,3]=0

b[6:12,0]=a[:,0,1]
b[6:12,1]=a[:,1,1]
b[6:12,2]=a[:,2,1]
b[6:12,3]=1
...
 

Я могу сделать это с помощью for-loops, но я уверен, что есть гораздо более элегантные решения.

Комментарии:

1. Откуда берутся записи, ссылающиеся на 1?

2. Повторяется ли дополнительная колонка 0..L-1 N несколько раз или нет 0...N*L-1 ?

3. Извините. Я исправил свой пример. Вот мое грязное решение: b = np.zeros((a.shape[0]*a.shape[2],a.shape[1] 1)); for i in range(a.shape[2]): b[i*a.shape[0]:(i 1)*a.shape[0],:-1]=a[:,:,i] b[i*a.shape[0]:(i 1)*a.shape[0],-1]=i

Ответ №1:

Я бы сначала переупорядочил оси вашего массива, затем выделил больший результирующий массив, а затем использовал два (широковещательных) назначения для установки новых значений:

 import numpy as np

a = np.arange(6*3*5).reshape(6, 3, 5)  # shape (N, M, L)

aux = a.transpose(0, 2, 1)  # shape (N, L, M)
res = np.empty_like(a, shape=aux.shape[:-1]   (aux.shape[-1]   1,))
res[..., :-1] = aux  # (N, L, M)-shaped slice
res[..., -1] = np.arange(aux.shape[1])  # (N, L)-shaped slice

# two different interpretations:
#res = res.reshape(-1, res.shape[-1])  # shape (N*L, M   1)
res = res.transpose(0, 1, 2).reshape(-1, res.shape[-1])  # shape (N*L, M   1)
 

Из двух интерпретаций вашего вопроса последняя (версия без комментариев) воспроизводит вашу «грязную версию», опубликованную в комментарии. Если это действительно то, что вам нужно, мы могли бы выполнить оригинальную транспонировку таким образом, чтобы на первое место ставилась ось L размера:

 import numpy as np

a = np.arange(6*3*5).reshape(6, 3, 5)  # shape (N, M, L)

aux = a.transpose(2, 0, 1)  # shape (L, N, M)
res = np.empty_like(a, shape=aux.shape[:-1]   (aux.shape[-1]   1,))
res[..., :-1] = aux  # (L, N, M)-shaped slice
res[..., -1] = np.arange(aux.shape[0])[:, None]  # (L, N)-shaped slice

res = res.reshape(-1, res.shape[-1])  # shape (N*L, M   1)
 

Комментарии:

1. Ваше решение довольно крутое! Это в точности соответствует моему грязному коду для цикла. Я предполагаю, что с большими массивами ваше решение будет намного быстрее.

2. @Жан-Эрик, вероятно, быстрее, да, но L время зацикливания-это тоже не конец света с точки зрения производительности. Вам придется рассчитать все варианты, если вы хотите выяснить, какой из них самый быстрый. Возможно, вам будет лучше выбрать наиболее читаемую версию.

Ответ №2:

 a_new = a.transpose(0, 2, 1).reshape(N*L, M, order="F")
extra_column = np.repeat(np.arange(L), N)
b = np.column_stack((a_new, extra_column))
 

Сначала мы меняем местами последние 2 оси a с transpose , а затем reshape меняем их на желаемую форму, но с F порядком ортрана, чтобы соответствовать выходу. Дополнительная колонка получается с повторением np.arange(L) и добавлением column_stack .


Пробный прогон:

 >>> N, M, L = 6, 3 ,5
>>> a = np.arange(N*M*L).reshape(N, M, L)
>>> # above operations...
>>> b

array([[ 0,  5, 10,  0],
       [15, 20, 25,  0],
       [30, 35, 40,  0],
       [45, 50, 55,  0],
       [60, 65, 70,  0],
       [75, 80, 85,  0],
       [ 1,  6, 11,  1],
       [16, 21, 26,  1],
       [31, 36, 41,  1],
       [46, 51, 56,  1],
       [61, 66, 71,  1],
       [76, 81, 86,  1],
       [ 2,  7, 12,  2],
       [17, 22, 27,  2],
       [32, 37, 42,  2],
       [47, 52, 57,  2],
       [62, 67, 72,  2],
       [77, 82, 87,  2],
       [ 3,  8, 13,  3],
       [18, 23, 28,  3],
       [33, 38, 43,  3],
       [48, 53, 58,  3],
       [63, 68, 73,  3],
       [78, 83, 88,  3],
       [ 4,  9, 14,  4],
       [19, 24, 29,  4],
       [34, 39, 44,  4],
       [49, 54, 59,  4],
       [64, 69, 74,  4],
       [79, 84, 89,  4]])
 

Комментарии:

1. Привет, ваше решение довольно эффективно! Он соответствует результату моего грязного кода и выглядит очень красиво. Спасибо. Я сомневаюсь, что ваше решение быстрее, чем решение Андраса Дика?

2. привет @Жан-Эрик, я провел тест, и для формы (6, 5, 3) другой ответ быстрее, для форм (60, 50, 30) , и (600, 500, 300) это кажется быстрее. Так что, я полагаю, зависит от размера массива.