¿Cómo uso itertools.groupby ()?

507

No he podido encontrar una explicación comprensible de cómo usar realmente la itertools.groupby()función de Python . Lo que intento hacer es esto:

  • Tome una lista: en este caso, los lxmlelementos secundarios de un elemento objetivado
  • Dividirlo en grupos según algunos criterios.
  • Luego, repita cada uno de estos grupos por separado.

He revisado la documentación y los ejemplos , pero he tenido problemas para intentar aplicarlos más allá de una simple lista de números.

Entonces, ¿cómo uso itertools.groupby()? ¿Hay otra técnica que debería estar usando? También se agradecerán los indicadores de buena lectura de "prerrequisitos".

James Sulak
fuente
un caso útil para el sería leetcode.com/problems/string-compression
ShawnLee

Respuestas:

657

NOTA IMPORTANTE: primero debe ordenar sus datos .


La parte que no entendí es que en el ejemplo de construcción

groups = []
uniquekeys = []
for k, g in groupby(data, keyfunc):
   groups.append(list(g))    # Store group iterator as a list
   uniquekeys.append(k)

kes la clave de agrupación actual y ges un iterador que puede usar para iterar sobre el grupo definido por esa clave de agrupación. En otras palabras, el groupbyiterador mismo devuelve iteradores.

Aquí hay un ejemplo de eso, usando nombres de variables más claros:

from itertools import groupby

things = [("animal", "bear"), ("animal", "duck"), ("plant", "cactus"), ("vehicle", "speed boat"), ("vehicle", "school bus")]

for key, group in groupby(things, lambda x: x[0]):
    for thing in group:
        print "A %s is a %s." % (thing[1], key)
    print " "

Esto te dará el resultado:

Un oso es un animal.
Un pato es un animal.

Un cactus es una planta.

Una lancha rápida es un vehículo.
Un autobús escolar es un vehículo.

En este ejemplo, thingshay una lista de tuplas donde el primer elemento de cada tupla es el grupo al que pertenece el segundo elemento.

La groupby()función toma dos argumentos: (1) los datos para agrupar y (2) la función para agruparlos.

Aquí, lambda x: x[0]le indica groupby()que use el primer elemento en cada tupla como la clave de agrupación.

En la fordeclaración anterior , groupbydevuelve tres pares (clave, iterador de grupo), una vez para cada clave única. Puede usar el iterador devuelto para iterar sobre cada elemento individual en ese grupo.

Aquí hay un ejemplo ligeramente diferente con los mismos datos, utilizando una lista de comprensión:

for key, group in groupby(things, lambda x: x[0]):
    listOfThings = " and ".join([thing[1] for thing in group])
    print key + "s:  " + listOfThings + "."

Esto te dará el resultado:

animales: oso y pato.
plantas: cactus.
vehículos: lancha rápida y autobús escolar.

James Sulak
fuente
1
¿Hay alguna forma de especificar los grupos de antemano y luego no requerir la clasificación?
John Salvatier
2
itertools generalmente hace clic para mí, pero también tenía un 'bloque' para este. Aprecio tus ejemplos, mucho más claros que los documentos. Creo que las herramientas de iterto tienden a hacer clic o no, y son mucho más fáciles de entender si te encuentras con problemas similares. No he necesitado este en la naturaleza todavía.
Profano
3
Los documentos de @Julian python parecen geniales para la mayoría de las cosas, pero cuando se trata de iteradores, generadores y cherrypy, los documentos en su mayoría me desconciertan. Los documentos de Django son doblemente desconcertantes.
Marc Maxmeister el
66
+1 para la clasificación: no entendí lo que querías decir hasta que agrupé mis datos.
Cody
44
@DavidCrook muy tarde a la fiesta pero podría ayudar a alguien. Es probablemente porque la matriz no está ordenada intento groupby(sorted(my_collection, key=lambda x: x[0]), lambda x: x[0]))bajo la suposición de que my_collection = [("animal", "bear"), ("plant", "cactus"), ("animal", "duck")]y que se desean agrupar poranimal or plant
Robin Nemeth
72

El ejemplo en los documentos de Python es bastante sencillo:

groups = []
uniquekeys = []
for k, g in groupby(data, keyfunc):
    groups.append(list(g))      # Store group iterator as a list
    uniquekeys.append(k)

Entonces, en su caso, los datos son una lista de nodos, keyfunces donde va la lógica de su función de criterios y luego groupby()agrupa los datos.

