W3docs

Agrupación de listas en Python

Aprende tres formas de agrupar listas en Python: defaultdict, itertools.groupby y comprensiones de diccionario, con ejemplos ejecutables y errores comunes.

Agrupar una lista significa dividir sus elementos en subcolecciones que comparten una clave común — por ejemplo, agrupar palabras por su primera letra, o agrupar registros por un campo de categoría. Python ofrece tres enfoques principales: un bucle manual con collections.defaultdict, itertools.groupby de la biblioteca estándar, y comprensiones de diccionario. Este capítulo explica cada técnica, cuándo elegir una sobre otra y los errores que se deben evitar.

Capítulos relacionados: Listas de Python · Métodos de lista · Comprensión de listas · Recorrer listas · Módulo collections

Qué significa "agrupar"

Dada una lista plana y una función clave que asigna cada elemento a una etiqueta de grupo, el objetivo es producir un mapeo de cada etiqueta a la lista de elementos que le pertenecen:

['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']
  key = first letter
  →  {'a': ['apple', 'avocado', 'apricot'],
      'b': ['banana', 'blueberry'],
      'c': ['cherry']}

Las tres técnicas descritas a continuación producen este tipo de resultado. Difieren en verbosidad, rendimiento y las restricciones que imponen sobre la entrada.

Técnica 1: Bucle manual con defaultdict

collections.defaultdict es el enfoque más común y flexible. Cuando se accede a una clave que aún no existe, un defaultdict(list) crea automáticamente una lista vacía para esa clave, por lo que nunca se necesita una comprobación del tipo if key in d.

from collections import defaultdict

words = ['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']

by_letter = defaultdict(list)
for word in words:
    by_letter[word[0]].append(word)

for letter, group in sorted(by_letter.items()):
    print(f'{letter}: {group}')
# a: ['apple', 'avocado', 'apricot']
# b: ['banana', 'blueberry']
# c: ['cherry']

¿Por qué usar defaultdict en lugar de un dict normal?

Con un dict normal se necesita una comprobación explícita antes del primer append:

# Plain dict — more boilerplate, same result
by_letter = {}
for word in words:
    if word[0] not in by_letter:
        by_letter[word[0]] = []
    by_letter[word[0]].append(word)

Una alternativa más corta con un dict normal es dict.setdefault:

by_letter = {}
for word in words:
    by_letter.setdefault(word[0], []).append(word)

setdefault está bien para scripts cortos, pero defaultdict es más rápido (sin búsquedas de clave repetidas) y más explícito en cuanto a la intención.

Agrupación por una clave calculada

La clave puede ser cualquier expresión, no solo un atributo. Aquí, una lista de enteros se divide en grupos pares e impares:

from collections import defaultdict

numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]

by_parity = defaultdict(list)
for n in numbers:
    by_parity['even' if n % 2 == 0 else 'odd'].append(n)

print('even:', sorted(by_parity['even']))  # even: [2, 4, 6]
print('odd:', sorted(by_parity['odd']))    # odd: [1, 1, 3, 3, 5, 5, 5, 9]

Agrupación de una lista de dicts

Este es el escenario más común en el mundo real: agrupar filas de datos por el valor de un campo:

from collections import defaultdict

data = [
    {'category': 'fruit', 'name': 'apple'},
    {'category': 'vegetable', 'name': 'carrot'},
    {'category': 'fruit', 'name': 'banana'},
    {'category': 'vegetable', 'name': 'broccoli'},
]

grouped = defaultdict(list)
for item in data:
    grouped[item['category']].append(item['name'])

for category, names in grouped.items():
    print(f'{category}: {names}')
# fruit: ['apple', 'banana']
# vegetable: ['carrot', 'broccoli']

Técnica 2: itertools.groupby

itertools.groupby agrupa elementos consecutivos que comparten la misma clave. Es útil cuando se necesita preservar la estructura de secuencias repetidas o cuando los datos ya están ordenados y se desea evitar construir todo el diccionario de una vez (es perezoso/en streaming).

from itertools import groupby

words = ['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']

# groupby only groups consecutive elements, so sort first
words_sorted = sorted(words, key=lambda w: w[0])

for letter, group in groupby(words_sorted, key=lambda w: w[0]):
    print(f'{letter}: {list(group)}')
# a: ['apple', 'avocado', 'apricot']
# b: ['banana', 'blueberry']
# c: ['cherry']

El error crítico: ordenar antes de groupby

groupby solo agrupa elementos consecutivos que comparten la misma clave. Si la entrada no está ordenada por la clave, se obtienen varios grupos pequeños en lugar de un grupo por clave:

from itertools import groupby

