80 votes

Comptage des valeurs de Groupby sur le dataframe pandas

J'ai le cadre de données suivant :

df = pd.DataFrame([
    (1, 1, 'term1'),
    (1, 2, 'term2'),
    (1, 1, 'term1'),
    (1, 1, 'term2'),
    (2, 2, 'term3'),
    (2, 3, 'term1'),
    (2, 2, 'term1')
], columns=['id', 'group', 'term'])

Je veux les regrouper par id y group et calculer le nombre de chaque terme pour cette paire id, groupe.

Au final, je vais obtenir quelque chose comme ça :

enter image description here

J'ai pu obtenir ce que je voulais en bouclant sur toutes les lignes avec df.iterrows() et de créer un nouveau cadre de données, mais cela est clairement inefficace. (Si cela peut aider, je connais la liste de tous les termes à l'avance et il y en a ~10).

Il semble que je doive regrouper par et ensuite compter les valeurs, donc j'ai essayé cela avec df.groupby(['id', 'group']).value_counts() ce qui ne fonctionne pas car valeur_comptes opère sur la série groupby et non sur un dataframe.

Comment puis-je y parvenir sans faire de boucle ?

139voto

piRSquared Points 159

J'utilise groupby y size

df.groupby(['id', 'group', 'term']).size().unstack(fill_value=0)

enter image description here


Timing

enter image description here

1 000 000 de rangs

df = pd.DataFrame(dict(id=np.random.choice(100, 1000000),
                       group=np.random.choice(20, 1000000),
                       term=np.random.choice(10, 1000000)))

enter image description here

0 votes

@jezrael thx, size est également plus rapide. crosstab est étrangement inefficace

0 votes

Et je suis surpris que crosstab est tellement paresseux ;)

0 votes

@jezrael, crosstab utilise pivot_table en interne... ;)

24voto

MaxU Points 5284

En utilisant tableau_pivot() méthode :

In [22]: df.pivot_table(index=['id','group'], columns='term', aggfunc='size', fill_value=0)
Out[22]:
term      term1  term2  term3
id group
1  1          2      1      0
   2          0      1      0
2  2          1      0      1
   3          1      0      0

Timing contre 700K rangs DF :

In [24]: df = pd.concat([df] * 10**5, ignore_index=True)

In [25]: df.shape
Out[25]: (700000, 3)

In [3]: %timeit df.groupby(['id', 'group', 'term'])['term'].size().unstack(fill_value=0)
1 loop, best of 3: 226 ms per loop

In [4]: %timeit df.pivot_table(index=['id','group'], columns='term', aggfunc='size', fill_value=0)
1 loop, best of 3: 236 ms per loop

In [5]: %timeit pd.crosstab([df.id, df.group], df.term)
1 loop, best of 3: 355 ms per loop

In [6]: %timeit df.groupby(['id','group','term'])['term'].size().unstack().fillna(0).astype(int)
1 loop, best of 3: 232 ms per loop

In [7]: %timeit df.groupby(['id', 'group', 'term']).size().unstack(fill_value=0)
1 loop, best of 3: 231 ms per loop

Timing contre 7M rangs DF :

In [9]: df = pd.concat([df] * 10, ignore_index=True)

In [10]: df.shape
Out[10]: (7000000, 3)

In [11]: %timeit df.groupby(['id', 'group', 'term'])['term'].size().unstack(fill_value=0)
1 loop, best of 3: 2.27 s per loop

In [12]: %timeit df.pivot_table(index=['id','group'], columns='term', aggfunc='size', fill_value=0)
1 loop, best of 3: 2.3 s per loop

In [13]: %timeit pd.crosstab([df.id, df.group], df.term)
1 loop, best of 3: 3.37 s per loop

In [14]: %timeit df.groupby(['id','group','term'])['term'].size().unstack().fillna(0).astype(int)
1 loop, best of 3: 2.28 s per loop

In [15]: %timeit df.groupby(['id', 'group', 'term']).size().unstack(fill_value=0)
1 loop, best of 3: 1.89 s per loop

1 votes

J'essayais juste de mettre à jour les timings avec un échantillon plus grand :-)

0 votes

Wow ! le pivot semble tout aussi efficace à plus grande échelle. Je vais devoir m'en souvenir. Je te donnerais bien +1 mais je l'ai déjà fait il y a un moment.

0 votes

Alors size était l'alias que nous avons oublié aquí :)

21voto

A.Kot Points 3343

Au lieu de vous souvenir de longues solutions, que diriez-vous de celle que les pandas ont intégrée pour vous :

df.groupby(['id', 'group', 'term']).count()

13voto

jezrael Points 290608

Vous pouvez utiliser crosstab :

print (pd.crosstab([df.id, df.group], df.term))
term      term1  term2  term3
id group                     
1  1          2      1      0
   2          0      1      0
2  2          1      0      1
   3          1      0      0

Une autre solution avec groupby avec agrégation size le remodelage par unstack :

df.groupby(['id', 'group', 'term'])['term'].size().unstack(fill_value=0)

term      term1  term2  term3
id group                     
1  1          2      1      0
   2          0      1      0
2  2          1      0      1
   3          1      0      0

Horaires :

df = pd.concat([df]*10000).reset_index(drop=True)

In [48]: %timeit (df.groupby(['id', 'group', 'term']).size().unstack(fill_value=0))
100 loops, best of 3: 12.4 ms per loop

In [49]: %timeit (df.groupby(['id', 'group', 'term'])['term'].size().unstack(fill_value=0))
100 loops, best of 3: 12.2 ms per loop

1 votes

Wow wow wow, tu es incroyable. Et cela ne vous a pris que 3 minutes (le même temps qu'il m'a fallu pour écrire une boucle, et moins de temps qu'il m'a fallu pour écrire cette question). J'apprécierais vraiment si vous pouviez écrire quelques explications sur le pourquoi de ce fonctionnement, mais je serai probablement capable de le comprendre par moi-même en quelques minutes.

0 votes

Dans votre cas crosstab est meilleur que pivot_table car la fonction d'agrégation par défaut est len (c'est la même chose que size ) et je pense que c'est aussi une solution plus rapide. Crosstab utiliser le premier argument comme index y second de colonnes. Donnez-moi un temps, j'essaie d'ajouter des timings.

0 votes

Mais je pense qu'il est préférable d'expliquer dans docs .

0voto

Barth Points 11

Si vous voulez utiliser value_counts vous pouvez l'utiliser sur une série donnée, et recourir à ce qui suit :

df.groupby(["id", "group"])["term"].value_counts().unstack(fill_value=0)

ou d'une manière équivalente, en utilisant le .agg méthode :

df.groupby(["id", "group"]).agg({"term": "value_counts"}).unstack(fill_value=0)

Une autre option consiste à utiliser directement value_counts sur le DataFrame lui-même sans avoir recours à groupby :

df.value_counts().unstack(fill_value=0)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X