Подтвердить что ты не робот

Pythonic способ создания массива numpy из списка массивов numpy

Я генерирую список одномерных массивов numpy в цикле и позже конвертирую этот список в массив 2d numpy. Я бы предварительно выделил массив 2d numpy, если бы знал количество элементов раньше времени, но я этого не делаю, поэтому я помещаю все в список.

Макет ниже:

>>> 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), чем положить их в список, а затем сделать из него numpy.array(я создаю новый obj и копирование данных)? Существует ли "расширяемая" матричная структура данных, доступная в хорошо проверенном модуле?

Типичный размер моей 2d-матрицы будет составлять от 100x10 до 5000x10 поплавков

EDIT: В этом примере я использую карту, но в моем фактическом приложении у меня есть цикл цикла

4b9b3361

Ответ 1

Предположим, вы знаете, что последний массив 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:

#!/usr/bin/env python
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 = 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())    

Ответ 2

Удобный способ, используя 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.]])

Ответ 3

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

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

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

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

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

Ответ 4

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

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