Добавление Numpy: автоматическое приведение массива неправильного размера

#python #list #performance #numpy #append

#python #Список #Производительность #numpy #добавить

Вопрос:

есть ли способ выполнить следующее без предложения if?

Я читаю набор файлов netcdf с помощью pupynere и хочу создать массив с помощью numpy append. Иногда входные данные многомерны (см. переменную «a» ниже), иногда одномерны («b»), но количество элементов в первом измерении всегда одно и то же («9» в примере ниже).

 > import numpy as np
> a = np.arange(27).reshape(3,9)
> b = np.arange(9)
> a.shape
(3, 9)
> b.shape
(9,)
  

это работает так, как ожидалось:

 > np.append(a,a, axis=0)
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, 25, 26],
   [ 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, 25, 26]])
  

но добавление b работает не так элегантно:

 > np.append(a,b, axis=0)
ValueError: arrays must have same number of dimensions
  

Проблема с append заключается в (из руководства numpy)

«Когда указана ось, значения должны иметь правильную форму».

Мне пришлось бы сначала выполнить приведение, чтобы получить правильный результат.

 > np.append(a,b.reshape(1,9), axis=0)
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, 25, 26],
   [ 0,  1,  2,  3,  4,  5,  6,  7,  8]])
  

Итак, в моем цикле чтения файла я в настоящее время использую предложение if, подобное этому:

 for i in [a, b]:
    if np.size(i.shape) == 2:
        result = np.append(result, i, axis=0)
    else:
        result = np.append(result, i.reshape(1,9), axis=0)
  

Есть ли способ добавить «a» и «b» без оператора if?

РЕДАКТИРОВАТЬ: Хотя @Sven отлично ответил на исходный вопрос (используя np.atleast_2d() ), он (и другие) указал, что код неэффективен. В приведенном ниже ответе я объединил их предложения и заменил свой исходный код. Теперь это должно быть намного эффективнее. Спасибо.

Ответ №1:

Вы можете использовать numpy.atleast_2d() :

 result = np.append(result, np.atleast_2d(i), axis=0)
  

Тем не менее, обратите внимание, что повторное использование numpy.append() является очень неэффективным способом построения массива NumPy — его приходится перераспределять на каждом шаге. Если это вообще возможно, предварительно выделите массив с желаемым конечным размером и заполните его впоследствии с помощью нарезки.

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

1. спасибо за быстрый ответ. Интересно, я не знал метода atleast_2d(), но, похоже, он работает. Однако, я думаю, вы имели в виду np.atleast_2d (i)? Что касается распределения, я не знаю окончательного размера, могу ли я все еще что-то сделать, чтобы уменьшить неэффективность?

2. Если важна производительность, вы можете сначала собрать все массивы, которые вы хотите объединить, в список Python, вычислить конечный размер вашего массива, а затем выделить и заполнить массив. (Просто задайте новый вопрос, если возникнут какие-либо проблемы с этим подходом.)

3. @Sven, повторно: сохраняем массивы. В большинстве ситуаций это, вероятно, лучше, чем мое предложение (при условии, что памяти достаточно для хранения всех массивов дважды).

4. @Sven @Henry, большое спасибо за вашу помощь и советы. Если я столкнусь с проблемой скорости, я попробую использовать метод списка python. ciao!

5. @Sebastian: Просто еще одна мысль — возможно, вызов numpy.vstack() после создания списка, как описано выше, является вашим лучшим предположением.

Ответ №2:

Вы можете просто добавить все массивы в список, а затем использовать np.vstack() для объединения их всех вместе в конце. Это позволяет избежать постоянного перераспределения растущего массива с каждым добавлением.

 |1> a = np.arange(27).reshape(3,9)

|2> b = np.arange(9)

|3> np.vstack([a,b])
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, 25, 26],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8]])
  

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

1. Да, похоже, это работает, также @Sven рекомендовал это. Причина, по которой я не должен вызывать result = np.vstack(result,i) в моем цикле for, заключается в неэффективности, верно?

Ответ №3:

Я собираюсь улучшить свой код с помощью @Sven, @Henry и @Robert. @Sven ответил на вопрос, поэтому он заслужил репутацию за этот вопрос, но — как подчеркивали он и другие — есть более эффективный способ сделать то, что я хочу.

Это включает в себя использование списка python, который позволяет добавлять с снижением производительности O (1), тогда как numpy.append() имеет снижение производительности O (N * * 2). После этого список преобразуется в массив numpy:

Предположим, i имеет либо тип a , либо b :

 > a = np.arange(27).reshape(3,9)
> b = np.arange(9)
> a.shape
(3, 9)
> b.shape
(9,)
  

Инициализируйте список и добавьте все прочитанные данные, например, если данные отображаются в порядке ‘aaba’.

 > mList = []
> for i in [a,a,b,a]:
     mList.append(i)
  

Ваш mList будет выглядеть следующим образом:

 > mList
[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, 25, 26]]),
 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, 25, 26]]),
 array([0, 1, 2, 3, 4, 5, 6, 7, 8]),
 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, 25, 26]])]
  

наконец, vstack список для формирования массива numpy:

 > result = np.vstack(mList[:])
> result.shape
(10, 9)
  

Еще раз спасибо за ценную помощь.

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

1. В этом нет необходимости np.atleast_2d() при использовании np.vstack() , но и вреда немного.

2. @Robert о, ты прав. Отлично, это делает его еще более компактным. Я изменю свой ответ.

Ответ №4:

Как указывалось, append необходимо перераспределить каждый массив numpy. Альтернативное решение, которое выделяет один раз, было бы чем-то вроде этого:

 total_size = 0
for i in [a,b]:
    total_size  = i.size

result = numpy.empty(total_size, dtype=a.dtype)
offset = 0
for i in [a,b]:
    # copy in the array
    result[offset:offset i.size] = i.ravel()
    offset  = i.size

# if you know its always divisible by 9:
result = result.reshape(result.size//9, 9)
  

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

 result = result[0:known_final_size]
  

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

1. хороший фрагмент, спасибо. Однако для этого мне потребовалось бы открывать каждый из файлов (несколько сотен) два раза, верно? Во-первых, для получения размера (цикл 1) и, во-вторых, для извлечения данных (цикл 2). Хранение файла (указателя?) в памяти, вероятно, также неэффективно (но я никоим образом не могу судить об этом). Что вы думаете?

2. Я не знаком с точными библиотеками, о которых идет речь, поэтому простите меня, если ответ не может быть полностью передан. Если дело в том, что вы каждый раз загружаете файл, можете ли вы получить total_size из метаданных файла (конечно, размер файла установит верхнюю границу размера массива, предполагая, что он не сжат)? Что касается разницы во времени, просто измерьте ее! (модуль timeit делает это очень простым). Кстати, самый простой способ решить проблему многократной загрузки файла — это сделать то, что предлагает @Sven в своих комментариях к своему сообщению, и просто загрузить все массивы в память.