Pandy groupby: Jak uzyskać związek strun

122

Mam taką ramkę danych:

   A         B       C
0  1  0.749065    This
1  2  0.301084      is
2  3  0.463468       a
3  4  0.643961  random
4  1  0.866521  string
5  2  0.120737       !

Powołanie

In [10]: print df.groupby("A")["B"].sum()

wróci

A
1    1.615586
2    0.421821
3    0.463468
4    0.643961

Teraz chciałbym zrobić „to samo” dla kolumny „C”. Ponieważ ta kolumna zawiera ciągi, sum () nie działa (chociaż możesz pomyśleć, że połączy łańcuchy). To, co naprawdę chciałbym zobaczyć, to lista lub zestaw ciągów dla każdej grupy, tj

A
1    {This, string}
2    {is, !}
3    {a}
4    {random}

Próbowałem znaleźć sposób, aby to zrobić.

Series.unique () ( http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.unique.html ) nie działa, chociaż

df.groupby("A")["B"]

jest

pandas.core.groupby.SeriesGroupBy object

więc miałem nadzieję, że jakakolwiek metoda serii zadziała. Jakieś pomysły?

Anne
źródło

Odpowiedzi:

178
In [4]: df = read_csv(StringIO(data),sep='\s+')

In [5]: df
Out[5]: 
   A         B       C
0  1  0.749065    This
1  2  0.301084      is
2  3  0.463468       a
3  4  0.643961  random
4  1  0.866521  string
5  2  0.120737       !

In [6]: df.dtypes
Out[6]: 
A      int64
B    float64
C     object
dtype: object

Kiedy stosujesz własną funkcję, nie ma automatycznego wykluczania kolumn nienumerycznych. Jest to jednak wolniejsze niż zastosowanie .sum()dogroupby

In [8]: df.groupby('A').apply(lambda x: x.sum())
Out[8]: 
   A         B           C
A                         
1  2  1.615586  Thisstring
2  4  0.421821         is!
3  3  0.463468           a
4  4  0.643961      random

sum domyślnie łączy się

In [9]: df.groupby('A')['C'].apply(lambda x: x.sum())
Out[9]: 
A
1    Thisstring
2           is!
3             a
4        random
dtype: object

Możesz robić prawie wszystko, co chcesz

In [11]: df.groupby('A')['C'].apply(lambda x: "{%s}" % ', '.join(x))
Out[11]: 
A
1    {This, string}
2           {is, !}
3               {a}
4          {random}
dtype: object

Robię to na całej klatce, jednej grupie naraz. Kluczem jest zwrócenie plikuSeries

def f(x):
     return Series(dict(A = x['A'].sum(), 
                        B = x['B'].sum(), 
                        C = "{%s}" % ', '.join(x['C'])))

In [14]: df.groupby('A').apply(f)
Out[14]: 
   A         B               C
A                             
1  2  1.615586  {This, string}
2  4  0.421821         {is, !}
3  3  0.463468             {a}
4  4  0.643961        {random}
Jeff
źródło
Wygląda na to, że operacje te są teraz wektoryzowane, co eliminuje potrzebę applyi lambdas. Przyszedłem tutaj, zastanawiając się, dlaczego pandaswłaściwie konkatuje i nie zwraca błędu podczas sumowania ciągów.
NelsonGon
1
Jeśli próbujesz połączyć łańcuchy i dodać znak pomiędzy nimi, rozwiązanie .agg zalecane przez @voithos poniżej jest znacznie szybsze niż zalecane tutaj .apply. W moich testach robiłem się 5-10x szybciej.
Doubledown
70

Możesz użyć tej applymetody, aby zastosować dowolną funkcję do zgrupowanych danych. Więc jeśli chcesz zestaw, aplikuj set. Jeśli chcesz otrzymać listę, aplikuj list.

>>> d
   A       B
0  1    This
1  2      is
2  3       a
3  4  random
4  1  string
5  2       !
>>> d.groupby('A')['B'].apply(list)
A
1    [This, string]
2           [is, !]
3               [a]
4          [random]
dtype: object

