2009-12-01 16 views
5

me gustaría hacer una buena función para agregar datos entre una matriz (que es una matriz de registro numpy, pero no cambiar nada)manera Pythonic a las matrices de agregados (numpy o no)

tiene una matriz de los datos que desea agregar entre uno de los ejes: por ejemplo, una serie de dtype=[(name, (np.str_,8), (job, (np.str_,8), (income, np.uint32)] y que desea tener el ingreso medio por puesto de trabajo

hice esta función, y en el ejemplo que debería ser llamado como aggregate(data,'job','income',mean)


def aggregate(data, key, value, func): 

    data_per_key = {} 

    for k,v in zip(data[key], data[value]): 

     if k not in data_per_key.keys(): 

      data_per_key[k]=[] 

     data_per_key[k].append(v) 

    return [(k,func(data_per_key[k])) for k in data_per_key.keys()] 

El problema es que no me parece muy agradable Me gustaría tenerlo en una línea: ¿tiene alguna idea?

Gracias por su respuesta Louis

PD: Me gustaría mantener el func en la llamada para que usted también puede pedir mediana, mínimo ...

+0

No sé numpy, pero su 'dtype' parece tener un problema con los paréntesis .. – int3

+0

Los paréntesis no coinciden. Esto genera confusión adicional. –

+0

No entiendo tu comentario de que "te gustaría tenerlo en una línea". Cuando llamas a la función, esa será una línea. ¿Importa cuántas líneas tiene la función en sí? De todos modos, creo que tu mejor opción es usar 'defaultdict' como dicen las respuestas. – steveha

Respuesta

5

Tal vez la función que está buscando es matplotlib.mlab.rec_groupby:

import matplotlib.mlab 

data=np.array(
    [('Aaron','Digger',1), 
    ('Bill','Planter',2), 
    ('Carl','Waterer',3), 
    ('Darlene','Planter',3), 
    ('Earl','Digger',7)], 
    dtype=[('name', np.str_,8), ('job', np.str_,8), ('income', np.uint32)]) 

result=matplotlib.mlab.rec_groupby(data, ('job',), (('income',np.mean,'avg_income'),)) 

rendimientos

('Digger', 4.0) 
('Planter', 2.5) 
('Waterer', 3.0) 

matplotlib.mlab.rec_groupby devuelve un recArray:

print(result.dtype) 
# [('job', '|S7'), ('avg_income', '<f8')] 

Usted puede también estar interesado en probar pandas, que tiene incluso more versatile facilities para el manejo de group-by operations.

+0

que es exactamente lo que estaba buscando: ¡el trabajo hecho en una línea! ¡Además está devolviendo directamente una matriz! ¡Perfecto! – Louis

5

Su if k not in data_per_key.keys() podría reescribirse como if k not in data_per_key, pero puede hacer aún mejor con defaultdict. Aquí hay una versión que utiliza defaultdict para deshacerse de la comprobación de la existencia:

import collections 

def aggregate(data, key, value, func): 
    data_per_key = collections.defaultdict(list) 
    for k,v in zip(data[key], data[value]): 
     data_per_key[k].append(v) 

    return [(k,func(data_per_key[k])) for k in data_per_key.keys()] 
+3

Cambiaría la última línea a 'devolver [(k, f (v)) para k, v en data_per_key.items()]' –

+0

Esa es una buena decisión, pero estaba tratando de resaltar el asunto 'defaultdict' haciendo que el único cambio. Sin embargo, su devolución es definitivamente mejor. –

+0

gracias por el truco de defaultdict! y también para la iteración final – Louis

2

Here es una receta que emula bastante bien la funcionalidad de matlabs accumarray. Utiliza iteradores de pitones muy bien, sin embargo, en cuanto a rendimiento, apesta comparado con la implementación de matlab. Como tenía el mismo problema, había escrito una implementación usando scipy.weave.Lo puedes encontrar aquí: https://github.com/ml31415/accumarray

2

mejor flexibilidad y facilidad de lectura se obtiene con pandas:

import pandas 

data=np.array(
    [('Aaron','Digger',1), 
    ('Bill','Planter',2), 
    ('Carl','Waterer',3), 
    ('Darlene','Planter',3), 
    ('Earl','Digger',7)], 
    dtype=[('name', np.str_,8), ('job', np.str_,8), ('income', np.uint32)]) 

df = pandas.DataFrame(data) 
result = df.groupby('job').mean() 

rendimientos a:

  income 
job 
Digger  4.0 
Planter  2.5 
Waterer  3.0 

pandas trama de datos es una gran clase para trabajar, pero se puede obtenga los resultados que necesita:

result.to_records() 
result.to_dict() 
result.to_csv() 

Y así sucesivamente ...

+1

'pandas' es un orden de magnitud más lento que mi solución dada anteriormente. Ver la comparación de velocidad allí. – Michael

+0

@Michael, lo siento, en realidad no quise decir el rendimiento, soy consciente de que los pandas no son una biblioteca que apunta a un rendimiento superior, yo prefiero usar enfoques como el bincount para el rendimiento. Edité la publicación original. – caiohamamura

2

El mejor rendimiento se logra usando ndimage.mean desde scipy. Esta será dos veces más rápido que la respuesta aceptada para este pequeño conjunto de datos, y alrededor de 3,5 veces más rápido para las entradas más grandes:

from scipy import ndimage 

data=np.array(
    [('Aaron','Digger',1), 
    ('Bill','Planter',2), 
    ('Carl','Waterer',3), 
    ('Darlene','Planter',3), 
    ('Earl','Digger',7)], 
    dtype=[('name', np.str_,8), ('job', np.str_,8), ('income', np.uint32)]) 

unique = np.unique(data['job']) 
result=np.dstack([unique, ndimage.mean(data['income'], data['job'], unique)]) 

cederá a:

array([[['Digger', '4.0'], 
     ['Planter', '2.5'], 
     ['Waterer', '3.0']]], 
     dtype='|S32') 

EDIT: con bincount

(más rápido!)

Esto es aproximadamente 5 veces más rápido que la respuesta aceptada por la pequeña entrada de ejemplo, si repite los datos de 100000 veces será de alrededor de 8.5x más rápido:

unique, uniqueInd, uniqueCount = np.unique(data['job'], return_inverse=True, return_counts=True) 
means = np.bincount(uniqueInd, data['income'])/uniqueCount 
return np.dstack([unique, means]) 
Cuestiones relacionadas