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

次のように、入力タプルに1つのキーと2つ以上の値がある場合、これをどのように実行できますか。タプル[('11013331', 'red', 'KAT'), ('9085267', 'blue' 'KAT')]の最後の要素はキーで、最初の2つは値です。結果は次のようになります。result= [{type: 'KAT'、items:[( '11013331'、red)、( '9085267'、blue)]}]
user1144616

1
from operator import itemgetter
バウマン

1
ステップ1は、インポートせずに行うことができますd= {}; for k,v in input: d.setdefault(k, []).append(v)
ecoe

私はPythonでMapReduceプログラムに取り組んでいますが、辞書やパンダなどの外部ライブラリを扱わずに、リスト内の値でグループ化する方法はあるのでしょうか。そうでない場合、どうすればアイテムを削除して結果を入力できますか?
Kourosh

54

Pythonの組み込み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つの辞書を作成し、各値に値のリストを含めることを検討することもできます。現在のフォームで、特定のタイプの値を見つけるには、リストを繰り返し処理して、一致する 'type'キーを含むdictを見つけ、そこから 'items'要素を取得する必要があります。1項目のdictのリストの代わりに単一のdictを使用する場合、マスターdictへの単一のキー付き検索で特定のタイプの項目を見つけることができます。を使用すると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))

または、新しいfangled dict-comprehensionフォームを使用します。

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

私はPythonでMapReduceプログラムに取り組んでいますが、辞書やパンダなどの外部ライブラリを扱わずに、リスト内の値でグループ化する方法はあるのでしょうか。そうでない場合、どうすればアイテムを削除して結果を入力できますか?
Kourosh

@Kourosh-新しい質問として投稿しますが、「アイテムを取り除き、結果を入力する」、「辞書を扱わない」ことで、どういう意味かを必ず示してください。
PaulMcG

7

パンダの単純なグループ化も好きでした。強力でシンプルで、大規模なデータセットに最適

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


3

この回答は@PaulMcGの回答に似ていますが、入力を並べ替える必要はありません。

関数型プログラミングの場合groupByは、1行で記述できます(インポートは含ま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()、常に返し常に返されます。つまり、それはラムダは、単一の式を評価できることをPythonの制限を回避するハックです。)Noneorgrp

これは、指定された関数を評価することによってキーが見つかり、その値が元の順序での元のアイテムのリストであるdictを返します。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')}

これは正確にあなたが求めた出力ではありませんが、あなたのニーズにぴったりかもしれません。


私はPythonで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.