2010-01-21 12 views
28

Genero un elenco di matrici numpy unidimensionali in un ciclo e successivamente converto questo elenco in un array 2p numpy. Avrei preassegnato un array 2d numpy se conoscessi il numero di elementi prima del tempo, ma non lo faccio, quindi metto tutto in una lista.Modo Pythonic per creare una matrice numpy da un elenco di array numpy

Il mock up è qui sotto:

>>> 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.]]) 

La mia domanda è la seguente:

c'è un modo migliore (performancewise) per andare circa il compito di raccogliere dati numerici sequenziali (nel mio caso numpy array) che metterli in una lista e quindi creare un numpy.array (sto creando un nuovo obj e copiando i dati)? Esiste una struttura di dati di matrice "espandibile" disponibile in un modulo ben testato?

Una dimensione tipica della mia matrice 2d sarebbe tra 100x10 e 5000x10 galleggia

EDIT: In questo esempio, sto usando la mappa, ma nella mia effettiva applicazione ho un ciclo for

risposta

14

Supponiamo di sapere che l'array finale arr non sarà mai più grande di 5000x10. Quindi è possibile pre-allocare un array di dimensioni massime, popolarlo con i dati come si passa attraverso il ciclo e quindi utilizzare arr.resize per ridurlo alla dimensione scoperta di dopo essere usciti dal ciclo.

Le prove di seguito suggeriscono di farlo sarà leggermente più veloce rispetto alla costruzione di elenchi di python intermedi indipendentemente dalla dimensione massima dell'array.

Inoltre, arr.resize deseleziona la memoria non utilizzata, quindi l'impronta di memoria finale (anche se non intermedia) è inferiore a quella utilizzata da python_lists_to_array.

Questo dimostra numpy_all_the_way è più veloce:

% 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 

Questo dimostra numpy_all_the_way usa meno memoria:

% 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

Cosa stai facendo è il modo standard. Una proprietà di array numpy è che hanno bisogno di memoria contigua. L'unica possibilità di "buchi" che posso pensare è possibile con il strides membro di PyArrayObject, ma ciò non influisce sulla discussione qui. Poiché gli array di numpy hanno memoria contigua e sono "preallocati", aggiungere una nuova riga/colonna significa allocare nuova memoria, copiare i dati e quindi liberare la vecchia memoria. Se lo fai molto, non è molto efficiente.

Un caso in cui qualcuno potrebbe non voler creare una lista e quindi convertirlo in una matrice numpy alla fine è quando la lista contiene molti numeri: una serie numerica di numeri occupa molto meno spazio di una lista Python nativa di numeri (dato che l'elenco Python nativo memorizza oggetti Python). Per le dimensioni tipiche dell'array, non penso che sia un problema.

Quando si crea l'array finale da un elenco di matrici, è che copia tutti i dati in una nuova posizione per l'array nuovo (2-nel proprio esempio). Questo è ancora molto più efficiente che avere un array numpy e fare next = numpy.vstack((next, new_row)) ogni volta che ottieni nuovi dati. vstack() copierà tutti i dati per ogni "riga".

C'è stato un po 'di tempo fa thread on numpy-discussion mailing list che ha discusso la possibilità di aggiungere un nuovo tipo di array numpy che consente un'estensione/accodamento efficiente. Sembra che ci fosse un interesse significativo in questo momento, anche se non so se ne sia uscito qualcosa. Potresti voler guardare quel thread.

Direi che quello che stai facendo è molto Pythonic ed efficiente, quindi a meno che tu non abbia davvero bisogno di qualcos'altro (più efficienza dello spazio, forse?), Dovresti stare bene. È così che creo i miei array numpy quando non conosco il numero di elementi nella matrice all'inizio.

+0

@Alok --- grazie per la risposta premurosa . I tempi nella risposta di ~ unubuntu mostrano una preoccupazione circa il 5% di efficienza. Questo è quasi certamente un errore finché non arrivi al punto che devi assolutamente avere quel 5%. – telliott99

2

io aggiungo la mia versione di ~ risposta di unutbu. Simile a numpy_all_the way, ma ridimensiona dinamicamente se hai un errore di indice. Ho pensato che sarebbe stato un po 'più veloce per i piccoli set di dati, ma è un po' più lento - il controllo dei limiti rallenta troppo le cose.

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 
11

modo conveniente, utilizzando numpy.concatenate. Credo che sia anche più veloce, più di @ unutbu risposta:

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.]]) 
0

ancora più semplice di risposta @Gill Bates', qui è un codice di una sola riga:

np.stack(list_of_arrays, axis=0) 
Problemi correlati