Grup Python oleh

125

Asumsikan bahwa saya memiliki satu set pasangan data di mana indeks 0 adalah nilainya dan indeks 1 adalah tipe:

input = [
          ('11013331', 'KAT'), 
          ('9085267',  'NOT'), 
          ('5238761',  'ETH'), 
          ('5349618',  'ETH'), 
          ('11788544', 'NOT'), 
          ('962142',   'ETH'), 
          ('7795297',  'ETH'), 
          ('7341464',  'ETH'), 
          ('9843236',  'KAT'), 
          ('5594916',  'ETH'), 
          ('1550003',  'ETH')
        ]

Saya ingin mengelompokkan mereka berdasarkan tipenya (berdasarkan string yang diindeks pertama) seperti:

result = [ 
           { 
             type:'KAT', 
             items: ['11013331', '9843236'] 
           },
           {
             type:'NOT', 
             items: ['9085267', '11788544'] 
           },
           {
             type:'ETH', 
             items: ['5238761', '962142', '7795297', '7341464', '5594916', '1550003'] 
           }
         ] 

Bagaimana saya bisa mencapai ini dengan cara yang efisien?

Hellnar
sumber

Jawaban:

153

Lakukan dalam 2 langkah. Pertama, buat kamus.

>>> input = [('11013331', 'KAT'), ('9085267', 'NOT'), ('5238761', 'ETH'), ('5349618', 'ETH'), ('11788544', 'NOT'), ('962142', 'ETH'), ('7795297', 'ETH'), ('7341464', 'ETH'), ('9843236', 'KAT'), ('5594916', 'ETH'), ('1550003', 'ETH')]
>>> from collections import defaultdict
>>> res = defaultdict(list)
>>> for v, k in input: res[k].append(v)
...

Kemudian, ubah kamus itu menjadi format yang diharapkan.

>>> [{'type':k, 'items':v} for k,v in res.items()]
[{'items': ['9085267', '11788544'], 'type': 'NOT'}, {'items': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}, {'items': ['11013331', '9843236'], 'type': 'KAT'}]

Ini juga dimungkinkan dengan itertools.groupby tetapi membutuhkan input untuk diurutkan terlebih dahulu.

>>> sorted_input = sorted(input, key=itemgetter(1))
>>> groups = groupby(sorted_input, key=itemgetter(1))
>>> [{'type':k, 'items':[x[0] for x in v]} for k, v in groups]
[{'items': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}, {'items': ['11013331', '9843236'], 'type': 'KAT'}, {'items': ['9085267', '11788544'], 'type': 'NOT'}]

Perhatikan bahwa kedua hal ini tidak mengikuti urutan kunci aslinya. Anda membutuhkan OrderedDict jika Anda perlu menyimpan pesanan.

>>> from collections import OrderedDict
>>> res = OrderedDict()
>>> for v, k in input:
...   if k in res: res[k].append(v)
...   else: res[k] = [v]
... 
>>> [{'type':k, 'items':v} for k,v in res.items()]
[{'items': ['11013331', '9843236'], 'type': 'KAT'}, {'items': ['9085267', '11788544'], 'type': 'NOT'}, {'items': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}]
kennytm
sumber
Bagaimana ini bisa dilakukan jika tupel masukan memiliki satu kunci dan dua atau lebih nilai, seperti ini: di [('11013331', 'red', 'KAT'), ('9085267', 'blue' 'KAT')]mana elemen terakhir tupel adalah kunci dan dua yang pertama sebagai nilai. Hasilnya harus seperti ini: result = [{type: 'KAT', items: [('11013331', red), ('9085267', blue)]}]
user1144616
1
from operator import itemgetter
Baumann
1
langkah 1 dapat dilakukan tanpa impor:d= {}; for k,v in input: d.setdefault(k, []).append(v)
ecoe
Saya sedang mengerjakan program MapReduce dengan python, hanya bertanya-tanya apakah ada cara untuk mengelompokkan berdasarkan nilai dalam daftar tanpa berurusan dengan kamus atau pustaka eksternal seperti panda? Jika tidak, lalu bagaimana saya bisa menyingkirkan item dan mengetik hasil saya?
Kourosh
54

