W3docs

Agrupando Dados com Tuplas Python

Aprenda a agrupar dados com tuplas Python: chaves de dicionário, agrupamento multi-chave, itertools.groupby, namedtuple, Counter e zip.

As tuplas são ideais para agrupar dados em Python. Por serem imutáveis e hasheáveis, podem servir como chave de dicionário — algo que uma lista jamais pode fazer. Isso torna as tuplas a escolha natural sempre que você precisa agrupar registros por uma combinação de campos, rastrear coordenadas multidimensionais ou contar eventos compostos.

Este capítulo aborda quatro padrões práticos de agrupamento:

  • Tupla como chave de dicionário — agrupamento por um ou múltiplos campos
  • itertools.groupby com tuplas — agrupamento em fluxo sobre sequências ordenadas
  • collections.namedtuple — adicionando nomes a registros agrupados
  • collections.Counter com tuplas — contando eventos compostos

Capítulos relacionados: Python Tuples · Access Tuples · Loop Tuples · Python Dictionaries · Python Lists Group

Por Que Tuplas Podem Ser Chaves de Dicionário

Python exige que as chaves de dicionário sejam hasheáveis — seu valor nunca deve mudar após a chave ser armazenada. As tuplas satisfazem esse requisito por serem imutáveis. As listas não satisfazem e lançam um TypeError quando você tenta usá-las como chaves.

# 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'

Um detalhe importante: uma tupla que contém um elemento mutável (como uma lista) também não é hasheável e não pode ser usada como chave:

try:
    d = {(1, [2, 3]): "value"}
except TypeError as e:
    print(f"TypeError: {e}")
# TypeError: unhashable type: 'list'

Mantenha as chaves de tupla compostas inteiramente de valores imutáveis — strings, números, booleanos ou outras tuplas.

Agrupando por um Único Campo de Tupla

O caso de uso mais simples é desempacotar uma sequência de tuplas e agrupar por um elemento. Use collections.defaultdict(list) para evitar o código repetitivo de verificar se uma chave já existe.

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) cria automaticamente uma lista vazia na primeira vez que uma nova chave dept é encontrada, eliminando a necessidade de verificações do tipo if dept not in by_dept.

Agrupamento Multi-Chave com uma Chave de Tupla

O verdadeiro poder das chaves de tupla surge quando você precisa agrupar por mais de um campo ao mesmo tempo. Combine os campos em uma tupla e use essa tupla como chave do dicionário.

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']

Como (dept, level) é em si uma tupla, ela é hasheável e pode servir como chave de dicionário independentemente de quantos campos contenha. Desestruturar a chave com for (dept, level), names in ... mantém o código legível.

Agrupamento em grade e coordenadas

O agrupamento multi-chave com tuplas também lida com dados espaciais de forma natural:

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]

Agrupando com itertools.groupby

itertools.groupby agrupa elementos consecutivos que compartilham a mesma chave. É eficiente em memória por ser lazy — não carrega todos os grupos na memória de uma vez. A contrapartida é que a entrada deve estar ordenada pela mesma chave antes de ser passada ao groupby, caso contrário você obtém múltiplos grupos parciais para a mesma chave em vez de um único.

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']

Dois pontos a lembrar ao usar groupby com tuplas:

  1. Ordene primeiro. Sem ordenação, cada nova sequência do mesmo valor de chave cria um grupo separado.
  2. Consuma o iterador do grupo imediatamente. O iterador interno group é esgotado quando o loop externo avança para a próxima chave. Sempre chame list(group) dentro do corpo do loop antes de usá-lo em outro lugar.

Quando groupby é preferível ao defaultdict

Use groupby ao processar uma sequência grande e já ordenada onde você não quer carregar o resultado agrupado completo na memória. Para agrupamento de uso geral sem garantia de ordenação, defaultdict(list) é mais simples e confiável.

Agrupando com collections.namedtuple

namedtuple permite dar nomes aos campos de uma tupla, tornando os dados agrupados autodocumentados. Após definir o tipo namedtuple, as instâncias se comportam exatamente como tuplas comuns — são imutáveis, hasheáveis e iteráveis — mas os campos são acessíveis por nome além de por índice.

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

Observe que emp.department e emp.salary são mais legíveis do que emp[1] e emp[2]. A abordagem com namedtuple é especialmente útil quando a tupla tem muitos campos e a indexação posicional se torna difícil de acompanhar.

Contando Eventos Compostos com Counter

collections.Counter conta objetos hasheáveis. Quando o "objeto" que você deseja contar é uma combinação de valores, agrupe esses valores em uma tupla e passe a sequência de tuplas ao 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 usa a tupla como chave de hash internamente, de modo que cada combinação única de (method, status) é rastreada separadamente sem a necessidade de código de agrupamento manual.

Construindo Grupos de Tuplas com zip

zip emparelha elementos de duas ou mais sequências em tuplas. Essa é uma forma natural de montar registros agrupados a partir de listas paralelas antes de aplicar uma operação de agrupamento.

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)]

Escolhendo a Ferramenta de Agrupamento Certa

ObjetivoMelhor ferramenta
Agrupar por um campo de uma lista de tuplasdefaultdict(list)
Agrupar por dois ou mais campos simultaneamentedefaultdict(list) com chave de tupla
Agrupar em fluxo uma sequência grande pré-ordenadaitertools.groupby
Adicionar nomes de campos a registros agrupadoscollections.namedtuple
Contar ocorrências de eventos compostoscollections.Counter
Montar listas paralelas em tuplas agrupadaszip

Armadilhas Comuns

Esquecer de ordenar antes do groupby. itertools.groupby só mescla chaves idênticas consecutivas. Se a mesma chave aparece em múltiplas posições não consecutivas, cada sequência vira um grupo separado. Sempre ordene pela mesma função de chave antes de chamar groupby.

Usar um valor mutável dentro de uma chave de tupla. Uma tupla que contém uma lista não é hasheável e lança TypeError quando usada como chave de dicionário. Mantenha as chaves de tupla compostas de strings, números, booleanos ou tuplas aninhadas.

Consumir o iterador do groupby mais de uma vez. O sub-iterador de grupo do groupby é esgotado assim que o loop externo avança. Chame list(group) dentro do corpo do loop se precisar iterar o grupo mais de uma vez.

Tratar a saída do defaultdict como um dict comum. Um defaultdict cria automaticamente novas chaves quando você lê uma chave inexistente, o que pode popular silenciosamente o dicionário com listas vazias. Se precisar verificar a existência de uma chave sem criar novas entradas, converta para um dict comum primeiro: dict(grouped).

Tópicos Relacionados

Prática

Prática
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?