Debe tener cuidado de ordenar los datos según los criterios antes de llamar groupbyo no funcionará. groupbyEl método en realidad solo itera a través de una lista y cada vez que la clave cambia, crea un nuevo grupo.

Seb
fuente
46
¿Entonces leíste keyfuncy dijiste "sí, sé exactamente qué es eso porque esta documentación es bastante sencilla"? ¡Increíble!
Jarad
55
¡Creo que la mayoría de la gente ya sabe sobre este ejemplo "directo" pero inútil, ya que no dice qué tipo de 'datos' y 'keyfunc' usar! Pero supongo que tampoco lo sabes, de lo contrario ayudarías a las personas al aclararlo y no solo copiarlo y pegarlo. O tu
Apostolos
69

itertools.groupby es una herramienta para agrupar elementos.

De los documentos , recogemos aún más lo que podría hacer:

# [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B

# [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D

groupby los objetos producen pares clave-grupo donde el grupo es un generador.

Caracteristicas

  • A. Agrupe elementos consecutivos
  • B. Agrupe todas las apariciones de un elemento, dado un iterable ordenado
  • C. Especifique cómo agrupar elementos con una función clave *

Comparaciones

# Define a printer for comparing outputs
>>> def print_groupby(iterable, keyfunc=None):
...    for k, g in it.groupby(iterable, keyfunc):
...        print("key: '{}'--> group: {}".format(k, list(g)))

# Feature A: group consecutive occurrences
>>> print_groupby("BCAACACAADBBB")
key: 'B'--> group: ['B']
key: 'C'--> group: ['C']
key: 'A'--> group: ['A', 'A']
key: 'C'--> group: ['C']
key: 'A'--> group: ['A']
key: 'C'--> group: ['C']
key: 'A'--> group: ['A', 'A']
key: 'D'--> group: ['D']
key: 'B'--> group: ['B', 'B', 'B']

# Feature B: group all occurrences
>>> print_groupby(sorted("BCAACACAADBBB"))
key: 'A'--> group: ['A', 'A', 'A', 'A', 'A']
key: 'B'--> group: ['B', 'B', 'B', 'B']
key: 'C'--> group: ['C', 'C', 'C']
key: 'D'--> group: ['D']

# Feature C: group by a key function
>>> # keyfunc = lambda s: s.islower()                      # equivalent
>>> def keyfunc(s):
...     """Return a True if a string is lowercase, else False."""   
...     return s.islower()
>>> print_groupby(sorted("bCAaCacAADBbB"), keyfunc)
key: 'False'--> group: ['A', 'A', 'A', 'B', 'B', 'C', 'C', 'D']
key: 'True'--> group: ['a', 'a', 'b', 'b', 'c']

Usos

Nota: Varios de los últimos ejemplos se derivan de PyCon de Víctor Terrón (hablar) (español) , "Kung Fu en la madrugada con itertools". Vea también el groupbycódigo fuente escrito en C.

* Una función donde todos los elementos se pasan y comparan, influyendo en el resultado. Otros objetos con funciones clave incluyen sorted(), max()y min().


Respuesta

