Comment utiliser itertools.groupby ()?

507

Je n'ai pas pu trouver d'explication compréhensible sur la façon d'utiliser réellement la itertools.groupby()fonction de Python . Ce que j'essaie de faire, c'est ceci:

  • Prenez une liste - dans ce cas, les enfants d'un lxmlélément objectivé
  • Divisez-le en groupes en fonction de certains critères
  • Ensuite, parcourez séparément chacun de ces groupes.

J'ai examiné la documentation et les exemples , mais j'ai eu du mal à les appliquer au-delà d'une simple liste de chiffres.

Alors, comment puis-je utiliser itertools.groupby()? Y a-t-il une autre technique que je devrais utiliser? Des pointeurs vers une bonne lecture «préalable» seraient également appréciés.

James Sulak
la source
un cas utile pour le serait leetcode.com/problems/string-compression
ShawnLee

Réponses:

657

REMARQUE IMPORTANTE: vous devez d'abord trier vos données .


La partie que je n'ai pas obtenue est que dans l'exemple de construction

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

kest la clé de regroupement actuelle et gest un itérateur que vous pouvez utiliser pour itérer sur le groupe défini par cette clé de regroupement. En d'autres termes, legroupby itérateur lui-même renvoie des itérateurs.

Voici un exemple de cela, en utilisant des noms de variables plus clairs:

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

Cela vous donnera la sortie:

Un ours est un animal.
Un canard est un animal.

Un cactus est une plante.

Un bateau rapide est un véhicule.
Un autobus scolaire est un véhicule.

Dans cet exemple, thingsest une liste de tuples où le premier élément de chaque tuple est le groupe auquel appartient le deuxième élément.

La groupby()fonction prend deux arguments: (1) les données à grouper et (2) la fonction à grouper avec.

Ici, lambda x: x[0]indique groupby()d'utiliser le premier élément de chaque tuple comme clé de regroupement.

Dans la fordéclaration ci-dessus ,groupby renvoie trois paires (clé, itérateur de groupe) - une fois pour chaque clé unique. Vous pouvez utiliser l'itérateur renvoyé pour parcourir chaque élément individuel de ce groupe.

Voici un exemple légèrement différent avec les mêmes données, utilisant une compréhension de liste:

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

Cela vous donnera la sortie:

animaux: ours et canard.
plantes: cactus.
véhicules: hors-bord et autobus scolaire.

James Sulak
la source
1
Existe-t-il un moyen de spécifier les groupes à l'avance et de ne pas exiger de tri?
John Salvatier
2
itertools clique généralement pour moi, mais j'avais également un «bloc» pour celui-ci. J'ai apprécié vos exemples, bien plus clairs que les documents. Je pense que les outils ont tendance à cliquer ou non, et sont beaucoup plus faciles à saisir si vous rencontrez des problèmes similaires. Je n'ai pas encore eu besoin de celui-ci à l'état sauvage.
Profane
3
Les documents @Julian python semblent parfaits pour la plupart des choses, mais en ce qui concerne les itérateurs, les générateurs et les cerises, les documents me mystifient principalement. Les documents de Django sont doublement déroutants.
Marc Maxmeister
6
+1 pour le tri - Je n'ai pas compris ce que vous vouliez dire avant de regrouper mes données.
Cody
4
@DavidCrook très tard pour la fête mais pourrait aider quelqu'un. C'est probablement parce que votre tableau n'est pas trié, essayez groupby(sorted(my_collection, key=lambda x: x[0]), lambda x: x[0]))en supposant que my_collection = [("animal", "bear"), ("plant", "cactus"), ("animal", "duck")]et que vous souhaitez regrouper paranimal or plant
Robin Nemeth
72

L'exemple sur les documents Python est assez simple:

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

Donc, dans votre cas, les données sont une liste de nœuds, keyfuncc'est là que va la logique de votre fonction de critères, puis groupby()regroupe les données.

