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

다음과 같이 유형별로 (첫 번째 인덱싱 된 문자열 기준) 그룹화하고 싶습니다.

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

이 두 가지 모두 키의 원래 순서를 따르지 않습니다. 주문을 유지하려면 OrderedDict가 필요합니다.

>>> 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', 항목 : [( '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 프로그램을 작업 중입니다. 사전이나 팬더와 같은 외부 라이브러리를 처리하지 않고 목록의 값으로 그룹화하는 방법이 있는지 궁금합니다. 그렇지 않은 경우 항목을 제거하고 결과를 입력하려면 어떻게해야합니까?
Kourosh

54

파이썬의 내장 itertools모듈은 실제로 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'가 values_iterator에 의해 반환 된 튜플의 0 번째 요소의 목록 인 딕셔너리 목록으로 바꾸는 것입니다. 이렇게 :

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질문에 명시된대로 원하는 사전이 포함되어 있습니다.

그러나 유형별로 키가 지정된 단일 사전과 값 목록을 포함하는 각 값을 만드는 것을 고려할 수 있습니다. 현재 양식에서 특정 유형의 값을 찾으려면 목록을 반복하여 일치하는 '유형'키가 포함 된 사전을 찾은 다음 여기에서 '항목'요소를 가져와야합니다. 1- 항목 사전 목록 대신 단일 사전을 사용하는 경우 마스터 사전에 대한 단일 키 조회로 특정 유형의 항목을 찾을 수 있습니다. 를 사용하면 groupby다음과 같습니다.

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

result이제이 dict가 포함되어 있습니다 ( res@KennyTM의 대답 의 중간 defaultdict 와 유사합니다 ).

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

또는 새로운 dict-comprehension 형식을 사용합니다.

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

파이썬에서 MapReduce 프로그램을 작업 중입니다. 사전이나 팬더와 같은 외부 라이브러리를 처리하지 않고 목록의 값으로 그룹화하는 방법이 있는지 궁금합니다. 그렇지 않은 경우 항목을 제거하고 결과를 입력하려면 어떻게해야합니까?
Kourosh

@Kourosh-새 질문으로 게시하되 "항목 제거 및 결과 입력"및 "사전 처리 안 함"이 의미하는 바를 표시해야합니다.
PaulMcG

7

나는 또한 pandas 간단한 그룹화를 좋아했습니다 . 강력하고 간단하며 대규모 데이터 세트에 가장 적합합니다.

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()항상 반환 Noneor 항상 반환됩니다 grp. 즉 그것은 람다가 하나의 표현식을 평가에만 할 수있는 파이썬의 제한을 해결하기 위해 해킹입니다.)

이것은 주어진 함수를 평가하여 키가 발견되고 그 값이 원래 순서의 원래 항목 목록 인 dict를 리턴합니다. OP의 예에서 이것을 호출하면 groupBy(lambda pair: pair[1], input)다음 dict가 반환됩니다.

{'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 프로그램을 작업 중입니다. 사전이나 팬더와 같은 외부 라이브러리를 처리하지 않고 목록의 값으로 그룹화하는 방법이 있는지 궁금합니다. 그렇지 않은 경우 항목을 제거하고 결과를 입력하려면 어떻게해야합니까?
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.