Jak utworzyć nową kolumnę na podstawie danych wyjściowych pandy groupby (). Sum ()?

84

Próbuję utworzyć nową kolumnę z groupbyobliczeń. W poniższym kodzie otrzymuję poprawne obliczone wartości dla każdej daty (patrz grupa poniżej), ale kiedy próbuję utworzyć nową kolumnę ( df['Data4']) za jej pomocą, otrzymuję NaN. Więc próbuję utworzyć nową kolumnę w ramce danych z sumą Data3wszystkich dat i zastosować ją do każdego wiersza dat. Na przykład 2015-05-08 znajduje się w 2 wierszach (łącznie 50 + 5 = 55) iw tej nowej kolumnie chciałbym mieć 55 w obu wierszach.

import pandas as pd
import numpy as np
from pandas import DataFrame

df = pd.DataFrame({
    'Date' : ['2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05', '2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05'], 
    'Sym'  : ['aapl', 'aapl', 'aapl', 'aapl', 'aaww', 'aaww', 'aaww', 'aaww'], 
    'Data2': [11, 8, 10, 15, 110, 60, 100, 40],
    'Data3': [5, 8, 6, 1, 50, 100, 60, 120]
})

group = df['Data3'].groupby(df['Date']).sum()

df['Data4'] = group
fe ner
źródło

Odpowiedzi:

192

Chcesz użyć transformtej opcji, zwróci serię z indeksem wyrównanym do pliku df, dzięki czemu możesz dodać ją jako nową kolumnę:

In [74]:

df = pd.DataFrame({'Date': ['2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05', '2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05'], 'Sym': ['aapl', 'aapl', 'aapl', 'aapl', 'aaww', 'aaww', 'aaww', 'aaww'], 'Data2': [11, 8, 10, 15, 110, 60, 100, 40],'Data3': [5, 8, 6, 1, 50, 100, 60, 120]})
​
df['Data4'] = df['Data3'].groupby(df['Date']).transform('sum')
df
Out[74]:
   Data2  Data3        Date   Sym  Data4
0     11      5  2015-05-08  aapl     55
1      8      8  2015-05-07  aapl    108
2     10      6  2015-05-06  aapl     66
3     15      1  2015-05-05  aapl    121
4    110     50  2015-05-08  aaww     55
5     60    100  2015-05-07  aaww    108
6    100     60  2015-05-06  aaww     66
7     40    120  2015-05-05  aaww    121
EdChum
źródło
Co się stanie, jeśli mamy drugą grupę, tak jak tutaj: stackoverflow.com/a/40067099/281545
Mr_and_Mrs_D
@Mr_and_Mrs_D musiałbyś zresetować indeks i wykonać lewe scalenie we wspólnych kolumnach w takim przypadku, aby dodać kolumnę z powrotem
EdChum
10
Alternatywnie można użyć df.groupby('Date')['Data3'].transform('sum')(co wydaje mi się nieco łatwiejsze do zapamiętania).
Cleb
42

Jak utworzyć nową kolumnę za pomocą Groupby (). Sum ()?

Są dwa sposoby - jeden prosty, a drugi nieco bardziej interesujący.


Ulubiony przez wszystkich: GroupBy.transform()z'sum'

Odpowiedź @Ed Chum może być nieco uproszczona. Zadzwoń DataFrame.groupbyzamiast Series.groupby. Powoduje to prostszą składnię.

# The setup.
df[['Date', 'Data3']]

         Date  Data3
0  2015-05-08      5
1  2015-05-07      8
2  2015-05-06      6
3  2015-05-05      1
4  2015-05-08     50
5  2015-05-07    100
6  2015-05-06     60
7  2015-05-05    120

df.groupby('Date')['Data3'].transform('sum')

0     55
1    108
2     66
3    121
4     55
5    108
6     66
7    121
Name: Data3, dtype: int64 

To odrobinę szybciej

df2 = pd.concat([df] * 12345)

%timeit df2['Data3'].groupby(df['Date']).transform('sum')
%timeit df2.groupby('Date')['Data3'].transform('sum')

10.4 ms ± 367 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
8.58 ms ± 559 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Niekonwencjonalne, ale warte uwagi: GroupBy.sum()+Series.map()

Natknąłem się na interesującą osobliwość w API. Z tego, co mówię, możesz to odtworzyć na dowolnej większej wersji powyżej 0.20 (testowałem to na 0.23 i 0.24). Wygląda na to, że konsekwentnie możesz skrócić o kilka milisekund czasu, który zajmuje, transformjeśli zamiast tego użyjesz bezpośredniej funkcji GroupByi nadasz ją za pomocą map:

df.Date.map(df.groupby('Date')['Data3'].sum())

0     55
1    108
2     66
3    121
4     55
5    108
6     66
7    121
Name: Date, dtype: int64

Porównać z

df.groupby('Date')['Data3'].transform('sum')

0     55
1    108
2     66
3    121
4     55
5    108
6     66
7    121
Name: Data3, dtype: int64

Moje badania pokazują, że mapjest to nieco szybciej, jeśli można sobie pozwolić, aby skorzystać z bezpośredniej GroupByfunkcji (takich jak mean, min, max, first, etc). W większości ogólnych sytuacji jest mniej więcej szybszy do około 200 tysięcy rekordów. Po tym wydajność naprawdę zależy od danych.

(Po lewej: v0.23, po prawej: v0.24)

Dobra alternatywa, którą warto znać, i lepiej, jeśli masz mniejsze ramki z mniejszą liczbą grup. . . ale polecam transformjako pierwszy wybór. I tak pomyślałem, że warto się tym podzielić.

Kod porównawczy, w celach informacyjnych:

import perfplot

perfplot.show(
    setup=lambda n: pd.DataFrame({'A': np.random.choice(n//10, n), 'B': np.ones(n)}),
    kernels=[
        lambda df: df.groupby('A')['B'].transform('sum'),
        lambda df:  df.A.map(df.groupby('A')['B'].sum()),
    ],
    labels=['GroupBy.transform', 'GroupBy.sum + map'],
    n_range=[2**k for k in range(5, 20)],
    xlabel='N',
    logy=True,
    logx=True
)
cs95
źródło
1
Dobrze wiedzieć! Czy mógłbyś uwzględnić (przynajmniej w przyszłych perfplots) numery wersji? Różnica w wydajności jest interesująca, ale są to w końcu szczegóły implementacji, które mogą zostać rozwiązane w przyszłości. Zwłaszcza jeśli programiści zwrócą uwagę na twoje posty.
jpp
@jpp yup to fair! Dodane wersje. Zostało to przetestowane na 0.23, ale uważam, że różnica jest widoczna, jeśli masz jakąkolwiek wersję powyżej 0.20.
cs95