2010-12-07 6 views
15

En supposant que j'ai un tableau numpy comme: [1,2,3,4,5,6] et un autre tableau: [0,0,1,2,2,1] Je veux additionner les éléments du premier tableau par groupe (le deuxième tableau) et obtenir les résultats des n-groupes dans l'ordre des numéros de groupe (dans ce cas, le résultat serait [3, 9, 9]). Comment puis-je le faire en numpy?Somme tableau par nombre en numpy

+0

Pourquoi avez-vous besoin de numpy pour cela? N'utilisez-vous pas seulement les listes vanilla python? Sinon, quel type chiffré utilisez-vous? –

+1

J'ai besoin de numpy pour cela parce que je ne veux pas parcourir le tableau n fois pour n groupes, puisque mes tailles de tableaux peuvent être arbitrairement grandes. Je n'utilise pas les listes python, je montrais juste un exemple de jeu de données entre parenthèses. Le type de données est int. –

+0

connexes http://stackoverflow.com/questions/7089379/most-efficient-way-to-sum-huge-2d-numpy-array-grouped-by-id-column – TooTone

Répondre

8

Il y a plus d'une façon de faire, mais voici une façon:

import numpy as np 
data = np.arange(1, 7) 
groups = np.array([0,0,1,2,2,1]) 

unique_groups = np.unique(groups) 
sums = [] 
for group in unique_groups: 
    sums.append(data[groups == group].sum()) 

Vous pouvez choses vectoriser afin qu'il n'y ait pas de boucle du tout, mais je recommanderais contre elle. Il devient illisible, et nécessitera quelques tableaux temporaires 2D, ce qui pourrait nécessiter une grande quantité de mémoire si vous avez beaucoup de données.

Editer: Voici une façon de vectoriser entièrement. Gardez à l'esprit que cela peut (et sera probablement) plus lent que la version ci-dessus. (Et il peut y avoir une meilleure façon de le vectoriser, mais il est tard et je suis fatigué, donc c'est juste la première chose à entrer dans ma tête ...)

Cependant, gardez à l'esprit que c'est un mauvais exemple ... Vous êtes vraiment mieux (en termes de vitesse et de lisibilité) avec la boucle au-dessus ...

import numpy as np 
data = np.arange(1, 7) 
groups = np.array([0,0,1,2,2,1]) 

unique_groups = np.unique(groups) 

# Forgive the bad naming here... 
# I can't think of more descriptive variable names at the moment... 
x, y = np.meshgrid(groups, unique_groups) 
data_stack = np.tile(data, (unique_groups.size, 1)) 

data_in_group = np.zeros_like(data_stack) 
data_in_group[x==y] = data_stack[x==y] 

sums = data_in_group.sum(axis=1) 
+0

Merci! La mémoire n'est pas un problème et j'aimerais éviter les boucles. Comment le vectoriserais-tu? –

+0

@Scribble Master - Voir le montage ... Il n'y a rien de mal à boucler sur les groupes uniques, cependant. La deuxième version sera probablement lente, et sacrément difficile à lire. Avec la boucle, vous bouclerez seulement (en python, de toute façon) sur le nombre de groupes uniques. La comparaison interne 'data [groups == group]' sera assez rapide. –

+0

Merci encore! –

0

pure mise en œuvre du python:

l = [1,2,3,4,5,6] 
g = [0,0,1,2,2,1] 

from itertools import izip 
from operator import itemgetter 
from collections import defaultdict 

def group_sum(l, g): 
    groups = defaultdict(int) 
    for li, gi in izip(l, g): 
     groups[gi] += li 
    return map(itemgetter(1), sorted(groups.iteritems())) 

print group_sum(l, g) 

[3, 9, 9] 
7

Si les groupes sont indexé par des entiers consécutifs, vous pouvez abuser de la fonction numpy.histogram() pour obtenir le résultat:

data = numpy.arange(1, 7) 
groups = numpy.array([0,0,1,2,2,1]) 
sums = numpy.histogram(groups, 
         bins=numpy.arange(groups.min(), groups.max()+2), 
         weights=data)[0] 
