Regrouper des données par clé (faire un « group by ») est une opération courante : compter des occurrences, agréger des montants par catégorie, calculer des moyennes par groupe, etc. En Python, il existe plusieurs façons de procéder selon la taille des données, le besoin d’agrégation et vos dépendances.
Dans cet article :
- Groupement simple avec
defaultdict
- Groupement en flux avec
itertools.groupby
- Comptages avec
Counter
- Multi‑clés et agrégations personnalisées
- « Group by » haut niveau avec
pandas
- Pièges et bonnes pratiques
Jeu de données d’exemple
Nous utiliserons une petite liste de ventes :
ventes = [
{"ville": "Paris", "produit": "Livre", "qte": 2, "prix": 12.5},
{"ville": "Lyon", "produit": "Stylo", "qte": 5, "prix": 1.2},
{"ville": "Paris", "produit": "Stylo", "qte": 3, "prix": 1.2},
{"ville": "Nantes", "produit": "Livre", "qte": 1, "prix": 12.5},
{"ville": "Paris", "produit": "Cahier", "qte": 4, "prix": 3.0},
{"ville": "Lyon", "produit": "Livre", "qte": 2, "prix": 12.5},
]
1) Groupement simple avec collections.defaultdict
La manière la plus directe pour regrouper des éléments par clé est d’utiliser defaultdict(list)
:
from collections import defaultdict
par_ville = defaultdict(list)
for v in ventes:
par_ville[v["ville"]].append(v)
# Accès
print(par_ville["Paris"]) # -> liste des ventes de Paris
- Avantages : simple, lisible, ne nécessite pas de tri préalable.
- Inconvénients : stocke toutes les lignes en mémoire dans des listes.
Variation avec setdefault
(si vous ne voulez pas importer defaultdict
) :
groupes = {}
for v in ventes:
groupes.setdefault(v["ville"], []).append(v)
2) Groupement avec itertools.groupby
itertools.groupby
groupe les éléments consécutifs ayant la même clé. Il exige que les données soient triées par la clé de groupement, sinon les groupes seront fragmentés.
from itertools import groupby
from operator import itemgetter
# Trier d'abord par la clé
ventes_triees = sorted(ventes, key=itemgetter("ville"))
# Grouper
for ville, groupe_iter in groupby(ventes_triees, key=itemgetter("ville")):
groupe = list(groupe_iter) # matérialiser si besoin de réutiliser
print(ville, "->", len(groupe), "lignes")
- Avantages : fonctionne en flux (chaque groupe est produit à la volée), utile pour gros volumes si la source est déjà triée.
- Inconvénients : nécessite un tri (O(n log n)) ou une source déjà triée. Les éléments identiques, mais non contigus ne sont pas fusionnés.
Astuce : groupement par plusieurs champs en une fois en utilisant une clé composée :
cles = ("ville", "produit")
ventes_triees = sorted(ventes, key=itemgetter(*cles))
for cle, grp in groupby(ventes_triees, key=itemgetter(*cles)):
ville, produit = cle
total_qte = sum(v["qte"] for v in grp)
print((ville, produit), "->", total_qte)
3) Compter rapidement avec collections.Counter
Si vous voulez seulement compter le nombre d’occurrences d’une clé (et pas regrouper les lignes), Counter
est très pratique :
from collections import Counter
# Combien de ventes par ville ?
compte = Counter(v["ville"] for v in ventes)
print(compte) # Counter({'Paris': 3, 'Lyon': 2, 'Nantes': 1})
Pour compter par clé multiple, utilisez un tuple comme clé :
compte_ville_produit = Counter((v["ville"], v["produit"]) for v in ventes)
4) Agrégations personnalisées (sommes, moyennes…)
Pour agréger des mesures par groupe (ex. chiffre d’affaires par ville), on peut accumuler des totaux dans un dictionnaire :
from collections import defaultdict
ca_par_ville = defaultdict(float)
for v in ventes:
ca_par_ville[v["ville"]] += v["qte"] * v["prix"]
print(dict(ca_par_ville))
Moyenne par groupe (accumuler somme et compte) :
from collections import defaultdict
somme_et_n = defaultdict(lambda: [0.0, 0]) # [somme, n]
for v in ventes:
d = somme_et_n[v["ville"]]
d[0] += v["qte"] * v["prix"]
d[1] += 1
moy_par_ville = {ville: somme / n for ville, (somme, n) in somme_et_n.items()}
5) Group by haut niveau avec pandas
Lorsque vous manipulez des données en tableau, pandas
offre un groupby
très puissant et concis.
import pandas as pd
df = pd.DataFrame(ventes)
# Somme des quantités par ville
print(df.groupby("ville")["qte"].sum())
# Agrégations multiples (créer d'abord une colonne chiffre d'affaires)
df = df.assign(ca=df["qte"] * df["prix"])
agg = df.groupby("ville").agg(
total_qte=("qte", "sum"),
total_ca=("ca", "sum"),
)
print(agg)
Agrégation sur plusieurs clés et plusieurs mesures :
res = (
df.groupby(["ville", "produit"]).agg(
total_qte=("qte", "sum"),
prix_moyen=("prix", "mean"),
)
)
print(res)
Astuces pandas :
as_index=False
pour conserver les colonnes de groupement comme colonnes normales.reset_index()
pour aplatir l’index après un groupby.pivot_table
est une alternative pratique pour des tableaux croisés.
6) Choisir la bonne approche
- Petites/moyennes données en mémoire, besoin de groupes réels :
defaultdict(list)
. - Données triées ou besoin de streaming par groupe :
itertools.groupby
(après tri si nécessaire). - Simple comptage d’occurrences :
Counter
. - Agrégations numériques personnalisées sans conserver les lignes : dictionnaires d’accumulateurs.
- Données tabulaires et besoins analytiques avancés :
pandas.DataFrame.groupby
.
Pièges et bonnes pratiques
itertools.groupby
regroupe seulement les éléments consécutifs : triez par la même clé avant de grouper.- Pour plusieurs clés : utilisez des tuples comme clés (
(ville, produit)
) ouitemgetter(*cles)
. - Si l’ordre d’apparition d’origine est important, préférez
defaultdict
+ accumulation;groupby
après tri perd l’ordre initial. - Pour de très gros volumes non triés, envisagez une base embarquée (
sqlite3
),pandas
en mode chunk, ou un tri externe. - Évitez d’empiler de gros objets dans des listes si vous ne les réutilisez pas : cumulez directement les agrégats.
- Utilisez
operator.itemgetter
/attrgetter
pour des clés rapides et lisibles.
Conclusion
Python offre plusieurs stratégies de « group by », du plus bas niveau (defaultdict
, groupby
) jusqu’au haut niveau avec pandas
.
Choisissez la méthode en fonction de votre volume de données, du besoin de conserver les lignes ou non, et des agrégations à réaliser.