Эквивалент tapply() R в Python Pandas

У меня есть набор данных, который содержит данные о кормлении 3 животных, состоящие из идентификаторов тегов животных (1,2,3), типа (A, B) и количества (кг) корма, даваемого при каждом «приеме пищи»:

Animal   FeedType   Amount(kg)
Animal1     A         10
Animal2     B         7
Animal3     A         4
Animal2     A         2
Animal1     B         5
Animal2     B         6
Animal3     A         2

В базе R я могу легко вывести матрицу ниже, которая имеет unique('Animal') в качестве строк, unique('FeedType') в качестве столбцов и совокупное Amount (kg) в соответствующих ячейках матрицы, используя tapply(), как показано ниже.

out <- with(mydf, tapply(Amount, list(Animal, FeedType), sum))

         A  B
Animal1 10  5
Animal2  2 13
Animal3  6 NA

Есть ли эквивалентная функциональность для кадра данных Python Pandas? Каков самый элегантный и быстрый способ добиться этого в Pandas?

P.S. Я хочу иметь возможность указать, в каком столбце, в данном случае Amount, выполнять агрегацию.

Заранее спасибо.

ИЗМЕНИТЬ:

Я пробовал оба подхода в двух ответах. Результаты производительности с моим фактическим фреймом данных Pandas из 216 347 строк и 15 столбцов:

start_time1 = timeit.default_timer()
mydf.groupby(['Animal','FeedType'])['Amount'].sum()
elapsed_groupby = timeit.default_timer() - start_time1

start_time2 = timeit.default_timer()
mydf.pivot_table(rows='Animal', cols='FeedType',values='Amount',aggfunc='sum')
elapsed_pivot = timeit.default_timer() - start_time2

print ('elapsed_groupby: ' + str(elapsed_groupby))
print ('elapsed_pivot: ' + str(elapsed_pivot))

дает:

elapsed_groupby: 10.172213
elapsed_pivot: 8.465783

Поэтому в моем случае pivot_table() работает быстрее.


person Zhubarb    schedule 03.01.2014    source источник


Ответы (2)


Подход @Zelazny7 с groupby и unstack, безусловно, хорош, но для полноты вы также можете сделать это напрямую с pivot_table (см. doc) [версия 0.13 и ниже]:

In [13]: df.pivot_table(rows='Animal', cols='FeedType', values='Amount(kg)', aggfunc='sum')
Out[13]:
FeedType   A   B
Animal
Animal1   10   5
Animal2    2  13
Animal3    6 NaN

В более новых версиях Pandas (версия 0.14 и новее) аргументы pivot_table были изменены:

In [13]: df.pivot_table(index='Animal', columns='FeedType', values='Amount(kg)', aggfunc='sum')
Out[13]:
FeedType   A   B
Animal
Animal1   10   5
Animal2    2  13
Animal3    6 NaN
person joris    schedule 03.01.2014
comment
Спасибо. Это быстрее, чем groupBy()? Я не мог понять, как указать столбец Amount в подходе groupBy(). Я чувствую, что группировка по всему фрейму данных не нужна и может вызвать проблемы с производительностью. Я хочу специально использовать df$Amount при агрегировании. - person Zhubarb; 03.01.2014
comment
Некоторые быстрые timeit результаты показывают, что групповой подход работает немного быстрее (2,44 мс против 3,28 мс при 100 циклах). - person Zelazny7; 03.01.2014
comment
@ Zelazny7 Zelazny7, я протестировал оба подхода, используя timeit, как вы предложили. pivot_table() в моем случае было быстрее, когда я добавил к своему вопросу. Но я все равно благодарен за ваш ответ. - person Zhubarb; 03.01.2014
comment
@Zhubarb Если вы используете IPython, для таймингов мне проще использовать %timeit (например, %timeit mydf.groupby(['Animal','FeedType'])['Amount'].sum()) - person joris; 03.01.2014

Сначала я прочитал в ваших данных:

In [7]: df = pd.read_clipboard(sep="\s+", index_col=False)

In [8]: df
Out[8]:
    Animal FeedType  Amount(kg)
0  Animal1        A          10
1  Animal2        B           7
2  Animal3        A           4
3  Animal2        A           2
4  Animal1        B           5
5  Animal2        B           6
6  Animal3        A           2

Затем я могу сгруппировать по двум столбцам для агрегирования:

In [9]: df.groupby(['Animal','FeedType']).sum()
Out[9]:
                  Amount(kg)
Animal  FeedType
Animal1 A                 10
        B                  5
Animal2 A                  2
        B                 13
Animal3 A                  6

Чтобы получить его в том же формате, я могу unstack dataframe:

In [10]: df.groupby(['Animal','FeedType']).sum().unstack()
Out[10]:
          Amount(kg)
FeedType           A   B
Animal
Animal1           10   5
Animal2            2  13
Animal3            6 NaN
person Zelazny7    schedule 03.01.2014
comment
Спасибо за ваш ответ! Как я могу указать: df.groupby(['Animal','FeedType']).sum() для суммирования по 'Amount' конкретно? У меня есть другие столбцы с плавающей запятой в том же фрейме данных, и меня интересуют только значения 'Amount'. - person Zhubarb; 03.01.2014
comment
Да, извините, я понял это после того, как написал свой комментарий. Но значит ли это, что я группирую по всем столбцам, которых нет в ['Animal', 'FeedType']? Я не указал это в своем вопросе, чтобы он был кратким и точным, но у меня действительно много столбцов, и я хотел бы ограничить операцию одним столбцом, если это возможно. - person Zhubarb; 03.01.2014