Come posso usare itertools.groupby ()?


507

Non sono stato in grado di trovare una spiegazione comprensibile su come utilizzare effettivamente la itertools.groupby()funzione di Python . Quello che sto cercando di fare è questo:

  • Prendi un elenco - in questo caso, i figli di un lxmlelemento oggettivato
  • Dividilo in gruppi in base ad alcuni criteri
  • Quindi, ripeti più avanti ciascuno di questi gruppi separatamente.

Ho esaminato la documentazione e gli esempi , ma ho avuto problemi a provare ad applicarli oltre un semplice elenco di numeri.

Quindi, come posso usare itertools.groupby()? C'è un'altra tecnica che dovrei usare? Anche i suggerimenti per una buona lettura dei "prerequisiti" sarebbero apprezzati.


un caso utile per questo sarebbe leetcode.com/problems/string-compression
ShawnLee

Risposte:


657

NOTA IMPORTANTE: è necessario prima ordinare i dati .


La parte che non ho ottenuto è quella nella costruzione di esempio

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

kè la chiave di raggruppamento corrente ed gè un iteratore che è possibile utilizzare per scorrere il gruppo definito da quella chiave di raggruppamento. In altre parole, lo groupbystesso iteratore restituisce iteratori.

Ecco un esempio di ciò, usando nomi di variabili più chiari:

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 " "

Questo ti darà l'output:

Un orso è un animale.
Un'anatra è un animale.

Un cactus è una pianta.

Una barca veloce è un veicolo.
Uno scuolabus è un veicolo.

In questo esempio, thingsè un elenco di tuple in cui il primo elemento in ciascuna tupla è il gruppo a cui appartiene il secondo elemento.

La groupby()funzione accetta due argomenti: (1) i dati da raggruppare e (2) la funzione con cui raggrupparli.

Qui, lambda x: x[0]dice groupby()di usare il primo elemento in ogni tupla come chiave di raggruppamento.

Nell'istruzione precedente for, groupbyrestituisce tre coppie (chiave, iteratore di gruppo), una volta per ogni chiave univoca. È possibile utilizzare l'iteratore restituito per scorrere su ogni singolo elemento in quel gruppo.

Ecco un esempio leggermente diverso con gli stessi dati, usando una comprensione dell'elenco:

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

Questo ti darà l'output:

animali: orso e anatra.
piante: cactus.
veicoli: motoscafo e scuolabus.


1
C'è un modo per specificare in anticipo i gruppi e quindi non richiedere l'ordinamento?
John Salvatier,

2
itertools di solito fa clic per me, ma ho anche avuto un 'blocco' per questo. Ho apprezzato i tuoi esempi, molto più chiari dei documenti. Penso che gli itertools tendano a fare clic o meno, e sono molto più facili da capire se ti capita di avere problemi simili. Non ne avevo ancora bisogno in natura.
Profano,

3
I documenti di Julian Python sembrano fantastici per la maggior parte delle cose, ma quando si tratta di iteratori, generatori e cherrypy i documenti per lo più mi confondono. I documenti di Django sono doppiamente sconcertanti.
Marc Maxmeister,

6
+1 per l'ordinamento: non ho capito cosa volevi dire fino a quando non ho raggruppato i miei dati.
Cody,

4
@DavidCrook alla festa molto tardi, ma potrebbe aiutare qualcuno. Probabilmente è perché il tuo array non è ordinato, prova groupby(sorted(my_collection, key=lambda x: x[0]), lambda x: x[0]))supponendo che my_collection = [("animal", "bear"), ("plant", "cactus"), ("animal", "duck")]tu voglia raggruppareanimal or plant
Robin Nemeth,

72

L'esempio sui documenti di Python è abbastanza semplice:

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

Quindi, nel tuo caso, i dati sono un elenco di nodi, keyfuncè dove va la logica della funzione dei criteri e quindi groupby()raggruppa i dati.

È necessario fare attenzione a ordinare i dati in base ai criteri prima di chiamare groupby, altrimenti non funzioneranno. groupbyIl metodo in realtà scorre solo attraverso un elenco e ogni volta che cambia la chiave crea un nuovo gruppo.


46
Quindi hai letto keyfunce hai detto "sì, so esattamente cos'è perché questa documentazione è abbastanza semplice". Incredibile!
Jarad,

5
Credo che molte persone conoscano già questo esempio "semplice" ma inutile, dal momento che non dice che tipo di "dati" e "keyfunc" usare !! Ma immagino che nemmeno tu lo sappia, altrimenti aiuteresti le persone chiarendolo e non semplicemente incollandolo. O tu?
Apostolos,

69

itertools.groupby è uno strumento per raggruppare elementi.

Dai documenti , riusciamo a capire cosa potrebbe fare:

# [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 gli oggetti producono coppie gruppo-chiave in cui il gruppo è un generatore.

Caratteristiche

  • A. Raggruppa elementi consecutivi insieme
  • B. Raggruppa tutte le occorrenze di un elemento, dato un iterabile ordinato
  • C. Specificare come raggruppare gli elementi con una funzione chiave *

confronti

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

usi

Nota: molti di questi ultimi esempi derivano dal PyCon (talk) (spagnolo) di Víctor Terrón , "Kung Fu at Dawn with Itertools". Vedi anche il groupbycodice sorgente scritto in C.

* Una funzione in cui tutti gli elementi vengono passati e confrontati, influenzando il risultato. Altri oggetti con funzioni chiave includono sorted(), max()e min().


Risposta

# OP: Yes, you can use `groupby`, e.g. 
[do_something(list(g)) for _, g in groupby(lxml_elements, criteria_func)]

