Regrouper des données avec les tuples Python
Apprenez à regrouper des données avec les tuples Python : clés de dictionnaire, regroupement multi-champs, itertools.groupby, namedtuple et Counter.
Les tuples sont idéaux pour regrouper des données en Python. Parce qu'un tuple est immuable et hachable, il peut servir de clé de dictionnaire — ce qu'une liste ne peut jamais faire. Cela fait des tuples le choix naturel lorsque vous devez regrouper des enregistrements par une combinaison de champs, suivre des coordonnées multidimensionnelles, ou compter des événements composites.
Ce chapitre couvre quatre patterns de regroupement pratiques :
- Tuple comme clé de dictionnaire — regroupement par un ou plusieurs champs
itertools.groupbyavec des tuples — regroupement en flux sur des séquences triéescollections.namedtuple— ajout de noms aux enregistrements regroupéscollections.Counteravec des tuples — comptage d'événements composites
Chapitres connexes : Python Tuples · Accéder aux Tuples · Boucler sur les Tuples · Dictionnaires Python · Regroupement de Listes Python
Pourquoi les tuples peuvent être des clés de dictionnaire
Python exige que les clés de dictionnaire soient hachables — leur valeur ne doit jamais changer après que la clé est stockée. Les tuples satisfont cette condition car ils sont immuables. Les listes ne la satisfont pas et lèvent une TypeError lorsque vous essayez de les utiliser comme clés.
# A tuple can be a dictionary key
coordinates = {}
coordinates[(10, 20)] = "warehouse A"
coordinates[(30, 40)] = "warehouse B"
print(coordinates[(10, 20)]) # warehouse A
# A list cannot be a dictionary key
try:
d = {[10, 20]: "warehouse A"}
except TypeError as e:
print(f"TypeError: {e}")
# TypeError: unhashable type: 'list'Un piège important : un tuple qui contient un élément mutable (comme une liste) est également non hachable et ne peut pas être utilisé comme clé :
try:
d = {(1, [2, 3]): "value"}
except TypeError as e:
print(f"TypeError: {e}")
# TypeError: unhashable type: 'list'Composez les clés tuple uniquement de valeurs immuables — chaînes de caractères, nombres, booléens, ou autres tuples.
Regroupement par un seul champ de tuple
Le cas d'utilisation le plus simple consiste à décomposer une séquence de tuples et à les regrouper par un élément. Utilisez collections.defaultdict(list) pour éviter le code répétitif qui vérifie si une clé existe déjà.
from collections import defaultdict
employees = [
("Alice", "Engineering"),
("Bob", "Marketing"),
("Carol", "Engineering"),
("Dave", "Marketing"),
("Eve", "Engineering"),
]
by_dept = defaultdict(list)
for name, dept in employees:
by_dept[dept].append(name)
for dept, members in sorted(by_dept.items()):
print(f"{dept}: {members}")
# Engineering: ['Alice', 'Carol', 'Eve']
# Marketing: ['Bob', 'Dave']defaultdict(list) crée automatiquement une liste vide la première fois qu'une nouvelle clé dept est rencontrée, ce qui élimine le besoin d'un test if dept not in by_dept.
Regroupement multi-clés avec une clé tuple
La véritable puissance des clés tuple apparaît lorsque vous devez regrouper par plus d'un champ à la fois. Combinez les champs dans un tuple et utilisez ce tuple comme clé de dictionnaire.
from collections import defaultdict
records = [
("Alice", "Engineering", "Senior"),
("Bob", "Marketing", "Junior"),
("Carol", "Engineering", "Junior"),
("Dave", "Marketing", "Senior"),
("Eve", "Engineering", "Senior"),
]
# Group by (department, level) — a two-field composite key
grouped = defaultdict(list)
for name, dept, level in records:
grouped[(dept, level)].append(name)
for (dept, level), names in sorted(grouped.items()):
print(f"{dept} / {level}: {names}")
# Engineering / Junior: ['Carol']
# Engineering / Senior: ['Alice', 'Eve']
# Marketing / Junior: ['Bob']
# Marketing / Senior: ['Dave']Parce que (dept, level) est lui-même un tuple, il est hachable et peut servir de clé de dictionnaire quel que soit le nombre de champs qu'il contient. La déstructuration de la clé avec for (dept, level), names in ... maintient le code lisible.
Regroupement de grille et de coordonnées
Le regroupement multi-clés par tuple gère également les données spatiales de façon naturelle :
points = [(0, 0), (1, 2), (0, 1), (1, 3), (2, 4)]
from collections import defaultdict
by_x = defaultdict(list)
for x, y in points:
by_x[x].append(y)
for x, ys in sorted(by_x.items()):
print(f"x={x}: y-values={ys}")
# x=0: y-values=[0, 1]
# x=1: y-values=[2, 3]
# x=2: y-values=[4]Regroupement avec itertools.groupby
itertools.groupby regroupe les éléments consécutifs qui partagent la même clé. Il est économe en mémoire car il est paresseux — il ne charge pas tous les groupes en mémoire d'un coup. La contrepartie est que l'entrée doit être triée par la même clé avant d'être passée à groupby, sinon vous obtenez plusieurs groupes partiels pour la même clé au lieu d'un seul.
from itertools import groupby
sales = [
("East", "Q1", 1200),
("East", "Q2", 1500),
("West", "Q1", 900),
("West", "Q2", 1100),
("East", "Q3", 1800),
]
# Sort by region (index 0) before grouping
sales_sorted = sorted(sales, key=lambda t: t[0])
for region, group in groupby(sales_sorted, key=lambda t: t[0]):
items = list(group)
total = sum(q[2] for q in items)
print(f"{region}: total={total}, quarters={[q[1] for q in items]}")
# East: total=4500, quarters=['Q1', 'Q2', 'Q3']
# West: total=2000, quarters=['Q1', 'Q2']Deux choses à retenir lors de l'utilisation de groupby avec des tuples :
- Triez d'abord. Sans tri, chaque nouvelle occurrence de la même valeur de clé crée un groupe séparé.
- Consommez l'itérateur de groupe immédiatement. L'itérateur interne
groupest épuisé lorsque la boucle externe passe à la clé suivante. Appelez toujourslist(group)dans le corps de la boucle avant de l'utiliser ailleurs.
Quand groupby est préférable à defaultdict
Utilisez groupby lorsque vous traitez une large séquence déjà triée et que vous ne souhaitez pas charger le résultat groupé complet en mémoire. Pour un regroupement général sans garantie de tri, defaultdict(list) est plus simple et plus fiable.
Regroupement avec collections.namedtuple
namedtuple vous permet de donner des noms aux champs d'un tuple, rendant les données regroupées autodocumentées. Une fois le type namedtuple défini, les instances se comportent exactement comme des tuples ordinaires — elles sont immuables, hachables et itérables — mais les champs sont accessibles par nom aussi bien que par index.
from collections import namedtuple, defaultdict
Employee = namedtuple("Employee", ["name", "department", "salary"])
employees = [
Employee("Alice", "Engineering", 95000),
Employee("Bob", "Marketing", 72000),
Employee("Carol", "Engineering", 88000),
Employee("Dave", "Marketing", 68000),
Employee("Eve", "Engineering", 102000),
]
by_dept = defaultdict(list)
for emp in employees:
by_dept[emp.department].append(emp)
for dept, members in sorted(by_dept.items()):
avg_salary = sum(e.salary for e in members) / len(members)
print(f"{dept}: {[e.name for e in members]}, avg salary={avg_salary:.0f}")
# Engineering: ['Alice', 'Carol', 'Eve'], avg salary=95000
# Marketing: ['Bob', 'Dave'], avg salary=70000Notez que emp.department et emp.salary se lisent plus clairement que emp[1] et emp[2]. L'approche namedtuple est particulièrement utile quand le tuple comporte de nombreux champs et que l'indexation positionnelle devient difficile à suivre.
Compter les événements composites avec Counter
collections.Counter compte les objets hachables. Lorsque l'« objet » que vous souhaitez compter est une combinaison de valeurs, enveloppez ces valeurs dans un tuple et passez la séquence de tuples à Counter.
from collections import Counter
log = [
("GET", 200),
("POST", 201),
("GET", 200),
("GET", 404),
("POST", 500),
("GET", 200),
("DELETE", 204),
]
counts = Counter(log)
for entry, n in counts.most_common():
method, status = entry
print(f"{method} {status}: {n} times")
# GET 200: 3 times
# POST 201: 1 times
# GET 404: 1 times
# POST 500: 1 times
# DELETE 204: 1 timesCounter utilise le tuple comme clé de hachage en interne, de sorte que chaque combinaison unique (method, status) est suivie séparément sans aucun code de regroupement manuel.
Construction de groupes de tuples avec zip
zip associe des éléments de deux séquences ou plus en tuples. C'est un moyen naturel d'assembler des enregistrements groupés à partir de listes parallèles avant d'appliquer une opération de regroupement.
from collections import defaultdict
names = ["Alice", "Bob", "Carol"]
scores = [95, 87, 92]
departments = ["Engineering", "Marketing", "Engineering"]
# Pair the three sequences into tuples
records = list(zip(names, scores, departments))
print(records)
# [('Alice', 95, 'Engineering'), ('Bob', 87, 'Marketing'), ('Carol', 92, 'Engineering')]
# Now group by department
by_dept = defaultdict(list)
for name, score, dept in records:
by_dept[dept].append((name, score))
for dept, members in sorted(by_dept.items()):
print(f"{dept}: {members}")
# Engineering: [('Alice', 95), ('Carol', 92)]
# Marketing: [('Bob', 87)]Choisir le bon outil de regroupement
| Objectif | Meilleur outil |
|---|---|
| Regrouper par un champ dans une liste de tuples | defaultdict(list) |
| Regrouper par deux champs ou plus simultanément | defaultdict(list) avec clé tuple |
| Regrouper en flux une large séquence pré-triée | itertools.groupby |
| Ajouter des noms de champs aux enregistrements regroupés | collections.namedtuple |
| Compter les occurrences d'événements composites | collections.Counter |
| Assembler des listes parallèles en tuples groupés | zip |
Pièges courants
Oublier de trier avant groupby. itertools.groupby ne fusionne que les clés identiques consécutives. Si la même clé apparaît à plusieurs positions non consécutives, chaque occurrence devient un groupe séparé. Triez toujours par la même fonction de clé avant d'appeler groupby.
Utiliser une valeur mutable à l'intérieur d'une clé tuple. Un tuple contenant une liste n'est pas hachable et lève une TypeError lorsqu'il est utilisé comme clé de dictionnaire. Composez les clés tuple de chaînes de caractères, de nombres, de booléens ou de tuples imbriqués.
Consommer l'itérateur groupby plus d'une fois. Le sous-itérateur de groupe issu de groupby est épuisé dès que la boucle externe avance. Appelez list(group) dans le corps de la boucle si vous devez itérer le groupe plus d'une fois.
Traiter la sortie de defaultdict comme un dict ordinaire. Un defaultdict crée automatiquement de nouvelles clés lorsque vous lisez une clé manquante, ce qui peut silencieusement peupler le dictionnaire avec des listes vides. Si vous avez besoin de vérifier l'existence d'une clé sans créer de nouvelles entrées, convertissez d'abord en dict ordinaire : dict(grouped).
Sujets connexes
- Python Tuples — création, indexation et découpage de tuples
- Accéder aux Tuples — indexation, découpage et test d'appartenance
- Boucler sur les Tuples — itérer sur un tuple avec
foretwhile - Décompresser les Tuples — assigner les éléments d'un tuple à des variables en une ligne
- Modifier les Tuples — solutions de contournement pour modifier des tuples immuables
- Joindre les Tuples — concaténation et le constructeur
tuple() - Méthodes de Tuple —
count()etindex()en profondeur - Dictionnaires Python — le store clé-valeur qui rend possible le regroupement par clé tuple
- Regroupement de Listes Python — regroupement de listes avec
defaultdict,groupbyet les compréhensions de dictionnaire - Module Collections Python —
defaultdict,Counter,namedtupleet plus encore