กลุ่ม Python โดย


125

สมมติว่าฉันมีชุดคู่ข้อมูลโดยที่ดัชนี 0คือค่าและดัชนี 1เป็นประเภท:

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

ฉันต้องการจัดกลุ่มตามประเภทของพวกเขา (ตามสตริงที่จัดทำดัชนีที่ 1) ดังนี้:

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

ฉันจะบรรลุเป้าหมายนี้อย่างมีประสิทธิภาพได้อย่างไร?

คำตอบ:


153

ทำได้ใน 2 ขั้นตอน ขั้นแรกให้สร้างพจนานุกรม

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

จากนั้นแปลงพจนานุกรมนั้นเป็นรูปแบบที่คาดไว้

>>> [{'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'}]

นอกจากนี้ยังสามารถทำได้ด้วย itertools.groupby แต่ต้องมีการเรียงลำดับอินพุตก่อน

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

โปรดทราบว่าทั้งสองอย่างนี้ไม่เป็นไปตามลำดับดั้งเดิมของคีย์ คุณต้องมี OrderDict หากคุณต้องการเก็บคำสั่งซื้อไว้

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

จะทำได้อย่างไรหากทูเปิลอินพุตมีหนึ่งคีย์และสองค่าขึ้นไปเช่นนี้[('11013331', 'red', 'KAT'), ('9085267', 'blue' 'KAT')]โดยที่องค์ประกอบสุดท้ายของทูเปิลคือคีย์และสองตัวแรกเป็นค่า ผลลัพธ์ควรเป็นดังนี้ result = [{type: 'KAT', items: [('11013331', red), ('9085267', blue)]}]
user1144616

1
from operator import itemgetter
Baumann

1
ขั้นตอนที่ 1 สามารถทำได้โดยไม่ต้องนำเข้า:d= {}; for k,v in input: d.setdefault(k, []).append(v)
ecoe

ฉันกำลังทำงานกับโปรแกรม MapReduce ใน python เพียงแค่สงสัยว่ามีวิธีใดบ้างในการจัดกลุ่มตามค่าในรายการโดยไม่ต้องเกี่ยวข้องกับพจนานุกรมหรือไลบรารีภายนอกเช่นแพนด้า? ถ้าไม่เช่นนั้นฉันจะกำจัดรายการและพิมพ์ผลลัพธ์ของฉันได้อย่างไร?
Kourosh

54

itertoolsโมดูลในตัวของ Python มีgroupbyฟังก์ชัน แต่สำหรับองค์ประกอบที่จะจัดกลุ่มจะต้องเรียงลำดับก่อนเพื่อให้องค์ประกอบที่จะจัดกลุ่มนั้นอยู่ติดกันในรายการ:

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)

ตอนนี้การป้อนข้อมูลดูเหมือนว่า:

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

groupbyผลตอบแทนที่ได้ลำดับ 2 (key, values_iterator)อันดับหนึ่งของรูปแบบ สิ่งที่เราต้องการคือเปลี่ยนสิ่งนี้ให้เป็นรายการของคำสั่งโดยที่ 'type' เป็นคีย์และ 'items' คือรายการขององค์ประกอบที่ 0 ของ tuples ที่ส่งคืนโดย values_iterator แบบนี้:

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

ตอนนี้resultมีคำสั่งที่คุณต้องการตามที่ระบุไว้ในคำถามของคุณ

คุณอาจพิจารณาเพียงแค่สร้างคำสั่งเดียวจากสิ่งนี้โดยป้อนตามประเภทและแต่ละค่าที่มีรายการค่า ในรูปแบบปัจจุบันของคุณในการค้นหาค่าสำหรับประเภทใดประเภทหนึ่งคุณจะต้องวนซ้ำในรายการเพื่อค้นหาคำสั่งที่มีคีย์ 'type' ที่ตรงกันจากนั้นจึงรับองค์ประกอบ 'items' จากมัน หากคุณใช้คำสั่งเดียวแทนรายการของคำสั่ง 1 รายการคุณสามารถค้นหารายการสำหรับประเภทใดประเภทหนึ่งได้ด้วยการค้นหาคีย์เดียวในคำสั่งต้นแบบ เมื่อใช้groupbyสิ่งนี้จะมีลักษณะดังนี้:

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

resultตอนนี้มีคำสั่งนี้ (ซึ่งคล้ายกับresdefaultdict ระดับกลางในคำตอบของ @ Kenny ™):

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

