web-dev-qa-db-fra.com

Manière pythonique de créer un tableau numpy à partir d'une liste de tableaux numpy

Je génère une liste de tableaux numpy unidimensionnels dans une boucle et convertis plus tard cette liste en un tableau numpy 2d. J'aurais préalloué un tableau numpy 2d si je connaissais le nombre d'éléments à l'avance, mais je ne le sais pas, donc je mets tout dans une liste.

La maquette est ci-dessous:

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

Ma question est la suivante:

Existe-t-il un meilleur moyen (en termes de performances) de procéder à la collecte de données numériques séquentielles (dans mon cas, des tableaux numpy) que de les mettre dans une liste, puis d'en faire un tableau numpy (je crée un nouvel obj et copie les données)? Existe-t-il une structure de données matricielle "extensible" disponible dans un module bien testé?

Une taille typique de ma matrice 2d serait comprise entre 100x10 et 5000x10 flotteurs

ÉDITER: Dans cet exemple, j'utilise la carte, mais dans mon application actuelle, j'ai une boucle for

40
Dragan Chupacabric

Supposons que vous sachiez que le tableau final arr ne sera jamais supérieur à 5000x10. Vous pouvez ensuite pré-allouer un tableau de taille maximale, le remplir avec des données tout au long de la boucle, puis utiliser arr.resize pour le réduire à la taille découverte après avoir quitté la boucle.

Les tests ci-dessous suggèrent que cela sera légèrement plus rapide que la construction de listes intermédiaires python, quelle que soit la taille ultime du tableau.

Aussi, arr.resize désalloue la mémoire inutilisée, donc l'empreinte mémoire finale (mais peut-être pas intermédiaire) est plus petite que celle utilisée par python_lists_to_array.

Ceci montre numpy_all_the_way est plus rapide:

% 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

Ceci montre numpy_all_the_way utilise moins de mémoire:

% 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 __== '__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())
18
unutbu

Manière pratique, en utilisant numpy.concatenate . Je pense que c'est aussi plus rapide que la réponse de @ 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.]])
19
Gill Bates

Encore plus simple que la réponse de @Gill Bates, voici un code d'une ligne:

np.stack(list_of_arrays, axis=0)
13
fnjn

J'ajouterai ma propre version de la réponse de ~ unutbu. Similaire à numpy_all_the, mais vous redimensionnez dynamiquement si vous avez une erreur d'index. Je pensais que cela aurait été un peu plus rapide pour les petits ensembles de données, mais c'est un peu plus lent - la vérification des limites ralentit trop les choses.

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
wisty

Réponse @fnjn encore plus simple

np.vstack(list_of_arrays)

Ce que vous faites est la méthode standard. Une propriété des tableaux numpy est qu'ils ont besoin d'une mémoire contiguë. La seule possibilité de "trous" à laquelle je peux penser est possible avec le membre strides de PyArrayObject, mais cela n'affecte pas la discussion ici. Étant donné que les tableaux numpy ont une mémoire contiguë et sont "préalloués", ajouter une nouvelle ligne/colonne signifie allouer de la nouvelle mémoire, copier des données, puis libérer l'ancienne mémoire. Si vous faites beaucoup cela, ce n'est pas très efficace.

Un cas où quelqu'un pourrait ne pas vouloir créer une liste puis la convertir en un tableau numpy à la fin est lorsque la liste contient beaucoup de nombres: un tableau numpy de nombres prend beaucoup moins d'espace qu'un natif Python liste de nombres (puisque la liste native Python stocke Python objets). Pour vos tailles de tableau typiques, je ne pense pas que ce soit un problème.

Lorsque vous créez votre tableau final à partir d'une liste de tableaux, vous copiez toutes les données vers un nouvel emplacement pour le nouveau (2D dans votre exemple) ) tableau. C'est toujours beaucoup plus efficace que d'avoir un tableau numpy et de faire next = numpy.vstack((next, new_row)) chaque fois que vous obtenez de nouvelles données. vstack() copiera toutes les données pour chaque "ligne".

Il y avait un fil sur la liste de diffusion numpy-discussion il y a quelque temps qui discutait de la possibilité d'ajouter un nouveau type de tableau numpy qui permet une extension/ajout efficace. Il semble qu'il y ait eu un intérêt important à ce moment-là, même si je ne sais pas si quelque chose en est sorti. Vous voudrez peut-être regarder ce fil.

Je dirais que ce que vous faites est très Pythonique et efficace, donc à moins d'avoir vraiment besoin d'autre chose (plus d'espace, peut-être?), Vous devriez être d'accord. C'est ainsi que je crée mes tableaux numpy quand je ne connais pas le nombre d'éléments dans le tableau au début.

2
Alok Singhal