# Unsorted input — groupby produces WRONG results
numbers = [1, 1, 2, 3, 3, 1, 2, 2]
for key, group in groupby(numbers):
    print(f'{key}: {list(group)}')
# 1: [1, 1]   ← first run of 1s
# 2: [2]
# 3: [3, 3]
# 1: [1]      ← second run of 1s — NOT merged with the first!
# 2: [2, 2]

Siempre ordena con la misma función clave antes de llamar a groupby:

numbers_sorted = sorted(numbers)
for key, group in groupby(numbers_sorted):
    print(f'{key}: {list(group)}')
# 1: [1, 1, 1]
# 2: [2, 2, 2]
# 3: [3, 3]

Cuándo brilla groupby: procesamiento de datos grandes en streaming

Como groupby devuelve un iterador, no carga todos los grupos en memoria a la vez. Esto lo hace útil para procesar archivos grandes ordenados línea a línea sin construir un diccionario completo.

from itertools import groupby

# Grouping namedtuple records
from collections import namedtuple

Product = namedtuple('Product', ['category', 'name', 'price'])
products = [
    Product('dairy', 'milk', 1.10),
    Product('fruit', 'apple', 1.20),
    Product('fruit', 'banana', 0.50),
    Product('vegetable', 'broccoli', 1.50),
    Product('vegetable', 'carrot', 0.80),
]
# products is already sorted by category here

for category, group in groupby(products, key=lambda p: p.category):
    items = list(group)
    print(f'{category}: {[p.name for p in items]}')
# dairy: ['milk']
# fruit: ['apple', 'banana']
# vegetable: ['broccoli', 'carrot']

Técnica 3: Comprensión de diccionario

Una comprensión de diccionario construye el dict agrupado en una sola expresión. Es concisa pero tiene una desventaja: la comprensión de lista interna reescanea toda la entrada por cada clave única, lo que la hace O(n × k) donde k es el número de claves únicas. Para listas pequeñas esto está bien; para listas grandes, es preferible usar defaultdict.

words = ['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']

# Collect unique keys first, then build each group
letters = sorted(set(w[0] for w in words))
grouped = {letter: [w for w in words if w[0] == letter] for letter in letters}

for letter, group in grouped.items():
    print(f'{letter}: {group}')
# a: ['apple', 'avocado', 'apricot']
# b: ['banana', 'blueberry']
# c: ['cherry']

Esta técnica es más legible cuando el conjunto de claves es pequeño y ya se conoce — por ejemplo, agrupando resultados True/False o un conjunto fijo de categorías.

Agregación de grupos tras la agrupación

Un paso habitual después de agrupar es la agregación: calcular una suma, promedio, mínimo o conteo por grupo. Combina defaultdict(list) con aritmética estándar de Python:

from collections import defaultdict

scores = [
    ('Alice', 90), ('Bob', 75), ('Alice', 85),
    ('Bob', 88), ('Carol', 92),
]

by_student = defaultdict(list)
for name, score in scores:
    by_student[name].append(score)

for student, student_scores in sorted(by_student.items()):
    avg = sum(student_scores) / len(student_scores)
    print(f'{student}: scores={student_scores}, avg={avg:.1f}')
# Alice: scores=[90, 85], avg=87.5
# Bob: scores=[75, 88], avg=81.5
# Carol: scores=[92], avg=92.0

Cómo elegir la técnica adecuada

SituaciónMejor opción
Agrupación general, cualquier ordendefaultdict(list)
Necesidad de procesar en streaming datos grandes y ordenadositertools.groupby
Lista pequeña, expresión concisa en una líneaComprensión de diccionario
La entrada ya está ordenadadefaultdict o groupby indistintamente
Necesidad de agregar (suma, promedio, etc.)defaultdict(list) + aritmética

Errores comunes

Olvidar ordenar antes de groupby. groupby solo fusiona claves idénticas consecutivas. Siempre ordena la entrada con sorted() usando la misma función clave antes de pasarla a groupby.

Asignar list(group) inmediatamente. El iterador de grupo de groupby se agota en cuanto el bucle for externo avanza a la siguiente clave. Conviértelo a una lista dentro del cuerpo del bucle si necesitas usarlo más de una vez.

Modificar la lista de entrada durante la agrupación. Añadir o eliminar elementos de la lista durante un bucle de agrupación produce resultados impredecibles. Construye primero el dict agrupado y luego modifica los elementos.

defaultdict aparece en repr. defaultdict(list, {...}) se ve diferente de un dict normal en repr. Envuélvelo con dict(grouped) cuando necesites una salida de dict normal.

Práctica

Práctica
What must you do before passing a list to itertools.groupby() to get one group per unique key?
What must you do before passing a list to itertools.groupby() to get one group per unique key?
Was this page helpful?