(หากคุณต้องการลดสิ่งนี้เป็นซับเดียวคุณสามารถ:

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

หรือใช้แบบฟอร์มความเข้าใจแบบเขียนตามคำบอกแบบใหม่:

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

ฉันกำลังทำงานกับโปรแกรม MapReduce ใน python เพียงแค่สงสัยว่ามีวิธีใดบ้างในการจัดกลุ่มตามค่าในรายการโดยไม่ต้องเกี่ยวข้องกับพจนานุกรมหรือไลบรารีภายนอกเช่นแพนด้า? ถ้าไม่เช่นนั้นฉันจะกำจัดรายการและพิมพ์ผลลัพธ์ของฉันได้อย่างไร?
Kourosh

@Kourosh - โพสต์เป็นคำถามใหม่ แต่อย่าลืมระบุสิ่งที่คุณหมายถึงโดย "กำจัดรายการและพิมพ์ผลการค้นหาของฉัน" และ "โดยไม่ต้องเกี่ยวข้องกับพจนานุกรม"
PaulMcG

7

ฉันชอบการจัดกลุ่มแบบเรียบง่ายของแพนด้าด้วย มีประสิทธิภาพเรียบง่ายและเพียงพอที่สุดสำหรับชุดข้อมูลขนาดใหญ่

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


3

คำตอบนี้คล้ายกับคำตอบของ @ PaulMcGแต่ไม่จำเป็นต้องเรียงลำดับอินพุต

สำหรับผู้ที่ใช้ในการเขียนโปรแกรมเชิงฟังก์ชันgroupByสามารถเขียนได้ในบรรทัดเดียว (ไม่รวมการนำเข้า!) และต่างจากที่itertools.groupbyไม่จำเป็นต้องเรียงอินพุต:

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

(เหตุผลสำหรับ... or grpในlambdaเป็นที่สำหรับนี้reduce()ในการทำงานที่lambdaตอบสนองความต้องการที่จะกลับอาร์กิวเมนต์แรกของตนเพราะlist.append()เสมอกลับมักจะกลับ. Ie มันสับจะได้รับรอบข้อ จำกัด หลามที่แลมบ์ดาเท่านั้นที่สามารถประเมินการแสดงออกเดียว.)Noneorgrp

สิ่งนี้ส่งคืนคำสั่งที่มีคีย์ที่พบโดยการประเมินฟังก์ชันที่กำหนดและมีค่าเป็นรายการของรายการดั้งเดิมในลำดับดั้งเดิม สำหรับตัวอย่างของ OP การเรียกสิ่งนี้ว่าgroupBy(lambda pair: pair[1], input)จะส่งคืนคำสั่งนี้:

{'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')]}

และตามคำตอบของ @ PaulMcGรูปแบบที่ร้องขอของ OP สามารถพบได้โดยการสรุปความเข้าใจในรายการ สิ่งนี้จะทำ:

result = {key: [pair[0] for pair in values],
          for key, values in groupBy(lambda pair: pair[1], input).items()}

รหัสน้อยกว่ามาก แต่ยังเข้าใจได้ ยังดีเพราะมันไม่ได้สร้างล้อใหม่
devdanke

2

ฟังก์ชันต่อไปนี้จะจัดกลุ่มสิ่งที่มีความยาวใด ๆอย่างรวดเร็ว ( ไม่จำเป็นต้องเรียงลำดับ ) โดยคีย์ที่มีดัชนีใด ๆ :

# 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

ในกรณีของคำถามของคุณดัชนีของคีย์ที่คุณต้องการจัดกลุ่มคือ 1 ดังนั้น:

group_by(input,1)

จะช่วยให้

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

ซึ่งไม่ตรงกับผลลัพธ์ที่คุณขอ แต่อาจเหมาะกับความต้องการของคุณด้วย


ฉันกำลังทำงานกับโปรแกรม MapReduce ใน python เพียงแค่สงสัยว่ามีวิธีใดบ้างในการจัดกลุ่มตามค่าในรายการโดยไม่ต้องเกี่ยวข้องกับพจนานุกรมหรือไลบรารีภายนอกเช่นแพนด้า? ถ้าไม่เช่นนั้นฉันจะกำจัดรายการและพิมพ์ผลลัพธ์ของฉันได้อย่างไร?
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'}]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.