maneira Python para matrizes de agregados (numpy ou não)
Pergunta
Gostaria de fazer uma função agradável para agregar dados entre um conjunto (é um array de registro numpy, mas isso não muda nada)
Você tem uma matriz de dados que deseja agregada entre um eixo: por exemplo, uma série de dtype=[(name, (np.str_,8), (job, (np.str_,8), (income, np.uint32)]
e você quer ter a renda média por posto de trabalho
Eu fiz esta função, e no exemplo que deve ser chamado 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()]
O problema é que eu acho que não muito bom eu gostaria de tê-lo em uma linha:? Você tem alguma ideias
Obrigado por sua resposta Louis
PS: Eu gostaria de manter o func na chamada para que você também pode pedir mediana, mínimo ...
Solução
Talvez a função que você está procurando é 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'),))
rendimentos
('Digger', 4.0)
('Planter', 2.5)
('Waterer', 3.0)
matplotlib.mlab.rec_groupby
retorna um recarray:
print(result.dtype)
# [('job', '|S7'), ('avg_income', '<f8')]
Você pode também estar interessado em verificar para fora pandas , que tem mesmo more versátil instalações para lidar com grupo de operações .
Outras dicas
Seu if k not in data_per_key.keys()
poderia ser reescrita como if k not in data_per_key
, mas você pode fazer ainda melhor com defaultdict
. Aqui está uma versão que usa defaultdict
para se livrar do cheque existência:
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()]
Aqui é uma receita que emula a funcionalidade da matlabs accumarray muito bem. Ele usa jibóias iterators muito bem, no entanto, em termos de performance que suga em comparação com a implementação Matlab. Como eu tive o mesmo problema, eu tinha escrito uma implementação usando scipy.weave
. Você pode encontrá-lo aqui: https://github.com/ml31415/accumarray
Melhor flexibilidade e legibilidade é começar usando 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()
Os rendimentos para:
income
job
Digger 4.0
Planter 2.5
Waterer 3.0
Pandas trama de dados é uma grande classe para trabalhar, mas você pode obter de volta os resultados que você precisa:
result.to_records()
result.to_dict()
result.to_csv()
E assim por diante ...
Melhor desempenho é conseguido usando ndimage.mean de scipy . Este será duas vezes mais rápido do que resposta aceite para este pequeno conjunto de dados, e cerca de 3,5 vezes mais rápido para as entradas maiores:
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)])
Será que ceder a:
array([[['Digger', '4.0'],
['Planter', '2.5'],
['Waterer', '3.0']]],
dtype='|S32')
EDIT: com bincount (! Mais rápido)
Este é cerca de 5x mais rápido do que resposta aceita para o exemplo de entrada pequena, se você repetir os dados 100000 vezes será em torno de 8,5x mais 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])
deve ajudar a torná-lo um pouco mais bonito, mais Python, mais eficiente, possivelmente. Vou voltar mais tarde para verificar o seu progresso. Talvez você pode editar a função com isso em mente? ver também o próximo par de seções.