Pandas groupby: Comment obtenir une union de chaînes

122

J'ai un dataframe comme celui-ci:

   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       !

Appel

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

reviendra

A
1    1.615586
2    0.421821
3    0.463468
4    0.643961

Maintenant, je voudrais faire "la même chose" pour la colonne "C". Étant donné que cette colonne contient des chaînes, sum () ne fonctionne pas (bien que vous puissiez penser que cela concaténerait les chaînes). Ce que j'aimerais vraiment voir, c'est une liste ou un ensemble de chaînes pour chaque groupe, c'est-à-dire

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

J'ai essayé de trouver des moyens de le faire.

Series.unique () ( http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.unique.html ) ne fonctionne pas, bien que

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

est un

pandas.core.groupby.SeriesGroupBy object

donc j'espérais que n'importe quelle méthode de série fonctionnerait. Des idées?

Anne
la source

Réponses:

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

Lorsque vous appliquez votre propre fonction, il n'y a pas d'exclusion automatique des colonnes non numériques. Ceci est plus lent, cependant, que l'application de .sum()lagroupby

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 par défaut concatène

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

Tu peux faire à peu près ce que tu veux

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

Faire cela sur une image entière, un groupe à la fois. La clé est de retourner unSeries

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
la source
Il semble que ces opérations soient maintenant vectorisées, supprimant ainsi le besoin de applyet lambdas. Je suis venu ici en me demandant pourquoi en pandasfait concat et ne pas renvoyer une erreur sur la sommation des chaînes.
NelsonGon
1
Si vous essayez de concaténer des chaînes et d'ajouter un caractère entre les deux, la solution .agg recommandée par @voithos ci-dessous est beaucoup plus rapide que la .apply recommandée ici. Lors de mes tests, je devenais 5 à 10 fois plus rapide.
Doubledown le
70

Vous pouvez utiliser la applyméthode pour appliquer une fonction arbitraire aux données groupées. Donc, si vous voulez un ensemble, postulez set. Si vous voulez une liste, postulez 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

Si vous voulez autre chose, écrivez simplement une fonction qui fait ce que vous voulez et ensuite applycela.

BrenBarn
la source
Fonctionne bien, mais la colonne A est manquante.
Vineesh TP
@VineeshTP: La colonne A a été utilisée comme colonne de regroupement, elle est donc dans l'index, comme vous pouvez le voir dans l'exemple. Vous pouvez le récupérer sous forme de colonne en utilisant .reset_index().
BrenBarn
30

Vous pourrez peut-être utiliser la fonction aggregate(ou agg) pour concaténer les valeurs. (Code non testé)

df.groupby('A')['B'].agg(lambda col: ''.join(col))
voithos
la source
Ça marche vraiment. Incroyable. Comme @voithos l'a mentionné "non testé", je n'étais pas très optimiste. J'ai testé sa version en tant qu'entrée dans un dictionnaire agg et cela a fonctionné comme prévu: .agg ({'tp': 'sum', 'BaseWgt': 'max', 'TP_short': lambda col: ',' .join (col)}) Made my day
matthhias
2
Si vous essayez de concater des chaînes avec un certain type de séparateur, j'ai trouvé que cette suggestion .agg était beaucoup plus rapide que .apply. Pour un ensemble de données de plus de 600 000 chaînes de texte, j'ai obtenu des résultats identiques 5 à 10 fois plus rapidement.
Doubledown le
14

Vous pouvez essayer ceci:

df.groupby('A').agg({'B':'sum','C':'-'.join})
user3241146
la source
2
De l'avis: pourriez-vous s'il vous plaît ajouter plus d'explications à votre réponse?
toti08
1
Groupby est appliqué sur la colonne 'A' et avec la fonction agg, je pourrais utiliser différentes fonctions sur différentes colonnes, dire additionner les éléments de la colonne 'C', concaténer les éléments de la colonne 'C' tout en insérant un '-' entre les mots
user3241146
8

une solution simple serait:

>>> df.groupby(['A','B']).c.unique().reset_index()
UserYmY
la source
cela devrait être la bonne réponse. vous permet de répondre proprement. Merci beaucoup!
imsrgadich
Si, au cas où quelqu'un serait intéressé à joindre le contenu de la liste dans une chaîne df.groupby(['A','B']).c.unique().apply(lambda x: ';'.join(x)).reset_index()
Vivek-Ananth
8

Agrégations nommées avec pandas >= 0.25.0

Depuis la version 0.25.0 de pandas, nous avons nommé des agrégations où nous pouvons grouper, agréger et en même temps attribuer de nouveaux noms à nos colonnes. De cette façon, nous n'obtiendrons pas les colonnes MultiIndex, et les noms de colonne ont plus de sens étant donné les données qu'ils contiennent:


agréger et obtenir une liste de chaînes

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]

agréger et joindre les chaînes

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
la source
6

Si vous souhaitez écraser la colonne B dans le dataframe, cela devrait fonctionner:

    df = df.groupby('A',as_index=False).agg(lambda x:'\n'.join(x))
Amit
la source
2

Suite à la bonne réponse de @ Erfan, la plupart du temps, dans une analyse de valeurs agrégées, vous voulez les combinaisons uniques possibles de ces valeurs de caractères existantes:

unique_chars = lambda x: ', '.join(x.unique())
(df
 .groupby(['A'])
 .agg({'C': unique_chars}))
Paul Rougieux
la source