Pythonic способ создать массив NumPy из списка массивов NUMPY - PullRequest
40 голосов
/ 21 января 2010

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

Макет ниже:

>>> list_of_arrays = map(lambda x: x*ones(2), range(5))
>>> list_of_arrays
[array([ 0.,  0.]), array([ 1.,  1.]), array([ 2.,  2.]), array([ 3.,  3.]), array([ 4.,  4.])]
>>> arr = array(list_of_arrays)
>>> arr
array([[ 0.,  0.],
       [ 1.,  1.],
       [ 2.,  2.],
       [ 3.,  3.],
       [ 4.,  4.]])

Мой вопрос следующий:

Есть ли лучший способ (с точки зрения производительности) выполнить задачу сбора последовательных числовых данных (в моем случае числовых массивов), чем поместить их в список, а затем создать из него массив numpy.ar (я создаю новый объект и копирование данных)? Есть ли в хорошо протестированном модуле «расширяемая» матричная структура данных?

Типичный размер моей 2-мерной матрицы составляет от 100х10 до 5000х10 с плавающей точкой

РЕДАКТИРОВАТЬ: В этом примере я использую карту, но в моем реальном приложении у меня есть цикл for

Ответы [ 6 ]

19 голосов
/ 16 января 2016

Удобный способ, используя numpy.concatenate.Я считаю, что это также быстрее, чем ответ @ unutbu:

In [32]: import numpy as np 

In [33]: list_of_arrays = list(map(lambda x: x * np.ones(2), range(5)))

In [34]: list_of_arrays
Out[34]: 
[array([ 0.,  0.]),
 array([ 1.,  1.]),
 array([ 2.,  2.]),
 array([ 3.,  3.]),
 array([ 4.,  4.])]

In [37]: shape = list(list_of_arrays[0].shape)

In [38]: shape
Out[38]: [2]

In [39]: shape[:0] = [len(list_of_arrays)]

In [40]: shape
Out[40]: [5, 2]

In [41]: arr = np.concatenate(list_of_arrays).reshape(shape)

In [42]: arr
Out[42]: 
array([[ 0.,  0.],
       [ 1.,  1.],
       [ 2.,  2.],
       [ 3.,  3.],
       [ 4.,  4.]])
18 голосов
/ 21 января 2010

Предположим, вы знаете, что конечный массив arr никогда не будет больше 5000x10. Тогда вы можете предварительно выделить массив максимального размера, заполнить его данными как Вы проходите через цикл, а затем используете arr.resize, чтобы сократить его до обнаруженный размер после выхода из цикла.

Тесты ниже предполагают, что это будет немного быстрее, чем построение промежуточного Python перечисляет независимо от конечного размера массива.

Кроме того, arr.resize освобождает неиспользуемую память, поэтому конечная (хотя, возможно, и не промежуточная) область памяти меньше, чем используемая python_lists_to_array.

Это показывает, что numpy_all_the_way быстрее:

% python -mtimeit -s"import test" "test.numpy_all_the_way(100)"
100 loops, best of 3: 1.78 msec per loop
% python -mtimeit -s"import test" "test.numpy_all_the_way(1000)"
100 loops, best of 3: 18.1 msec per loop
% python -mtimeit -s"import test" "test.numpy_all_the_way(5000)"
10 loops, best of 3: 90.4 msec per loop

% python -mtimeit -s"import test" "test.python_lists_to_array(100)"
1000 loops, best of 3: 1.97 msec per loop
% python -mtimeit -s"import test" "test.python_lists_to_array(1000)"
10 loops, best of 3: 20.3 msec per loop
% python -mtimeit -s"import test" "test.python_lists_to_array(5000)"
10 loops, best of 3: 101 msec per loop

Это показывает, numpy_all_the_way использует меньше памяти:

% test.py
Initial memory usage: 19788
After python_lists_to_array: 20976
After numpy_all_the_way: 20348

test.py:

import numpy as np
import os


def memory_usage():
    pid = os.getpid()
    return next(line for line in open('/proc/%s/status' % pid).read().splitlines()
                if line.startswith('VmSize')).split()[-2]

N, M = 5000, 10


def python_lists_to_array(k):
    list_of_arrays = list(map(lambda x: x * np.ones(M), range(k)))
    arr = np.array(list_of_arrays)
    return arr


def numpy_all_the_way(k):
    arr = np.empty((N, M))
    for x in range(k):
        arr[x] = x * np.ones(M)
    arr.resize((k, M))
    return arr

if __name__ == '__main__':
    print('Initial memory usage: %s' % memory_usage())
    arr = python_lists_to_array(5000)
    print('After python_lists_to_array: %s' % memory_usage())
    arr = numpy_all_the_way(5000)
    print('After numpy_all_the_way: %s' % memory_usage())
12 голосов
/ 06 марта 2018

Даже проще, чем ответ @Gill Bates, вот код из одной строки:

np.stack(list_of_arrays, axis=0)
2 голосов
/ 19 октября 2018

Еще проще @fnjn ответить

np.vstack(list_of_arrays)
2 голосов
/ 21 января 2010

Я добавлю свою версию ответа ~ unutbu. Аналогично numpy_all_the way, но вы динамически изменяете размер, если у вас есть ошибка индекса. Я думал, что это было бы немного быстрее для небольших наборов данных, но немного медленнее - проверка границ слишком сильно замедляет процесс.

initial_guess = 1000

def my_numpy_all_the_way(k):
    arr=np.empty((initial_guess,M))
    for x,row in enumerate(make_test_data(k)):
        try:
            arr[x]=row
        except IndexError:
            arr.resize((arr.shape[0]*2, arr.shape[1]))
            arr[x]=row
    arr.resize((k,M))
    return arr
2 голосов
/ 21 января 2010

То, что вы делаете, является стандартным способом. Свойство массивов numpy состоит в том, что им нужна непрерывная память. Единственная возможность "дыр", о которой я могу думать, возможна с strides членом PyArrayObject, но это не влияет на обсуждение здесь. Поскольку числовые массивы имеют непрерывную память и «предварительно выделены», добавление новой строки / столбца означает выделение новой памяти, копирование данных, а затем освобождение старой памяти. Если вы делаете это много, это не очень эффективно.

Один случай, когда кто-то, возможно, не захочет создавать список и затем преобразовывать его в массив с нуля, в конце концов, когда список содержит много чисел: массив с числовыми данными занимает гораздо меньше места, чем собственный список Python числа (так как собственный список Python хранит объекты Python). Для ваших типичных размеров массивов, я не думаю, что это проблема.

Когда вы создаете свой окончательный массив из списка массивов, вы копируете все данные в новое место для нового (в вашем примере 2-го) массива. Это все еще намного эффективнее, чем иметь массив numpy и делать next = numpy.vstack((next, new_row)) каждый раз, когда вы получаете новые данные. vstack() скопирует все данные для каждой "строки".

Некоторое время назад в списке рассылки numpy-для обсуждения существовал поток, в котором обсуждалась возможность добавления нового типа массива numpy, который позволяет эффективно расширять / добавлять. Кажется, в то время это вызывало значительный интерес, хотя я не знаю, получилось ли что-то из этого. Возможно, вы захотите взглянуть на эту тему.

Я бы сказал, что то, что вы делаете, является очень питонским и эффективным, поэтому, если вам действительно не нужно что-то еще (может быть, больше эффективности пространства), с вами все будет в порядке. Именно так я создаю свои массивы numpy, когда в начале не знаю количество элементов в массиве.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...