1
Tecnicamente, i documenti dovrebbero probabilmente dirlo [''.join(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D.
Mateen Ulhaq,

1
Sì. La maggior parte delle dotstringhe itertools sono "ridotte" in questo modo. Poiché tutti gli itertools sono iteratori, devono essere espressi in un builtin ( list(), tuple()) o consumati in un ciclo / comprensione per visualizzare i contenuti. Si tratta di licenziamenti che l'autore probabilmente ha escluso per conservare spazio.
pylang

39

Un trucco neato con groupby è eseguire la codifica della lunghezza in una riga:

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

ti darà un elenco di 2 tuple in cui il primo elemento è il carattere e il secondo è il numero di ripetizioni.

Modifica: Nota che questo è ciò che separa itertools.groupbydalla GROUP BYsemantica SQL : itertools non (e in generale non può) ordinare in anticipo l'iteratore, quindi i gruppi con la stessa "chiave" non vengono uniti.


27

Un altro esempio:

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

risultati in

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

Si noti che igroup è un iteratore (un iteratore secondario come viene chiamato dalla documentazione).

Questo è utile per tagliare un generatore:

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)

Un altro esempio di groupby: quando le chiavi non sono ordinate. Nel seguente esempio, gli elementi in xx sono raggruppati per valori in yy. In questo caso, viene prima emesso un set di zeri, seguito da un set di zeri, seguito nuovamente da un set di zeri.

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]

È interessante, ma itertools.islice non sarebbe meglio per sminuzzare un iterabile? Restituisce un oggetto che scorre come un generatore, ma utilizza il codice C.
trojjer,

@trojjer islice sarebbe meglio SE i gruppi hanno dimensioni coerenti.
woodm1979,

Voglio ottenere: [0, 1, 2], [1, 2, 3], [2, 3, 4] ...
GilbertS

21

AVVERTIMENTO:

L'elenco di sintassi (groupby (...)) non funzionerà come previsto. Sembra distruggere gli oggetti iteratori interni, quindi usando

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

produrrà:

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

Invece, di list (groupby (...)), prova [(k, list (g)) per k, g in groupby (...)], o se usi spesso quella sintassi,

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

e ottenere l'accesso alla funzionalità groupby evitando quegli fastidiosi (per piccoli dati) iteratori tutti insieme.


3
Molte delle risposte si riferiscono al blocco inciampante che è necessario ordinare prima del groupby per ottenere i risultati previsti. Ho appena incontrato questa risposta, che spiega lo strano comportamento che non ho mai visto prima. Non l'ho mai visto prima perché solo ora stavo cercando di elencare (groupby (range (10)) come dice @singular. Prima di allora avevo sempre usato l'approccio "raccomandato" di iterare "manualmente" attraverso gli oggetti groupby piuttosto che lasciando che il costruttore list () lo faccia "automaticamente".
The Red Pea,

9

Vorrei fare un altro esempio in cui groupby senza ordinamento non funziona. Adattato dall'esempio di 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 " "

l'uscita è

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.

ci sono due gruppi con veicolo, mentre uno potrebbe aspettarsi solo un gruppo


5
Devi prima ordinare i dati, usando come chiave la funzione per cui stai raggruppando. Questo è menzionato in due post sopra, ma non è evidenziato.
mbatchkarov,

Stavo facendo una comprensione dict per preservare i sub-iteratori per chiave, fino a quando ho capito che era semplice come dict (groupby (iteratore, chiave)). Dolce.
trojjer,

Ripensandoci e dopo la sperimentazione, la chiamata dict avvolta attorno al groupby esaurirà i sub-iteratori di gruppo. Dannazione.
trojjer,

Qual è il punto di questa risposta? Come si basa sulla risposta originale ?
codeforester

7

@CaptSolo, ho provato il tuo esempio, ma non ha funzionato.

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

Produzione:

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

Come puoi vedere, ci sono due o due e due, ma sono entrati in gruppi separati. Questo è quando ho capito che è necessario ordinare l'elenco passato alla funzione groupby. Quindi, l'uso corretto sarebbe:

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

Produzione:

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

Basta ricordare, se l'elenco non è ordinato, la funzione groupby non funzionerà !


7
In realtà funziona. Potresti considerare questo comportamento come rotto, ma è utile in alcuni casi. Vedi le risposte a questa domanda per un esempio: stackoverflow.com/questions/1553275/...
Denis Otkidach

6

Ordinamento e 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'}

5

Come uso Python's itertools.groupby ()?

Puoi usare groupby per raggruppare le cose su cui scorrere. Dai a groupby un iterabile e una funzione / richiamo chiave opzionale con cui controllare gli elementi quando escono dall'iterabile, e restituisce un iteratore che fornisce una doppia tupla del risultato del richiamo chiave e degli oggetti reali in un altro iterabile. Dall'aiuto:

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

Ecco un esempio di groupby che utilizza una coroutine per raggruppare in base a un conteggio, utilizza un callable chiave (in questo caso coroutine.send) per sputare il conteggio per comunque molte iterazioni e un sotto-iteratore raggruppato di elementi:

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

stampe

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

1

Un esempio utile che mi sono imbattuto potrebbe essere utile:

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)

Esempio di input: 14445221

Esempio di output: (1,1) (3,4) (1,5) (2,2) (1,1)


1

Questa implementazione di base mi ha aiutato a capire questa funzione. Spero che aiuti anche gli altri:

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

0

È possibile scrivere la propria funzione 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]}

1
reinventare la ruota non è una grande idea, anche la domanda è spiegare itertools groupby, non scrivere da soli
user2678074,

1
@ user2678074 Hai ragione. È qualcosa se vuoi scriverne uno per un punto di vista dell'apprendimento.
Sky

2
Inoltre, usa un dict (elenco) predefinito in modo che sia ancora più breve
Mickey Perlstein il

@MickeyPerlstein e più veloce.
funnydman,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.