питонический способ агрегирования массивов (numpy или нет)
Вопрос
Я хотел бы создать приятную функцию для объединения данных в массив (это массив записей numpy, но это ничего не меняет)
у вас есть массив данных, которые вы хотите объединить по одной оси:например, массив из dtype=[(name, (np.str_,8), (job, (np.str_,8), (income, np.uint32)]
и вы хотите иметь средний доход на одну работу
Я выполнил эту функцию, и в приведенном примере она должна вызываться как 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()]
проблема в том, что я нахожу это не очень приятным, я бы хотел, чтобы это было в одной строке:у вас есть какие-нибудь идеи?
Спасибо за ваш ответ, Луис
PS:Я хотел бы сохранить функцию в вызове, чтобы вы также могли запросить медиану, минимум...
Решение
Возможно, функция, которую вы ищете, это matplotlib.mlab.rec_groupby матплотлиб.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'),))
урожайность
('Digger', 4.0)
('Planter', 2.5)
('Waterer', 3.0)
matplotlib.mlab.rec_groupby
возвращает повторное сопоставление:
print(result.dtype)
# [('job', '|S7'), ('avg_income', '<f8')]
Вы также можете быть заинтересованы в том, чтобы проверить панды, который имеет даже более универсальные средства для обработки группировка по операциям.
Другие советы
Ваш if k not in data_per_key.keys()
может быть переписан как if k not in data_per_key
, но вы можете сделать еще лучше с defaultdict
.Вот версия, которая использует defaultdict
чтобы избавиться от проверки существования:
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()]
Здесь это рецепт, который довольно хорошо эмулирует функциональность matlabs accumarray.Он довольно хорошо использует итераторы pythons, тем не менее, с точки зрения производительности он отстой по сравнению с реализацией matlab.Поскольку у меня была та же проблема, я написал реализацию, используя scipy.weave
.Вы можете найти его здесь: https://github.com/ml31415/accumarray
Наилучшая гибкость и удобочитаемость - это использование панды:
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()
Уступает :
income
job
Digger 4.0
Planter 2.5
Waterer 3.0
Pandas DataFrame - отличный класс для работы, но вы можете вернуть свои результаты по мере необходимости:
result.to_records()
result.to_dict()
result.to_csv()
И так далее...
Наилучшая производительность достигается с помощью ndimage.иметь в виду От сципи.Это будет в два раза быстрее, чем принятый ответ для этого небольшого набора данных, и примерно в 3,5 раза быстрее для больших входных данных:
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)])
Будет уступать:
array([[['Digger', '4.0'],
['Planter', '2.5'],
['Waterer', '3.0']]],
dtype='|S32')
Редактировать:с помощью bincount (быстрее!)
Это примерно в 5 раз быстрее, чем принятый ответ для небольшого примера ввода, если вы повторите данные 100000 раз, это будет примерно в 8,5 раз быстрее:
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])
должно помочь сделать его немного красивее, более питоническим, возможно, более эффективным.Я вернусь позже, чтобы проверить твои успехи.Может быть, вы можете отредактировать функцию с учетом этого?Также смотрите следующие пару разделов.