itertoolsModul built-in Python sebenarnya memiliki groupbyfungsi, tetapi untuk itu elemen yang akan dikelompokkan harus diurutkan terlebih dahulu sehingga elemen yang akan dikelompokkan bersebelahan dalam daftar:

from operator import itemgetter
sortkeyfn = itemgetter(1)
input = [('11013331', 'KAT'), ('9085267', 'NOT'), ('5238761', 'ETH'), 
 ('5349618', 'ETH'), ('11788544', 'NOT'), ('962142', 'ETH'), ('7795297', 'ETH'), 
 ('7341464', 'ETH'), ('9843236', 'KAT'), ('5594916', 'ETH'), ('1550003', 'ETH')] 
input.sort(key=sortkeyfn)

Sekarang masukan terlihat seperti:

[('5238761', 'ETH'), ('5349618', 'ETH'), ('962142', 'ETH'), ('7795297', 'ETH'),
 ('7341464', 'ETH'), ('5594916', 'ETH'), ('1550003', 'ETH'), ('11013331', 'KAT'),
 ('9843236', 'KAT'), ('9085267', 'NOT'), ('11788544', 'NOT')]

groupbymengembalikan urutan 2-tupel, dari bentuk (key, values_iterator). Apa yang kita inginkan adalah mengubahnya menjadi daftar dicts di mana 'type' adalah kuncinya, dan 'items' adalah daftar elemen ke-0 dari tupel yang dikembalikan oleh values_iterator. Seperti ini:

from itertools import groupby
result = []
for key,valuesiter in groupby(input, key=sortkeyfn):
    result.append(dict(type=key, items=list(v[0] for v in valuesiter)))

Sekarang result berisi dikt yang Anda inginkan, seperti yang dinyatakan dalam pertanyaan Anda.

Anda dapat mempertimbangkan, meskipun, hanya membuat satu dikt dari ini, dikunci menurut jenis, dan setiap nilai yang berisi daftar nilai. Dalam formulir Anda saat ini, untuk menemukan nilai untuk tipe tertentu, Anda harus mengulang daftar untuk menemukan dikt yang berisi kunci 'tipe' yang cocok, dan kemudian mendapatkan elemen 'item' darinya. Jika Anda menggunakan satu dikt dan bukan daftar dict 1-item, Anda dapat menemukan item untuk tipe tertentu dengan pencarian kunci tunggal ke dikt utama. Menggunakan groupby, ini akan terlihat seperti:

result = {}
for key,valuesiter in groupby(input, key=sortkeyfn):
    result[key] = list(v[0] for v in valuesiter)

resultsekarang berisi dict ini (ini mirip dengan resdefaultdict menengah dalam jawaban @ KennyTM):

{'NOT': ['9085267', '11788544'], 
 'ETH': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 
 'KAT': ['11013331', '9843236']}