# OP: Yes, you can use `groupby`, e.g. 
[do_something(list(g)) for _, g in groupby(lxml_elements, criteria_func)]
pylang
fuente
1
Técnicamente, los documentos probablemente deberían decir [''.join(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D.
Mateen Ulhaq
1
Si. La mayoría de las cadenas de documentos de itertools están "abreviadas" de esta manera. Como todas las herramientas de iterto son iteradores, se deben convertir en un builtin ( list(), tuple()) o consumirse en un bucle / comprensión para mostrar el contenido. Estas son redundancias que el autor probablemente excluyó para conservar espacio.
pylang
39

Un buen truco con groupby es ejecutar la codificación de longitud en una línea:

[(c,len(list(cgen))) for c,cgen in groupby(some_string)]

le dará una lista de 2 tuplas donde el primer elemento es el carácter y el segundo es el número de repeticiones.

Editar: Tenga en cuenta que esto es lo que se separa itertools.groupbyde la GROUP BYsemántica de SQL : itertools no clasifica (y en general no puede) el iterador por adelantado, por lo que los grupos con la misma "clave" no se fusionan.

nimish
fuente
27

Otro ejemplo:

for key, igroup in itertools.groupby(xrange(12), lambda x: x // 5):
    print key, list(igroup)

resultados en

0 [0, 1, 2, 3, 4]
1 [5, 6, 7, 8, 9]
2 [10, 11]

Tenga en cuenta que igroup es un iterador (un sub-iterador como lo llama la documentación).

Esto es útil para fragmentar un generador:

def chunker(items, chunk_size):
    '''Group items in chunks of chunk_size'''
    for _key, group in itertools.groupby(enumerate(items), lambda x: x[0] // chunk_size):
        yield (g[1] for g in group)

with open('file.txt') as fobj:
    for chunk in chunker(fobj):
        process(chunk)

Otro ejemplo de groupby: cuando las claves no están ordenadas. En el siguiente ejemplo, los elementos en xx se agrupan por valores en yy. En este caso, primero se genera un conjunto de ceros, seguido de un conjunto de unos, seguido nuevamente por un conjunto de ceros.

xx = range(10)
yy = [0, 0, 0, 1, 1, 1, 0, 0, 0, 0]
for group in itertools.groupby(iter(xx), lambda x: yy[x]):
    print group[0], list(group[1])

Produce:

0 [0, 1, 2]
1 [3, 4, 5]
0 [6, 7, 8, 9]
usuario650654
fuente
Eso es interesante, pero ¿itertools.islice no sería mejor para fragmentar un iterable? Devuelve un objeto que itera como un generador, pero usa el código C.
trojjer
@trojjer islice sería mejor SI los grupos tienen un tamaño consistente.
woodm1979
Quiero obtener: [0, 1, 2], [1, 2, 3], [2, 3, 4] ...
GilbertS
21

ADVERTENCIA:

La lista de sintaxis (groupby (...)) no funcionará de la manera prevista. Parece destruir los objetos iteradores internos, por lo que usar

for x in list(groupby(range(10))):
    print(list(x[1]))

Producirá:

[]
[]
[]
[]
[]
[]
[]
[]
[]
[9]

En lugar de list (groupby (...)), intente [(k, list (g)) para k, g en groupby (...)], o si usa esa sintaxis con frecuencia,

def groupbylist(*args, **kwargs):
    return [(k, list(g)) for k, g in groupby(*args, **kwargs)]

y obtenga acceso a la funcionalidad groupby mientras evita esos molestos iteradores (para datos pequeños) todos juntos.

RussellStewart
fuente
3
Muchas de las respuestas se refieren al escollo que debe ordenar antes de groupby para obtener los resultados esperados. Acabo de encontrar esta respuesta, que explica el comportamiento extraño que no había visto antes. No lo había visto antes porque solo ahora estaba tratando de enumerar (groupby (range (10)) como dice @singular. Antes de eso siempre había usado el enfoque "recomendado" de iterar "manualmente" a través de los objetos groupby en lugar de dejando que el constructor list () lo haga "automáticamente"
The Red Pea
9

Me gustaría dar otro ejemplo donde groupby sin clasificación no funciona. Adaptado del ejemplo de James Sulak

from itertools import groupby

things = [("vehicle", "bear"), ("animal", "duck"), ("animal", "cactus"), ("vehicle", "speed boat"), ("vehicle", "school bus")]

for key, group in groupby(things, lambda x: x[0]):
    for thing in group:
        print "A %s is a %s." % (thing[1], key)
    print " "

la salida es

A bear is a vehicle.

A duck is a animal.
A cactus is a animal.

A speed boat is a vehicle.
A school bus is a vehicle.

Hay dos grupos con vehículo, mientras que uno podría esperar solo un grupo

Kiriloff
fuente
55
Primero debe ordenar los datos, utilizando como clave la función por la que está agrupando. Esto se menciona en dos publicaciones anteriores, pero no está resaltado.
mbatchkarov
Estaba haciendo una comprensión dict para preservar los sub-iteradores por clave, hasta que me di cuenta de que esto era tan simple como dict (groupby (iterador, clave)). Dulce.
trojjer
Pensándolo bien y después de la experimentación, la llamada dict envuelta alrededor del grupo agotará los sub-iteradores del grupo. Maldición.
trojjer
¿Cuál es el punto de esta respuesta? ¿Cómo se basa en la respuesta original ?
codeforester
7

@CaptSolo, probé tu ejemplo, pero no funcionó.

from itertools import groupby 
[(c,len(list(cs))) for c,cs in groupby('Pedro Manoel')]

Salida:

[('P', 1), ('e', 1), ('d', 1), ('r', 1), ('o', 1), (' ', 1), ('M', 1), ('a', 1), ('n', 1), ('o', 1), ('e', 1), ('l', 1)]

Como puede ver, hay dos o y dos e, pero se agruparon en grupos separados. Fue entonces cuando me di cuenta de que necesita ordenar la lista que pasó a la función groupby. Entonces, el uso correcto sería:

name = list('Pedro Manoel')
name.sort()
[(c,len(list(cs))) for c,cs in groupby(name)]

Salida:

[(' ', 1), ('M', 1), ('P', 1), ('a', 1), ('d', 1), ('e', 2), ('l', 1), ('n', 1), ('o', 2), ('r', 1)]

Solo recordando, si la lista no está ordenada, ¡la función groupby no funcionará !

pedromanoel
fuente
77
En realidad funciona. Puede pensar que este comportamiento está roto, pero es útil en algunos casos. Vea las respuestas a esta pregunta para ver un ejemplo: stackoverflow.com/questions/1553275/…
Denis Otkidach el
6

Clasificación y groupby

from itertools import groupby

val = [{'name': 'satyajit', 'address': 'btm', 'pin': 560076}, 
       {'name': 'Mukul', 'address': 'Silk board', 'pin': 560078},
       {'name': 'Preetam', 'address': 'btm', 'pin': 560076}]


for pin, list_data in groupby(sorted(val, key=lambda k: k['pin']),lambda x: x['pin']):
...     print pin
...     for rec in list_data:
...             print rec
... 
o/p:

560076
{'name': 'satyajit', 'pin': 560076, 'address': 'btm'}
{'name': 'Preetam', 'pin': 560076, 'address': 'btm'}
560078
{'name': 'Mukul', 'pin': 560078, 'address': 'Silk board'}
Satyajit Das
fuente
5

¿Cómo uso los itertools.groupby () de Python?

Puede usar groupby para agrupar cosas para iterar. Le da a groupby un iterable, y una función de tecla opcional / invocable mediante la cual verificar los elementos a medida que salen del iterable, y devuelve un iterador que da una tupla de dos tuplas del resultado de la clave invocable y los elementos reales en Otro iterable. De la ayuda:

groupby(iterable[, keyfunc]) -> create an iterator which returns
(key, sub-iterator) grouped by each value of key(value).

Aquí hay un ejemplo de groupby usando una rutina para agrupar por un conteo, usa una clave invocable (en este caso coroutine.send) para escupir el conteo por la cantidad de iteraciones y un sub-iterador agrupado de elementos:

import itertools


def grouper(iterable, n):
    def coroutine(n):
        yield # queue up coroutine
        for i in itertools.count():
            for j in range(n):
                yield i
    groups = coroutine(n)
    next(groups) # queue up coroutine

    for c, objs in itertools.groupby(iterable, groups.send):
        yield c, list(objs)
    # or instead of materializing a list of objs, just:
    # return itertools.groupby(iterable, groups.send)

list(grouper(range(10), 3))

huellas dactilares

[(0, [0, 1, 2]), (1, [3, 4, 5]), (2, [6, 7, 8]), (3, [9])]
Aaron Hall
fuente
1

Un ejemplo útil que encontré puede ser útil:

from itertools import groupby

#user input

myinput = input()

#creating empty list to store output

myoutput = []

for k,g in groupby(myinput):

    myoutput.append((len(list(g)),int(k)))

print(*myoutput)

Entrada de muestra: 14445221

Salida de muestra: (1,1) (3,4) (1,5) (2,2) (1,1)

Arko
fuente
1

Esta implementación básica me ayudó a entender esta función. Espero que ayude a otros también:

arr = [(1, "A"), (1, "B"), (1, "C"), (2, "D"), (2, "E"), (3, "F")]

for k,g in groupby(arr, lambda x: x[0]):
    print("--", k, "--")
    for tup in g:
        print(tup[1])  # tup[0] == k
-- 1 --
A
B
C
-- 2 --
D
E
-- 3 --
F
Tiago
fuente
0

Puede escribir su propia función groupby:

           def groupby(data):
                kv = {}
                for k,v in data:
                    if k not in kv:
                         kv[k]=[v]
                    else:
                        kv[k].append(v)
           return kv

     Run on ipython:
       In [10]: data = [('a', 1), ('b',2),('a',2)]

        In [11]: groupby(data)
        Out[11]: {'a': [1, 2], 'b': [2]}
Cielo
fuente
1
reinventar la rueda no es una gran idea, también la pregunta es explicar itertools groupby, no escribir el propio
user2678074
1
@ user2678074 Tienes razón. Es algo si quieres escribir tu propio para un punto de vista de aprendizaje.
Cielo
2
También es mejor usar un defaultdict (lista) para que sea aún más corto
Mickey Perlstein
@MickeyPerlstein y más rápido.
funnydman