Vous devez être prudent de trier les données selon les critères avant d'appeler groupbyou cela ne fonctionnera pas. groupbyEn fait, la méthode parcourt simplement une liste et chaque fois que la clé change, elle crée un nouveau groupe.

Seb
la source
46
Alors vous avez lu keyfuncet vous avez dit "ouais, je sais exactement ce que c'est parce que cette documentation est assez simple."? Incroyable!
Jarad
5
Je crois que la plupart des gens connaissent déjà cet exemple "simple" mais inutile, car il ne dit pas quel type de "données" et de "keyfunc" utiliser !! Mais je suppose que vous ne le savez pas non plus, sinon vous aideriez les gens en le clarifiant et pas seulement en le copiant-collant. Ou vous?
Apostolos
69

itertools.groupby est un outil pour regrouper des éléments.

À partir des documents , nous glanons plus loin ce qu'il pourrait faire:

# [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 les objets produisent des paires de groupes de clés où le groupe est un générateur.

Caractéristiques

  • A. Regrouper les éléments consécutifs
  • B. Grouper toutes les occurrences d'un élément, en fonction d'un itérable trié
  • C. Précisez comment grouper les éléments avec une fonction clé *

Comparaisons

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

Les usages

Remarque: Plusieurs de ces derniers exemples proviennent du PyCon (discours) de Víctor Terrón (espagnol) , "Kung Fu at Dawn with Itertools". Voir aussi le groupbycode source écrit en C.

* Une fonction où tous les éléments sont passés et comparés, influençant le résultat. D'autres objets avec des fonctions clés incluent sorted(), max()et min().


Réponse

