この記事について
Python/Pandasのgroupbyについては、上記の前の記事で大口(?)を叩いていしまいましたが、そもそも標準ライブラリのitertoolsにgroupbyというメソッドがあるのでこれを機会に入門してみました。
itertoolsのgroupbyおよび、その他のitertools、collectionsの関数などでおもしろそうだと思ったものをつまみ食いしています。
- この記事について
- 公式へのリファレンスなど
- itertools.groupbyの定義
- itertools.groupbyの例
- その他の関数例
- accumulateなど(filterfalse、takewhile、dropwhileなど兄弟?シリーズ)
- さいごに
公式へのリファレンスなど
◆itertools.groupby docs.python.org
◆itertools docs.python.org
◆collections docs.python.org
itertools.groupbyの定義
itertools.groupby(iterable, key=None)
keyのところは関数が引数に取れるようです。sortedなどと同様ですね。iterableの一つずつの要素に、keyで指定の関数で値を取り出して、その値を元にグループ化されるということでしょう。
試してみます。
itertools.groupbyの例
import itertools as it a = list('ababaaacdeb') # groupby(key引数指定無し) print([[k, len(list(g))] for k, g in it.groupby(a)]) #[['a', 1], ['b', 1], ['a', 1], ['b', 1], ['a', 3], ['c', 1], ['d', 1], ['e', 1], ['b', 1]]
listの要素そのもの(a,b,..)単位でグループ化されて件数が得られるようです... というところですが、aの項が3つに分かれていますね。
公式の記事にあるとおり、Unixのuniqコマンドと同様にインプットの並びの塊でグループ化されるようです。
よって、並びの単位で同じものが続く件数を得たい場合は、この使い方になりますし、SQLのGROUPBYライクな動作をさせたい場合は、インプットをsortedなどでソートしてやることになります。
# groupby ソートする a = list('ababaaacdeb') print([[k, len(list(g))] for k, g in it.groupby(sorted(a))]) #[['a', 5], ['b', 3], ['c', 1], ['d', 1], ['e', 1]] # a,b,c,d,eのそれぞれの件数が得られた!!
ちなみに上記の相当の結果を得たいだけなら、collections.Counterの方がより直接的です。
# collections.Counter -> このケースだと、ソートしたgroupbyと同等 import collections as cl print(cl.Counter(a)) #Counter({'a': 5, 'b': 3, 'c': 1, 'd': 1, 'e': 1})
続いてgroupbyに関数を渡してみます。
# groupby再び: グループ化の条件としてkeyにlambdaを渡すことができる a = ['yaa', 'xab', 'xxx', 'xbb', 'zcc', 'ycc'] igen = it.groupby(sorted(a, key=lambda x: x[0]), lambda x: x[0]) print([[k, len(list(g))] for k, g in igen]) #[['x', 3], ['y', 2], ['z', 1]] # 上記のように数えるだけなら、collection.Counterでも同等 print(cl.Counter(map(lambda x:x[0],a))) #Counter({'x': 3, 'y': 2, 'z': 1})
「lambda x: x[0]」渡したことにより、インプットのリストの各要素の最初の1文字(x,y,z)でグループ化されましたね。
その他の関数例
chain
これは例を見た方が早いでしょう。
私は、似た動作をするものとしては、itertools.flattenの方をよく使うかな。
In [168]: list(it.chain('123','456')) Out[168]: ['1', '2', '3', '4', '5', '6'] In [169]: list(it.chain('abc','def','ghij')) Out[169]: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'] In [170]: list(it.chain([[1],[2]],['aa','bb'])) Out[170]: [[1], [2], 'aa', 'bb']
何かと嬉しいジェネレーター(count、cycle) および compress
countとcycleです。
おおよそ名前のイメージのとおりの挙動です。
cnt += 1
のようなコードで可読性が低いと感じるようでしたら、そのような記法からはおさらばできそうです。
# count g = it.count(10) print(next(g)) print(next(g)) # cycle g = it.cycle('ABCD') print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) g = it.cycle(list('ABCD')) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g))
続いて compress です。
# compress ビットマスクのような印象 print(list(it.compress('ABCDE',[1,0,0,1,0]))) # A,D
product、permutations、combinations...といった関数もありますが、ここではproductへのリンクに留めておきます。
accumulateなど(filterfalse、takewhile、dropwhileなど兄弟?シリーズ)
accumulate、filterfalse、takewhile、dropwhile、accumlateらはなかなか面白いです。
まず、accumulateの軽めの例。
# accumulate print(list(it.accumulate([1, 2, 3, 4, 5, 6]))) # 1,3,6,10,15,21
これらは引数が似ていてとっさにどれがどう使えるか条件反射的にわかるようにということで雑な図にしてみました。
In [330]: import itertools as it ...: data = [1, 2, 3, 4, 5, 6, 5, 4, 3, 10] ...: list(it.dropwhile(lambda x: x < 5, data)) ...: ...: ...: ...: Out[330]: [5, 6, 5, 4, 3, 10]
accumulateも他のもの同様に関数を引数に取れるのですが、map/reduceのreduce風というか、複数引数をとることができますので、細工の余地がありそうです。
# accumulateの例 再び In [163]: import itertools as it In [164]: data Out[164]: [3, 4, 6, 2, 1, 9, 0, 7, 5, 8] In [161]: list(it.accumulate(data)) Out[161]: [3, 7, 13, 15, 16, 25, 25, 32, 37, 45] In [162]: list(it.accumulate(data,lambda x,y: x + y )) Out[162]: [3, 7, 13, 15, 16, 25, 25, 32, 37, 45] In [165]: list(it.accumulate(data,lambda x,y: x + str(y) ,initial='a')) Out[165]: ['a', 'a3', 'a34', 'a346', 'a3462', 'a34621', 'a346219', 'a3462190', 'a34621907', 'a346219075', 'a3462190758'] In [167]: list(it.accumulate(data,lambda x,y: x + ( str(y) if y < 7 else '') ,initial='a')) Out[167]: ['a', 'a3', 'a34', 'a346', 'a3462', 'a34621', 'a34621', 'a346210', 'a346210', 'a3462105', 'a3462105']
accumlateを使って、カナとそのカナの行のローマ字の対応表を生成
accumlateを使って、カナとそのカナの行のローマ字の対応表を生成させて見ました。
◆accumulate使わず
import itertools as it kanaall = [chr(c) for c in range(ord('ァ'), ord('ン') + 1)] kana = list('ァカサタナハマャラヮン') roma = 'a,ka,sa,ta,na,ha,ma,ya,ra,wa'.split(',') kanaxroma = {k: r for k, r in zip(kana, roma)} kashira_roma = {} for k in kanaall: if kanaxroma.get(k) : kashira_roma[k] = tmp = kanaxroma[k] else: kashira_roma[k] = tmp print(kashira_roma)
◆accumulateを使う
import itertools as it kanaall = [chr(c) for c in range(ord('ァ'), ord('ン') + 1)] kana = list('ァカサタナハマャラヮン') roma = 'a,ka,sa,ta,na,ha,ma,ya,ra,wa'.split(',') kanaxroma = {k: r for k, r in zip(kana, roma)} _roma = it.accumulate(kanaall, lambda x, y: (y in kanaxroma) and kanaxroma[y] or x, initial='') next(_roma) # 1要素目はダミーなのでスキップする print({k:v for k,v in zip(kanaall,_roma)})
◆出力結果
どちらも下記の結果が得られます。
私の実力の都合でそれほど面白い事例にはなりませんでしたが...
{'ァ': 'a', 'ア': 'a', 'ィ': 'a', 'イ': 'a', 'ゥ': 'a', 'ウ': 'a', 'ェ': 'a', 'エ': 'a', 'ォ': 'a', 'オ': 'a', 'カ': 'ka', 'ガ': 'ka', 'キ': 'ka', 'ギ': 'ka', 'ク': 'ka', 'グ': 'ka', 'ケ': 'ka', 'ゲ': 'ka', 'コ': 'ka', 'ゴ': 'ka', 'サ': 'sa', 'ザ': 'sa', 'シ': 'sa', 'ジ': 'sa', 'ス': 'sa', 'ズ': 'sa', 'セ': 'sa', 'ゼ': 'sa', 'ソ': 'sa', 'ゾ': 'sa', 'タ': 'ta', 'ダ': 'ta', 'チ': 'ta', 'ヂ': 'ta', 'ッ': 'ta', 'ツ': 'ta', 'ヅ': 'ta', 'テ': 'ta', 'デ': 'ta', 'ト': 'ta', 'ド': 'ta', 'ナ': 'na', 'ニ': 'na', 'ヌ': 'na', 'ネ': 'na', 'ノ': 'na', 'ハ': 'ha', 'バ': 'ha', 'パ': 'ha', 'ヒ': 'ha', 'ビ': 'ha', 'ピ': 'ha', 'フ': 'ha', 'ブ': 'ha', 'プ': 'ha', 'ヘ': 'ha', 'ベ': 'ha', 'ペ': 'ha', 'ホ': 'ha', 'ボ': 'ha', 'ポ': 'ha', 'マ': 'ma', 'ミ': 'ma', 'ム': 'ma', 'メ': 'ma', 'モ': 'ma', 'ャ': 'ya', 'ヤ': 'ya', 'ュ': 'ya', 'ユ': 'ya', 'ョ': 'ya', 'ヨ': 'ya', 'ラ': 'ra', 'リ': 'ra', 'ル': 'ra', 'レ': 'ra', 'ロ': 'ra', 'ヮ': 'wa', 'ワ': 'wa', 'ヰ': 'wa', 'ヱ': 'wa', 'ヲ': 'wa', 'ン': 'wa'}
最後ですが、この流れでやや唐突ですが、collectionsの nametupleです。
私は他の言語でいうenumを比較的好むので、nametupleはわりかし好きな関数です。
In [333]: from collections import namedtuple ...: ...: Rect = namedtuple('Rect', ('x', 'y', 'width', 'height', )) ...: rect = Rect(1, 2, 3, 4) In [334]: rect.height Out[334]: 4 In [335]:
さいごに
公式の下記のレシピが、itertools自体およびちょっとしたPythonicなコードとして非常に参考になると思いました。