
regroupement de données pandas.DataFrame.groupby
¶
Une table de données pandas
est en 2 dimensions mais elle peut indiquer des sous-divisions de vos données. Par exemple, les passagers du Titanic sont divisés en femmes et hommes, en passagers de première, deuxième et troisiéme classe. On pourrait même les diviser en classe d’âge, enfants, jeunes, adultes…
Des analyses mettant en exergue ces différents groupes de personnes peuvent être intéressantes. Lors du naufrage du Titanic, valait-il mieux être une femme en première classe ou un enfant en troisième ?
On reprend nos données
import numpy as np
import pandas as pd
file = 'titanic.csv'
df = pd.read_csv(file, index_col=0)
df.head(3)
Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|
PassengerId | |||||||||||
1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S |
2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2. 3101282 | 7.9250 | NaN | S |
On va calculer des regroupements en utilisant la fonction pandas.DataFrame.groupby
, à laquelle on indique un ou plusieurs critères.
groupement par critère unique¶
Par exemple, prenons le seul critère de genre des passagers (Sex
). Cette colonne a deux valeurs: female
et male
, nous allons donc obtenir une partition de notre dataframe en deux dataframes : celle des hommes et celle des femmes, sur lesquelles nous allons pouvoir faire des analyses (moyenne…) différencées par genre.
Allons-y:
df_by_sex = df.groupby('Sex')
pandas
calcule les différentes valeurs de la colonne en question (ici Sex
), et partitionne la dataframe en autant de dataframes que de valeurs différentes.
pandas
met les regroupements dans un objet de type DataFrameGroupBy
(ici de nom df_by_sex
) qui vous donne accès à de nombreuses fonctionnalités (regardez le help pour plus de détails), nous allons voir ici très peu de choses ici.
les tailles des groupes¶
Les objets de type DataFrameGroupBy
contiennent une fonction très pratique pour récapituler les groupes : size
.
df_by_sex.size()
Sex
female 314
male 577
dtype: int64
accéder aux sous-dataframes¶
On peut aussi itérer sur un objet de type DataFrameGroupBy
afin de voir les différentes dataframes.
for name, subdf in df_by_sex:
print(f"La dataframe de clé '{name}' a {subdf.shape[0]} éléments sur les {df.shape[0]}")
La dataframe de clé 'female' a 314 éléments sur les 891
La dataframe de clé 'male' a 577 éléments sur les 891
Voilà, la fonction a bien partitionné votre dataframe en autant de dataframes que de genre des personnes.
les groupes (dictionnaire d’index par clés)¶
Vous pouvez accéder, au travers du champ groups
des objets de type DataFrameGroupBy
, au dictionnaire vous donnant les groupes d’Index
(ici deux parce male
et female
).
Chaque clé est une des valeurs possibles (donc à nouveau male
et female
), et sa valeur est la liste des index des lignes ayant cette valeur:
# df_by_sex.groups est un dictionnaire
# et voici ses clés et valeurs
for k, v in df_by_sex.groups.items():
print(f"{k}\t→ {v}")
female → Int64Index([ 2, 3, 4, 9, 10, 11, 12, 15, 16, 19,
...
867, 872, 875, 876, 880, 881, 883, 886, 888, 889],
dtype='int64', name='PassengerId', length=314)
male → Int64Index([ 1, 5, 6, 7, 8, 13, 14, 17, 18, 21,
...
874, 877, 878, 879, 882, 884, 885, 887, 890, 891],
dtype='int64', name='PassengerId', length=577)
groupement multi-critères¶
Si maintenant on s’intéresse à plusieurs colonnes ? Comment est-ce que ça pourrait se présenter à votre avis ?
La solution adoptée, c’est de passer à groupby
, non plus une seule colonne mais .. une liste de colonnes.
Le fonctionnement de groupby
dans ce cas consiste à
calculer pour chaque colonne les valeurs distinctes (comme dans le cas simple)
et en faire le produit cartésien pour obtenir les clés du groupement (incidemment, sous la forme de tuples)
Ainsi dans notre exemple si nous prenons les critères : Pclass
etSex
:
le premier critère donne trois valeurs
1
,2
et3
pour les trois classes de navigationle second donne 2 valeurs
female
etmale
et donc on va avoir 6 tuples qui serviront de clés pour le groupement : (1, ‘female’), (1, ‘male’), (2, ‘female’)…
df_by_sex_class = df.groupby(['Pclass', 'Sex'])
les tailles des groupes¶
Pour faire une synthèse on peut utiliser size()
pour récapituler les groupes; la présentation nous montre bien le produit cartésien qui a été fait
df_by_sex_class.size()
Pclass Sex
1 female 94
male 122
2 female 76
male 108
3 female 144
male 347
dtype: int64
En une seule ligne:
df.groupby(['Pclass', 'Sex']).size()
Pclass Sex
1 female 94
male 122
2 female 76
male 108
3 female 144
male 347
dtype: int64
accéder aux sous-dataframes¶
De même nous pouvons itérer sur les sous-dataframes.
# pour itérer sur les 6 catégories
for name, dataframe in df_by_sex_class:
print(f"{len(dataframe)} passagers en classe '{name[0]}' de genre '{name[1]}'")
94 passagers en classe '1' de genre 'female'
122 passagers en classe '1' de genre 'male'
76 passagers en classe '2' de genre 'female'
108 passagers en classe '2' de genre 'male'
144 passagers en classe '3' de genre 'female'
347 passagers en classe '3' de genre 'male'
Pour les curieux, une petite astuce, utile à ce stade; on pourrait avoir envie d’utiliser la méthode .head()
pour afficher chacune des sous-dataframes, en écrivant ceci
# malheureusement ceci ne marche pas !!
for name, dataframe in df_by_sex_class:
dataframe.head(1)
En fait ce qui se passe ici, c’est que la méthode .head()
renvoie un objet que le notebook sait afficher; donc quand on écrit une cellule qui ne contient que (ou dont la dernière instruction est) df.head()
, cet objet se fait afficher parce que c’est le résultat de la cellule (comme quand 40
se fait affichez quand vous évaluez une cellule avec juste 10+30
)
Mais dans le cas du for
qu’on est en train de vouloir écrire, le résultat du for est None
, et rien ne se fait afficher; il nous faut donc provoquer l’affichage (un peu comme quand on est obligé d’insérer un print()
au milieu d’une cellule); voici l’astuce pour arriver à provoquer l’affichage souhaité :
# pour que ça fonctionne il faut forcer l'affichage
# en utilisant display() qui se trouve dans le module IPython
import IPython
for name, dataframe in df_by_sex_class:
IPython.display.display(dataframe.head(1))
Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|
PassengerId | |||||||||||
2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|
PassengerId | |||||||||||
7 | 0 | 1 | McCarthy, Mr. Timothy J | male | 54.0 | 0 | 0 | 17463 | 51.8625 | E46 | S |
Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|
PassengerId | |||||||||||
10 | 1 | 2 | Nasser, Mrs. Nicholas (Adele Achem) | female | 14.0 | 1 | 0 | 237736 | 30.0708 | NaN | C |
Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|
PassengerId | |||||||||||
18 | 1 | 2 | Williams, Mr. Charles Eugene | male | NaN | 0 | 0 | 244373 | 13.0 | NaN | S |
Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|
PassengerId | |||||||||||
3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2. 3101282 | 7.925 | NaN | S |
Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|
PassengerId | |||||||||||
1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.25 | NaN | S |
À vous de jouer : calculer le groupby
avec le genre, la classe et si la personne a survécu ou non. Dans quel groupe de personnes reste-il le plus de survivants ? Et le moins ?
# votre code ici (une petite idée de correction ci-dessous)
df_by_sex_class_survived = df.groupby(['Pclass', 'Sex', 'Survived'])
df_by_sex_class_survived.size()
Pclass Sex Survived
1 female 0 3
1 91
male 0 77
1 45
2 female 0 6
1 70
male 0 91
1 17
3 female 0 72
1 72
male 0 300
1 47
dtype: int64
les groupes (dictionnaire d’index par clés)¶
Lister les clés
Les clés sont des tuples de valeurs.
df_by_sex_class_survived.groups.keys()
dict_keys([(1, 'female', 0), (1, 'female', 1), (1, 'male', 0), (1, 'male', 1), (2, 'female', 0), (2, 'female', 1), (2, 'male', 0), (2, 'male', 1), (3, 'female', 0), (3, 'female', 1), (3, 'male', 0), (3, 'male', 1)])
Les valeurs sont des listes d’index, ça me permet de retrouver les entrées dans la dataframe d’origine.
Par exemple, si nous voulons accéder aux trois seules femmes de première classe qui n’ont pas survécu. La clé est (1, 'female', 0)
.
Nous allons, cette fois, les rechercher dans la grande dataframe. Remarquez que bien sûr ici on utilise loc
puisque nous sommes uniquement dans l’espace des index.
df.loc[df_by_sex_class_survived.groups[(1, 'female', 0)]]
Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|
PassengerId | |||||||||||
178 | 0 | 1 | Isham, Miss. Ann Elizabeth | female | 50.0 | 0 | 0 | PC 17595 | 28.7125 | C49 | C |
298 | 0 | 1 | Allison, Miss. Helen Loraine | female | 2.0 | 1 | 2 | 113781 | 151.5500 | C22 C26 | S |
499 | 0 | 1 | Allison, Mrs. Hudson J C (Bessie Waldo Daniels) | female | 25.0 | 1 | 2 | 113781 | 151.5500 | C22 C26 | S |
découper des intervalles de valeurs dans une colonne¶
Parfois, nous sommes intéressés, non pas par les différentes valeurs d’une colonne (qui seraient trop nombreuses) mais par des intervalles de ces valeurs.
Prenons par exemple la colonne des âges. Si nous faisons un groupement brutalement sur la colonne Age
, nous obtenons 88 âges différents, ce qui ne nous apporte pas une information intéressante.
Par contre ça devient plus intéressant si on raisonne par classes d’âges, par exemple les enfants, jeunes, adultes et les plus de 55 ans.
‘enfant’ disons entre 0 et 12 ans
‘jeune’ disons entre 12 et 19 ans
‘adulte’ disons entre 19 et 55 ans
‘+55’ et les personnes de plus de 55 ans
Nous aimerions donc avoir une colonne dans notre dataframe avec ces labels pour compléter les âges.
La fonction pandas.cut
, appliquée à une colonne de votre dataframe, va vous générer une telle colonne, et vous pouvez donner des labels aux intervalles:
Nous allons rajouter la colonne à la dataframe. Sachant que les colonnes d’une dataframe sont les clés d’un dictionnaire, pour ajouter une colonne à votre dataframe, il faut faire comme pour les dict
en Python.
# 'bin' en anglais signifie corbeille ou panier
# c'est comme si on mettait les gens dans 4 paniers
df['age-periode'] = pd.cut(df['Age'], bins=[0, 12, 19, 55, 100])
Je montre les 6 premières lignes des 3 dernières colonnes de la dataframe:
# on a rajouté une colonne
df[df.columns[-3:]].head(6)
Cabin | Embarked | age-periode | |
---|---|---|---|
PassengerId | |||
1 | NaN | S | (19.0, 55.0] |
2 | C85 | C | (19.0, 55.0] |
3 | NaN | S | (19.0, 55.0] |
4 | C123 | S | (19.0, 55.0] |
5 | NaN | S | (19.0, 55.0] |
6 | NaN | Q | NaN |
Je donne des noms aux périodes d’âge (ici on va rajouter encore une colonne)
df['name-age-periode'] = pd.cut(df['Age'], bins=[0, 12, 19, 55, 100],
labels=['children', ' young', 'adult', 'old'])
df[df.columns[-3:]].head(6)
Embarked | age-periode | name-age-periode | |
---|---|---|---|
PassengerId | |||
1 | S | (19.0, 55.0] | adult |
2 | C | (19.0, 55.0] | adult |
3 | S | (19.0, 55.0] | adult |
4 | S | (19.0, 55.0] | adult |
5 | S | (19.0, 55.0] | adult |
6 | Q | NaN | NaN |
Et maintenant nous pouvons utiliser cette colonne dans des groupby
df.groupby(['name-age-periode']).size()
name-age-periode
children 69
young 95
adult 510
old 40
dtype: int64
# etc...
df.groupby(['name-age-periode', 'Survived']).size()
name-age-periode Survived
children 0 29
1 40
young 0 56
1 39
adult 0 311
1 199
old 0 28
1 12
dtype: int64
# etc..
df.groupby(['Pclass', 'Sex', 'Survived', ]).size()
Pclass Sex Survived
1 female 0 3
1 91
male 0 77
1 45
2 female 0 6
1 70
male 0 91
1 17
3 female 0 72
1 72
male 0 300
1 47
dtype: int64
Vous avez désormais une petite idée de l’utilisation de la fonction groupby
pour des recherches multi-critères sur une table de données.