# OP: Yes, you can use `groupby`, e.g. 
[do_something(list(g)) for _, g in groupby(lxml_elements, criteria_func)]
pylang
la source
1
Techniquement, les docs devraient probablement dire [''.join(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D.
Mateen Ulhaq
1
Oui. La plupart des docstrings d'itertools sont "abrégées" de cette manière. Étant donné que tous les outils sont des itérateurs, ils doivent être convertis en une fonction intégrée ( list(), tuple()) ou consommés dans une boucle / compréhension pour afficher le contenu. Ce sont des redondances que l'auteur a probablement exclues pour économiser de l'espace.
pylang
39

Une astuce intéressante avec groupby consiste à exécuter le codage de longueur sur une seule ligne:

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

vous donnera une liste de 2-tuples où le premier élément est le caractère et le 2e est le nombre de répétitions.

Edit: Notez que c'est ce qui sépare itertools.groupby de la GROUP BYsémantique SQL : itertools ne trie pas (et en général ne peut pas) l'itérateur à l'avance, donc les groupes avec la même "clé" ne sont pas fusionnés.

nimish
la source
27

Un autre exemple:

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

résulte en

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

Notez que igroup est un itérateur (un sous-itérateur comme l'appelle la documentation).

Ceci est utile pour découper un générateur:

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 autre exemple de groupby - lorsque les clés ne sont pas triées. Dans l'exemple suivant, les éléments de xx sont regroupés par valeurs en yy. Dans ce cas, un ensemble de zéros est émis en premier, suivi d'un ensemble de uns, suivi à nouveau par un ensemble de zéros.

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

Produit:

0 [0, 1, 2]
1 [3, 4, 5]
0 [6, 7, 8, 9]
user650654
la source
C'est intéressant, mais itertools.islice ne serait-il pas mieux pour fragmenter un itérable? Il renvoie un objet qui itère comme un générateur, mais il utilise du code C.
trojjer
@trojjer islice serait mieux SI les groupes sont de taille cohérente.
woodm1979
Je veux obtenir: [0, 1, 2], [1, 2, 3], [2, 3, 4] ...
GilbertS
21

ATTENTION:

La liste de syntaxe (groupby (...)) ne fonctionnera pas comme vous le souhaitez. Il semble détruire les objets internes de l'itérateur, donc en utilisant

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

produira:

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

Au lieu de list (groupby (...)), essayez [(k, list (g)) pour k, g dans groupby (...)], ou si vous utilisez souvent cette syntaxe,

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

et accéder à la fonctionnalité groupby tout en évitant tous ces itérateurs gênants (pour les petites données).

RussellStewart
la source
3
La plupart des réponses se réfèrent à la pierre d'achoppement que vous devez trier avant le regroupement pour obtenir les résultats attendus. Je viens de rencontrer cette réponse, ce qui explique le comportement étrange que je n'ai pas vu auparavant. Je n'ai pas vu auparavant parce que seulement maintenant j'essayais de lister (groupby (range (10)) comme le dit @singular. Avant cela, j'avais toujours utilisé l'approche "recommandée" d'itération "manuelle" à travers les objets groupby plutôt que laisser le constructeur list () le faire "automatiquement".
The Red Pea
9

Je voudrais donner un autre exemple où groupby sans tri ne fonctionne pas. Adapté de l'exemple 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 sortie est

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.

il y a deux groupes avec véhicule, alors que l'on ne peut s'attendre qu'à un seul groupe

kiriloff
la source
5
Vous devez d'abord trier les données en utilisant comme clé la fonction que vous regroupez. Ceci est mentionné dans deux articles ci-dessus, mais n'est pas mis en évidence.
mbatchkarov
Je faisais une compréhension de dict pour préserver les sous-itérateurs par clé, jusqu'à ce que je réalise que c'était aussi simple que dict (groupby (itérateur, clé)). Doux.
trojjer
Après réflexion et après expérimentation, l'appel dicté autour du groupe épuisera les sous-itérateurs du groupe. Zut.
trojjer
Quel est l'intérêt de cette réponse? Comment s'appuie-t-il sur la réponse originale ?
codeforester
7

@CaptSolo, j'ai essayé votre exemple, mais cela n'a pas fonctionné.

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

Production:

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

Comme vous pouvez le voir, il y a deux o et deux e, mais ils sont entrés dans des groupes séparés. C'est alors que j'ai réalisé que vous devez trier la liste transmise à la fonction groupby. Ainsi, l'utilisation correcte serait:

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

Production:

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

N'oubliez pas que si la liste n'est pas triée, la fonction groupby ne fonctionnera pas !

pedromanoel
la source
7
En fait ça marche. Vous pourriez penser que ce comportement est cassé, mais il est utile dans certains cas. Voir les réponses à cette question pour un exemple: stackoverflow.com/questions/1553275/…
Denis Otkidach
6

Tri et regroupement

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
la source
5

Comment utiliser itertools.groupby () de Python?

Vous pouvez utiliser groupby pour regrouper des éléments sur lesquels itérer. Vous donnez à groupby un itérable et une fonction clé / appelable facultative permettant de vérifier les éléments lorsqu'ils sortent de l'itérable, et il renvoie un itérateur qui donne deux fois le résultat de la clé appelable et les éléments réels dans un autre itérable. De l'aide:

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

Voici un exemple de groupby utilisant une coroutine pour regrouper par un nombre, il utilise une clé appelable (dans ce cas, coroutine.send) pour simplement cracher le nombre pour autant d'itérations et un sous-itérateur groupé d'éléments:

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

impressions

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

Un exemple utile que j'ai rencontré peut être 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)

Exemple d'entrée: 14445221

Exemple de sortie: (1,1) (3,4) (1,5) (2,2) (1,1)

Arko
la source
1

Cette implémentation de base m'a aidé à comprendre cette fonction. J'espère que cela aide aussi les autres:

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
la source
0

Vous pouvez écrire votre propre fonction 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]}
Ciel
la source
1
réinventer la roue n'est pas une bonne idée, la question est aussi d'expliquer itertools groupby, ne pas écrire propre
user2678074
1
@ user2678074 Vous avez raison. C'est quelque chose si vous voulez écrire votre propre point de vue d'apprentissage.
Sky
2
Mieux vaut également utiliser un defaultdict (liste) donc c'est encore plus court
Mickey Perlstein
@MickeyPerlstein et plus rapide.
funnydman