(Jika Anda ingin mengurangi ini menjadi satu baris, Anda dapat:

result = dict((key,list(v[0] for v in valuesiter)
              for key,valuesiter in groupby(input, key=sortkeyfn))

atau menggunakan bentuk dikt-pemahaman bermodel baru:

result = {key:list(v[0] for v in valuesiter)
              for key,valuesiter in groupby(input, key=sortkeyfn)}
PaulMcG
sumber
Saya sedang mengerjakan program MapReduce dengan python, hanya bertanya-tanya apakah ada cara untuk mengelompokkan berdasarkan nilai dalam daftar tanpa berurusan dengan kamus atau pustaka eksternal seperti panda? Jika tidak, lalu bagaimana saya bisa menyingkirkan item dan mengetik hasil saya?
Kourosh
@Kourosh - Posting sebagai pertanyaan baru, tetapi pastikan untuk menunjukkan apa yang Anda maksud dengan "singkirkan item dan ketik hasil saya", dan "tanpa berurusan dengan kamus".
PaulMcG
7

Saya juga menyukai pengelompokan sederhana panda . ini kuat, sederhana dan paling memadai untuk kumpulan data besar

result = pandas.DataFrame(input).groupby(1).groups

akiva
sumber
3

Jawaban ini mirip dengan jawaban @ PaulMcG tetapi tidak memerlukan penyortiran input.

Bagi mereka yang menjadi pemrograman fungsional, groupBydapat ditulis dalam satu baris (tidak termasuk impor!), Dan tidak seperti itertools.groupbyitu, tidak memerlukan input untuk diurutkan:

from functools import reduce # import needed for python3; builtin in python2
from collections import defaultdict

def groupBy(key, seq):
 return reduce(lambda grp, val: grp[key(val)].append(val) or grp, seq, defaultdict(list))

(Alasan untuk ... or grpdi lambdaadalah bahwa untuk ini reduce()untuk bekerja, lambdakebutuhan untuk kembali argumen pertama, karena list.append()selalu mengembalikan Noneyang orakan selalu kembali grp. Yaitu itu hack untuk berkeliling pembatasan python yang lambda hanya dapat mengevaluasi ekspresi tunggal.)

Ini mengembalikan sebuah dict yang kuncinya ditemukan dengan mengevaluasi fungsi yang diberikan dan yang nilainya adalah daftar item asli dalam urutan aslinya. Untuk contoh OP, memanggil ini sebagai groupBy(lambda pair: pair[1], input)akan mengembalikan perintah ini:

{'KAT': [('11013331', 'KAT'), ('9843236', 'KAT')],
 'NOT': [('9085267', 'NOT'), ('11788544', 'NOT')],
 'ETH': [('5238761', 'ETH'), ('5349618', 'ETH'), ('962142', 'ETH'), ('7795297', 'ETH'), ('7341464', 'ETH'), ('5594916', 'ETH'), ('1550003', 'ETH')]}

Dan sesuai jawaban @ PaulMcG, format yang diminta OP dapat ditemukan dengan membungkusnya dalam pemahaman daftar. Jadi ini akan melakukannya:

result = {key: [pair[0] for pair in values],
          for key, values in groupBy(lambda pair: pair[1], input).items()}
ronen
sumber
Lebih sedikit kode, namun bisa dimengerti. Juga bagus karena tidak menemukan kembali roda.
devdanke
2

Fungsi berikut akan dengan cepat ( tidak perlu penyortiran ) mengelompokkan tupel dengan panjang berapa pun dengan kunci yang memiliki indeks:

# given a sequence of tuples like [(3,'c',6),(7,'a',2),(88,'c',4),(45,'a',0)],
# returns a dict grouping tuples by idx-th element - with idx=1 we have:
# if merge is True {'c':(3,6,88,4),     'a':(7,2,45,0)}
# if merge is False {'c':((3,6),(88,4)), 'a':((7,2),(45,0))}
def group_by(seqs,idx=0,merge=True):
    d = dict()
    for seq in seqs:
        k = seq[idx]
        v = d.get(k,tuple()) + (seq[:idx]+seq[idx+1:] if merge else (seq[:idx]+seq[idx+1:],))
        d.update({k:v})
    return d

Dalam kasus pertanyaan Anda, indeks kunci yang ingin Anda kelompokkan adalah 1, oleh karena itu:

group_by(input,1)

memberi

{'ETH': ('5238761','5349618','962142','7795297','7341464','5594916','1550003'),
 'KAT': ('11013331', '9843236'),
 'NOT': ('9085267', '11788544')}

yang bukan merupakan keluaran yang Anda minta, tetapi mungkin juga sesuai dengan kebutuhan Anda.

mmj
sumber
Saya sedang mengerjakan program MapReduce dengan python, hanya bertanya-tanya apakah ada cara untuk mengelompokkan berdasarkan nilai dalam daftar tanpa berurusan dengan kamus atau pustaka eksternal seperti panda? Jika tidak, lalu bagaimana saya bisa menyingkirkan item dan mengetik hasil saya?
Kourosh
0
result = []
# Make a set of your "types":
input_set = set([tpl[1] for tpl in input])
>>> set(['ETH', 'KAT', 'NOT'])
# Iterate over the input_set
for type_ in input_set:
    # a dict to gather things:
    D = {}
    # filter all tuples from your input with the same type as type_
    tuples = filter(lambda tpl: tpl[1] == type_, input)
    # write them in the D:
    D["type"] = type_
    D["itmes"] = [tpl[0] for tpl in tuples]
    # append D to results:
    result.append(D)

result
>>> [{'itmes': ['9085267', '11788544'], 'type': 'NOT'}, {'itmes': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}, {'itmes': ['11013331', '9843236'], 'type': 'KAT'}]

sumber