W3docs

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.groupby avec des tuples — regroupement en flux sur des séquences triées
  • collections.namedtuple — ajout de noms aux enregistrements regroupés
  • collections.Counter avec 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 :

  1. Triez d'abord. Sans tri, chaque nouvelle occurrence de la même valeur de clé crée un groupe séparé.
  2. Consommez l'itérateur de groupe immédiatement. L'itérateur interne group est épuisé lorsque la boucle externe passe à la clé suivante. Appelez toujours list(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=70000

Notez 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 times

Counter 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

ObjectifMeilleur outil
Regrouper par un champ dans une liste de tuplesdefaultdict(list)
Regrouper par deux champs ou plus simultanémentdefaultdict(list) avec clé tuple
Regrouper en flux une large séquence pré-triéeitertools.groupby
Ajouter des noms de champs aux enregistrements regroupéscollections.namedtuple
Compter les occurrences d'événements compositescollections.Counter
Assembler des listes parallèles en tuples groupészip

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

Pratique

Pratique
Which of the following can be used as a Python dictionary key?
Which of the following can be used as a Python dictionary key?
Was this page helpful?