Pandas groupby en Python : comment grouper un DataFrame (agg, transform, exemples)
Vous avez un tableau de ventes et votre manager vous demande « le chiffre d'affaires par région ». Dans Excel, vous feriez un tableau croisé dynamique. En SQL, un GROUP BY. En Python, avec la librairie pandas, c'est la méthode groupby() qui fait le travail, et elle va beaucoup plus loin qu'un simple total.
Dans ce tutoriel, on part d'un petit DataFrame de ventes et on voit, étape par étape, comment grouper des lignes, calculer plusieurs indicateurs par groupe, garder le détail ligne par ligne et regrouper par mois. Tous les exemples ont été testés avec pandas 3. pandas est la bibliothèque de référence pour analyser des données en Python : autant bien maîtriser sa fonction la plus utilisée.
Qu'est-ce que la fonction pandas groupby ?
Cette fonction sert à regrouper les lignes d'un DataFrame qui partagent la même valeur dans une ou plusieurs colonnes, puis à calculer un résultat pour chaque groupe. C'est l'équivalent d'une requête SQL agrégée ou d'un tableau croisé dynamique dans Excel.
Une opération groupby se déroule toujours en trois temps. Par exemple, avec des ventes par région, pandas regroupe les données comme ceci :
Séparer : pandas répartit les lignes en groupes selon les valeurs de la colonne choisie.
Appliquer : une fonction est appliquée à chaque groupe : somme, moyenne, comptage...
Combiner : les résultats sont rassemblés dans un nouveau DataFrame.
Point important : l'appel df.groupby("region") seul ne calcule rien. Il renvoie un objet « en attente ». Le calcul se déclenche quand vous ajoutez une méthode comme sum() ou mean().
Exemple : le DataFrame de ventes à grouper
On travaille sur huit ventes, avec une date, une région, un vendeur, un produit et un montant :
import pandas as pd
# un DataFrame de 8 ventes, utilisé dans tous les exemples
ventes = pd.DataFrame({
"date": pd.to_datetime(["2026-01-05", "2026-01-12", "2026-01-20", "2026-02-03",
"2026-02-10", "2026-02-18", "2026-03-02", "2026-03-15"]),
"region": ["Nord", "Sud", "Nord", "Sud", "Nord", "Est", "Est", "Sud"],
"vendeur": ["Léa", "Hugo", "Léa", "Inès", "Marc", "Paul", "Paul", "Hugo"],
"produit": ["Formation", "Conseil", "Conseil", "Formation",
"Formation", "Conseil", "Formation", "Formation"],
"montant": [1200, 800, 950, 1500, 1100, 700, 1300, 900],
})Comment agréger une colonne avec groupby et sum
Pour obtenir le chiffre d'affaires par région, on indique la colonne à grouper, la colonne à calculer, puis la fonction d'agrégation :
# somme d'une colonne par groupe : un total par région
ventes.groupby("region")["montant"].sum()Résultat :
region
Est 2000
Nord 3250
Sud 3200
Name: montant, dtype: int64Les noms de régions deviennent les étiquettes des lignes du résultat. Pour changer de calcul, il suffit de remplacer sum() par une autre fonction :
mean() : moyenne (le panier moyen)
count() : nombre de valeurs non vides
size() : nombre de lignes, valeurs vides comprises
min() / max() : plus petite et plus grande valeur
nunique() : nombre de valeurs distinctes, par exemple le nombre de vendeurs par région
Grouper un DataFrame sur plusieurs colonnes
Passez une liste de colonnes pour obtenir un regroupement à plusieurs niveaux, par exemple le chiffre d'affaires par région et par produit :
# deux colonnes : une ligne par couple (région, produit)
ventes.groupby(["region", "produit"])["montant"].sum()Résultat :
region produit
Est Conseil 700
Formation 1300
Nord Conseil 950
Formation 2300
Sud Conseil 800
Formation 2400Vous préférez un tableau à double entrée, comme un tableau croisé dynamique, avec les produits en colonnes ? Ajoutez .unstack() à la fin. C'est l'objet de notre article sur les méthodes stack(), unstack(), pivot() et melt().
Appliquer plusieurs fonctions à chaque groupe avec agg()
Dans la vraie vie, on veut rarement un seul chiffre. La méthode agg() avec des agrégations nommées construit une synthèse complète en une instruction, en choisissant le nom de chaque colonne du résultat :
# une fonction d'agrégation différente pour chaque colonne
# colonnes du résultat : ca_total, panier_moyen, nb_ventes, vendeurs
synthese = ventes.groupby("region").agg(
ca_total=("montant", "sum"),
panier_moyen=("montant", "mean"),
nb_ventes=("montant", "count"),
vendeurs=("vendeur", "nunique"),
)Résultat :
ca_total panier_moyen nb_ventes vendeurs
region
Est 2000 1000.000000 2 1
Nord 3250 1083.333333 3 2
Sud 3200 1066.666667 3 2La syntaxe se lit ainsi : nom_de_la_colonne_résultat=("colonne_source", "fonction"). C'est la forme la plus lisible, et la plus facile à relire six mois plus tard.
Récupérer un DataFrame « plat » : as_index et reset_index()
Par défaut, la colonne de regroupement devient l'étiquette des lignes du résultat. C'est pratique pour lire, moins pour enchaîner avec un graphique, une fusion ou un export Excel. Deux solutions équivalentes :
# option 1 : garder "region" comme colonne
ventes.groupby("region", as_index=False)["montant"].sum()
# option 2 : la remettre en colonne après coup
ventes.groupby("region")["montant"].sum().reset_index()Résultat :
region montant
0 Est 2000
1 Nord 3250
2 Sud 3200Ce format est celui qu'il vous faut si vous voulez ensuite rattacher ces totaux à une autre table avec merge() : voir merge, join, concat : comment fusionner des DataFrames pandas.
Parcourir chaque groupe avec une boucle for
Pour comprendre ce que fait le regroupement en coulisses, rien ne vaut une boucle : à chaque tour, vous récupérez le nom du groupe et le sous-DataFrame correspondant.
# pour chaque groupe, la boucle donne son nom et ses lignes
for region, lignes in ventes.groupby("region"):
print(region, len(lignes), "ventes")
# récupérer les lignes d'un seul groupe
ventes.groupby("region").get_group("Nord")Résultat de la boucle :
Est 2 ventes
Nord 3 ventes
Sud 3 ventesLa boucle est utile pour explorer ou exporter un fichier par groupe. Pour calculer, préférez toujours les méthodes vectorisées vues plus haut, bien plus rapides.
transform() : garder le détail ligne par ligne
agg() réduit chaque groupe à une seule ligne. Mais parfois, on veut garder toutes les ventes et ajouter une information calculée sur le groupe, par exemple la part de chaque vente dans le total de sa région. C'est le rôle de transform(), qui renvoie un résultat de la même taille que le DataFrame de départ :
# transform calcule sur chaque groupe et renvoie une valeur par ligne
total_region = ventes.groupby("region")["montant"].transform("sum")
ventes["part_region"] = ventes["montant"] / total_regionRésultat (arrondi) :
region montant part_region
0 Nord 1200 0.37
1 Sud 800 0.25
2 Nord 950 0.29
3 Sud 1500 0.47
...transform() sert aussi à remplir des valeurs manquantes avec la moyenne ou la médiane de leur groupe. On le détaille dans notre article sur les valeurs manquantes dans pandas.
filter() : ne garder que certains groupes
Vous voulez uniquement les ventes des régions qui dépassent 2 500 € de chiffre d'affaires ? filter() applique une condition à chaque groupe et conserve toutes les lignes des groupes qui la respectent :
# garder uniquement les régions dont le total dépasse 2 500
ventes.groupby("region").filter(lambda g: g["montant"].sum() > 2500)Ici, la région Est (2 000 €) disparaît, les lignes Nord et Sud sont conservées. La petite fonction anonyme lambda g: ... reçoit chaque groupe sous forme de DataFrame. Si cette syntaxe vous est peu familière, lisez notre article sur les fonctions lambda.
À ne pas confondre avec le filtrage classique, qui travaille ligne par ligne : voir les différentes méthodes de filtrage avec pandas.
Regrouper les données par mois ou par trimestre
Pour un suivi mensuel du chiffre d'affaires, convertissez la date en période, puis regroupez :
# convertir la date en mois, puis faire la somme
ventes.groupby(ventes["date"].dt.to_period("M"))["montant"].sum()Résultat :
date
2026-01 2950
2026-02 3300
2026-03 2200Remplacez "M" par "Q" pour un regroupement par trimestre, ou par "Y" pour l'année. Attention : la colonne doit être au format date. Si elle a été importée comme texte, convertissez-la d'abord avec pd.to_datetime() (voir comment changer le type d'une colonne dans pandas).
Récapitulatif : quelle fonction pour quel besoin ?
Méthode | Ce qu'elle renvoie | Exemple d'usage |
sum(), mean(), count()... | Une ligne par groupe | CA par région |
agg() | Une ligne par groupe, plusieurs indicateurs | Tableau de synthèse commercial |
transform() | Autant de lignes qu'au départ | Part de chaque vente dans sa région |
filter() | Les lignes des groupes retenus | Régions à plus de 2 500 € |
size() | Nombre de lignes par groupe | Nombre de commandes par client |
5 erreurs fréquentes à éviter
1. Confondre count() et size()
count() ignore les valeurs vides (NaN), size() compte toutes les lignes. Si une vente n'a pas de montant renseigné, les deux ne donnent pas le même nombre de ventes. Choisissez en connaissance de cause.
2. Perdre les lignes dont la clé de regroupement est vide
Par défaut, les lignes dont la région est vide sont exclues sans avertissement du résultat. Vos totaux ne correspondent alors plus au total général. Pour les faire apparaître dans un groupe à part, ajoutez dropna=False :
# les lignes sans région forment leur propre groupe
ventes.groupby("region", dropna=False)["montant"].sum()3. Utiliser apply() quand une méthode native existe
apply() accepte n'importe quelle fonction, mais il est nettement plus lent que sum(), mean() ou agg(), qui sont optimisées. Sur quelques milliers de lignes, ça ne se voit pas ; sur plusieurs millions, la différence se compte en minutes. Réservez apply() aux calculs qu'aucune méthode native ne sait faire.
4. Oublier que la colonne de regroupement a changé de place
Après le regroupement, la région ne figure plus parmi les colonnes. Un ventes_par_region["region"] provoque alors une erreur KeyError. Pensez à as_index=False ou reset_index().
5. Vouloir afficher l'objet lui-même
print(ventes.groupby("region")) affiche un message du type DataFrameGroupBy object at 0x... et rien d'autre. Ce n'est pas un bug : il manque simplement la fonction de calcul.
SQL, Excel et pandas : les équivalences
Si vous venez d'Excel ou de SQL, ce tableau vous aidera à faire le lien :
Besoin | Excel | SQL | pandas (après le regroupement) |
Total par catégorie | Tableau croisé dynamique, SOMME.SI | SUM avec regroupement | .sum() |
Plusieurs indicateurs | TCD avec plusieurs valeurs | Plusieurs agrégats dans le SELECT | .agg() |
Filtrer sur un total | Filtre sur le TCD | HAVING | .filter() |
Total à côté de chaque ligne | SOMME.SI sur chaque ligne | Fonction de fenêtre SUM() OVER | .transform() |
Mémo : groupby en 10 lignes
Voici le mémo à garder sous la main, avec les usages les plus courants :
# groupby regroupe les lignes, puis calcule un résultat par groupe
g = df.groupby("col") # créer les groupes
g["val"].sum() # un total par groupe
g.agg(total=("val", "sum")) # plusieurs indicateurs, colonnes nommées
g["val"].transform("sum") # une valeur par ligne d'origine
g.filter(lambda x: len(x) > 2) # garder certains groupes seulement
g.size() # nombre de lignes par groupe
g[["a", "b"]].max() # plusieurs colonnes à la fois
df.groupby(level=0).sum() # regrouper selon l'index
df.groupby("col", dropna=False) # garder les clés vides (NaN)Pour aller plus loin
Récupérer les valeurs d'un groupe sous forme de liste : comment grouper les lignes d'un DataFrame et récupérer les valeurs en liste
Visualiser vos résultats : histogramme groupé et histogramme empilé
Un cas d'usage métier complet : Python pour le contrôle de gestion
Le regroupement de données fait partie des notions que nous pratiquons sur des données réelles dès le premier jour de notre formation Python pour l'analyse de données. La formation est éligible au CPF : voir les modalités de financement.
Questions fréquentes sur pandas groupby
Que fait groupby dans pandas ?
groupby() range les lignes d'un DataFrame en groupes selon la valeur d'une ou plusieurs colonnes, puis applique une fonction à chaque groupe (somme, moyenne, comptage...) et rassemble les résultats dans un nouveau tableau. C'est l'équivalent d'un tableau croisé dynamique Excel ou d'une requête SQL agrégée.
Comment grouper sur plusieurs colonnes ?
Passez une liste de noms de colonnes : df.groupby(["region", "produit"])["montant"].sum(). Le résultat a deux niveaux d'étiquettes, que vous pouvez aplatir avec reset_index() ou transformer en tableau à double entrée avec unstack().
Quelle différence entre agg() et transform() ?
agg() renvoie une ligne par groupe (un résumé), alors que transform() renvoie un résultat de la même longueur que le DataFrame d'origine, ce qui permet d'ajouter une colonne calculée sur le groupe à chaque ligne.
Pourquoi mon regroupement ne renvoie pas toutes les lignes ?
Les lignes dont la clé de regroupement est vide (NaN) sont exclues par défaut. Ajoutez dropna=False dans l'appel pour les conserver dans un groupe à part.
Comment renommer les colonnes après un regroupement ?
Le plus simple est d'utiliser les agrégations nommées dans agg() : df.groupby("region").agg(ca_total=("montant", "sum")). La colonne du résultat s'appelle directement ca_total.