Jeśli chcesz czegoś innego, po prostu napisz funkcję, która robi to, co chcesz, a potem applyto.

BrenBarn
źródło
Działa dobrze, ale brakuje kolumny A.
Vineesh TP
@VineeshTP: Kolumna A została użyta jako kolumna grupująca, więc znajduje się w indeksie, jak widać w przykładzie. Możesz go odzyskać jako kolumnę, używając .reset_index().
BrenBarn
30

Możesz użyć funkcji aggregate(lub agg) do konkatenacji wartości. (Nieprzetestowany kod)

df.groupby('A')['B'].agg(lambda col: ''.join(col))
voithos
źródło
To naprawdę działa. Niesamowity. Jak @voithos wspomniał o „nietestowanym”, nie byłem zbyt optymistyczny. Bit Testowałem jego wersję jako wpis w słowniku Agg i działała zgodnie z przeznaczeniem: .agg ({'tp': 'sum', 'BaseWgt': 'max', 'TP_short': lambda col: ',' .join (col)}) Made my day
matthhias
2
Jeśli próbujesz połączyć ciągi znaków razem z jakimś rodzajem separatora, zauważyłem, że ta sugestia .agg jest znacznie szybsza niż .apply. W przypadku zestawu danych obejmującego ponad 600 tys. Ciągów tekstowych otrzymałem identyczne wyniki 5–10 razy szybciej.
Doubledown
14

Możesz spróbować tego:

df.groupby('A').agg({'B':'sum','C':'-'.join})
user3241146
źródło
2
Z recenzji: czy możesz dodać więcej wyjaśnień do swojej odpowiedzi?
toti08
1
Grupowanie jest stosowane w kolumnie `` A '', a za pomocą funkcji ag można użyć różnych funkcji w różnych kolumnach, na przykład zsumować elementy w kolumnie
``
8

prostym rozwiązaniem byłoby:

>>> df.groupby(['A','B']).c.unique().reset_index()
UżytkownikRRR
źródło
to powinna być właściwa odpowiedź. dostajesz czystą odpowiedź. wielkie dzięki!
imsrgadich
Jeśli ktoś jest zainteresowany połączeniem zawartości listy w string df.groupby(['A','B']).c.unique().apply(lambda x: ';'.join(x)).reset_index()
Vivek-Ananth
8

Nazwane agregacje z pandas >= 0.25.0

Od wersji 0.25.0 pandy nazwaliśmy agregacje, w których możemy grupować, agregować i jednocześnie przypisywać nowe nazwy do naszych kolumn. W ten sposób nie otrzymamy kolumn MultiIndex, a nazwy kolumn mają większy sens, biorąc pod uwagę zawarte w nich dane:


agregować i uzyskać listę ciągów

grp = df.groupby('A').agg(B_sum=('B','sum'),
                          C=('C', list)).reset_index()

print(grp)
   A     B_sum               C
0  1  1.615586  [This, string]
1  2  0.421821         [is, !]
2  3  0.463468             [a]
3  4  0.643961        [random]

agregować i łączyć w ciągi

grp = df.groupby('A').agg(B_sum=('B','sum'),
                          C=('C', ', '.join)).reset_index()

print(grp)
   A     B_sum             C
0  1  1.615586  This, string
1  2  0.421821         is, !
2  3  0.463468             a
3  4  0.643961        random
Erfan
źródło
6

Jeśli chcesz nadpisać kolumnę B w ramce danych, powinno to zadziałać:

    df = df.groupby('A',as_index=False).agg(lambda x:'\n'.join(x))
Amit
źródło
2

Zgodnie z dobrą odpowiedzią @ Erfana, w większości przypadków w analizie zagregowanych wartości potrzebujesz unikalnych możliwych kombinacji tych istniejących wartości znaków:

unique_chars = lambda x: ', '.join(x.unique())
(df
 .groupby(['A'])
 .agg({'C': unique_chars}))
Paul Rougieux
źródło