2010-12-07 8 views
15

Suponiendo que tengo una matriz numpy como: [1,2,3,4,5,6] y otra matriz: [0,0,1,2,2,1] Quiero sumar los elementos en la primera matriz por grupo (la segunda matriz) y obtener resultados de n-groups en orden de número de grupo (en este caso, el resultado sería [3, 9, 9]). ¿Cómo hago esto en numpy?Suma matriz por número en numpy

+0

¿Por qué necesitas numpy para esto? ¿No estás usando listas vanilla python? Si no, ¿qué tipo de numpy estás usando? –

+1

Necesito numpy para esto porque no quiero recorrer la matriz n veces para n grupos, ya que los tamaños de mi matriz pueden ser arbitrariamente grandes. No estoy usando listas de Python, solo estaba mostrando un conjunto de datos de ejemplo entre paréntesis. El tipo de datos es int. –

+0

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

Respuesta

8

Hay más de una manera de hacer esto, pero aquí es una manera:

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()) 

Usted puede Vectorize cosas por lo que no hay para el lazo en absoluto, pero me gustaría recomendar en contra de ella. Se vuelve ilegible y requerirá un par de arreglos temporales en 2D, lo que podría requerir grandes cantidades de memoria si tiene muchos datos.

Editar: Aquí hay una forma de vectorizar completamente. Tenga en cuenta que esto puede (y probablemente será) más lento que la versión anterior. (Y puede haber una forma mejor de vectorizar esto, pero es tarde y estoy cansado, así que esto es solo lo primero que me viene a la cabeza ...)

Sin embargo, tenga en cuenta que este es un mal ejemplo ... Eres realmente mejor (tanto en términos de velocidad y facilidad de lectura) con el bucle de arriba ...

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

¡Gracias! La memoria no es un problema y me gustaría evitar los bucles. ¿Cómo lo vectorizarías? –

+0

@Scribble Master - Ver la edición ... Sin embargo, no hay nada de malo en pasar por encima de los grupos únicos. La segunda versión probablemente sea lenta, y es muy difícil de leer. Con el bucle, solo está bucleando (en python, de todos modos) sobre el número de grupos únicos. La comparación interna 'data [groups == group]' será bastante rápida. –

+0

¡Gracias de nuevo! –

0

Una aplicación Python puro:

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 los grupos son indexado por enteros consecutivos, puede abusar de la función numpy.histogram() para obtener el resultado:

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

Esto evitará cualquier bucle de Python.

+0

¡Buen punto! +1 de mi parte –

24

Este es un método vectorizado de hacer esta suma basado en la implementación de numpy.unique. De acuerdo con mis tiempos, es hasta 500 veces más rápido que el método de bucle y hasta 100 veces más rápido que el método de histograma.

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

¡Funciona brillantemente! – Nate

22

La función numpy bincount se hizo precisamente para este propósito y estoy seguro de que será mucho más rápido que los otros métodos para todos los tamaños de entradas:

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 

El elemento i de el resultado es la suma de todos los elementos data correspondientes a "id" i.

Espero que ayude.

+1

Puede confirmar que esto es muy rápido. Aproximadamente 10 veces más rápido que el método sum_by_group que Bi Rico proporcionó en pequeñas entradas. –

+0

¿y si 'data' son vectores? – zzh1996

+0

Parece que el argumento de los pesos tiene que ser de 1 dimensión. Una solución es ejecutar bincount una vez para cada dimensión del vector (es decir, dos veces si los datos son un conjunto de vectores bidimensionales). Una pequeña modificación de la respuesta de Peter también debería funcionar. – Alex

4

me trataron guiones de todos y mis consideraciones son:

Joe: sólo funcionará si usted tiene pocos grupos.

kevpie: Demasiado lento debido a los bucles (esto no es así Pythonic)

Bi_Rico y Sven: realizar buenas, pero sólo funcionarán para Int32 (si la suma va más de 2^32/2 se producirá un error)

Alex: es el más rápido, bueno para la suma.

Pero si quieres más flexibilidad y la posibilidad de agrupar por otras estadísticas sobre el uso SciPy:

from scipy import ndimage 

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

Esto es bueno porque tiene muchas estadísticas de grupo (suma, media, la varianza, ...).

0

me di cuenta de la etiqueta numpy pero en caso de que no les importa usar pandas, esta tarea se convierte en una sola línea:

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}) 

Así df tendrá el aspecto siguiente:

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

Ahora puede usar las funciones groupby() y sum()

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

que le da la salida deseada

 data 
groups  
0   3 
1   9 
2   9 

Por defecto, sería solucionado la trama de datos, por lo tanto, usar la bandera sort=False que podría mejorar la velocidad de tramas de datos enormes.

2

¡Estás todo mal! La mejor manera de hacerlo es:

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