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()항상 반환 None을or 항상 반환됩니다 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.