# array([3, 9, 9]) 

Cela évitera toutes les boucles Python.

+0

Bon point! +1 de moi! –

24

Ceci est une méthode vectorisée de faire cette somme basée sur l'implémentation de numpy.unique. Selon mes timings, il est jusqu'à 500 fois plus rapide que la méthode de la boucle et jusqu'à 100 fois plus rapide que la méthode de l'histogramme.

def sum_by_group(values, groups): 
    order = np.argsort(groups) 
    groups = groups[order] 
    values = values[order] 
    values.cumsum(out=values) 
    index = np.ones(len(groups), 'bool') 
    index[:-1] = groups[1:] != groups[:-1] 
    values = values[index] 
    groups = groups[index] 
    values[1:] = values[1:] - values[:-1] 
    return values, groups 
+0

Fonctionne avec brio! – Nate

22

La fonction numpy bincount a été fait exactement à cette fin et je suis sûr que ce sera beaucoup plus rapide que les autres méthodes pour toutes les tailles d'entrées:

data = [1,2,3,4,5,6] 
ids = [0,0,1,2,2,1] 

np.bincount(ids, weights=data) #returns [3,9,9] as a float64 array 

L'élément i-ième la sortie est la somme de tous les éléments data correspondant à "id" i.

Espérons que cela aide.

+1

Peut confirmer que c'est très rapide. Environ 10 fois plus rapide que la méthode sum_by_group fournie par Bi Rico sur de petites entrées. –

+0

que se passe-t-il si 'données' sont des vecteurs? – zzh1996

+0

Il semble que l'argument des poids doive être unidimensionnel. Une solution consiste à exécuter binaire une fois pour chaque dimension du vecteur (c'est-à-dire deux fois si les données sont un ensemble de vecteurs 2-d). Une légère modification de la réponse de Peter devrait également fonctionner. – Alex

4

J'ai essayé des scripts de tout le monde et mes considérations sont:

Joe: ne fonctionnera que si vous avez quelques groupes.

kevpie: Trop lent à cause des boucles (ce n'est pas ainsi pythonique)

Bi_Rico et Sven: effectuer bon, mais ne fonctionnera que pour Int32 (si la somme dépasse 2^32/2, il échouera) Alex: est le plus rapide, bon pour la somme.

Mais si vous voulez plus de flexibilité et la possibilité de regrouper par d'autres statistiques utilisent SciPy:

from scipy import ndimage 

data = np.arange(10000000) 
groups = np.arange(1000).repeat(10000) 
ndimage.sum(data, groups, range(1000)) 

Ce qui est bon parce que vous avez beaucoup de statistiques à groupe (somme, moyenne, variance, ...).

0

Je remarqué la balise numpy mais dans le cas où vous ne me dérange pas en utilisant pandas, cette tâche devient un one-liner:

import pandas as pd 
import numpy as np 

data = np.arange(1, 7) 
groups = np.array([0, 0, 1, 2, 2, 1]) 

df = pd.DataFrame({'data': data, 'groups': groups}) 

Alors df ressemble alors à ceci:

data groups 
0  1  0 
1  2  0 
2  3  1 
3  4  2 
4  5  2 
5  6  1 

maintenant vous pouvez utiliser les fonctions groupby() et sum()

print df.groupby(['groups'], sort=False).sum() 

qui vous donne la sortie désirée

 data 
groups  
0   3 
1   9 
2   9 

Par défaut, le dataframe serait réglé, donc j'utiliser le drapeau sort=False qui pourrait améliorer la vitesse pour dataframes énormes.

2

Vous avez tous tort! La meilleure façon de le faire est:

a = [1,2,3,4,5,6] 
ix = [0,0,1,2,2,1] 
accum = np.zeros(np.max(ix)+1) 
np.add.at(accum, ix, a) 
print accum 
> array([ 3., 9., 9.]) 
Questions connexes