Pythonで単語の数え上げとかするならCounterを使うと便利なはなし

  • 131
    いいね
  • 3
    コメント

Mecabで遊んでて、いいのないかなって思ったら見つけたのでメモ。

テキストでもCSVでも何でも良いですが、重複の存在するようなリストで、各要素の出現頻度を数えるようなコードを書きたいことはまれによくあると思います。
辞書を使って素直に実装すると


data = ['aaa', 'bbb', 'ccc', 'aaa', 'ddd']

word_and_counts = {}
for word in data:
    if word_and_counts.has_key(word):
        word_and_counts[word] += 1
    else:
        word_and_counts[word] = 1
for w, c in sorted(word_and_counts.iteritems(), key=lambda x: x[1], reverse=True):
    print w, c  # =>
                #   aaa 2
                #   bbb 1
                #   ccc 1
                #   ddd 1

とかそんな感じの雰囲気になると思います。

こういうときcollectionsモジュールが便利なんですよ。
つうわけでcollections.Counterを使って実装しなおします。

from collections import Counter

data = ['aaa', 'bbb', 'ccc', 'aaa', 'ddd']
counter = Counter(data)
for word, cnt in counter.most_common():
    print word, cnt # =>
                    #   aaa 2
                    #   bbb 1
                    #   ccc 1
                    #   ddd 1

なんか簡潔に実装出来ました。しかも組み込みなので早そうです。
その上、Counterは他にも各種演算子や便利メソッドをそなえています。

from collections import Counter

dataA = ['aaa', 'bbb', 'ccc', 'aaa', 'ddd']
dataB = ['aaa', 'bbb', 'bbb', 'bbb', 'abc']

counterA = Counter(dataA)
counterB = Counter(dataB)

counter = counterA + counterB  # 頻度を足し合わせられる
counterA.subtract(counterB)  # 要素の差をとる(破壊的メソッド)
counter.most_common(3)  # 上位3要素の取得(上記の例のように、引数nの省略を省略すればすべての要素を降順で取得)
# 他にもいくつか

ハッシュ可能なオブジェクトであれば良いということなので、他にもなんかいい感じの使い道があるかもですね?

他にも、collectionsモジュールはいい感じに便利なクラスがあったりするので一度目を通しておくとたまに役に立つ気がします。

最終的に、Counterを使って、ダウンロードしてきたツイッターのツイート履歴でMecabってみたコードが以下の感じになります。

# -*- coding: utf-8 -*-

from collections import Counter
import codecs
import json

import MeCab


# バッドノウハウ感あるけど、出力結果をリダイレクトしたいし
import sys
reload(sys)
sys.setdefaultencoding("utf-8")

# codecsはunicodeを返す
# 一行目に余計な記述があってだるいしテストコードだし面倒なので事前に消しておこう
_tweetfile = codecs.open('./data/js/tweets/2013_09.js', 'r', 'sjis')
tweets = json.load(_tweetfile)
# Mecabはstr型しか受け付けないのでエンコード
texts = (tw['text'].encode('utf-8') for tw in tweets)

tagger = MeCab.Tagger('-Ochasen')
counter = Counter()
for text in texts:
    nodes = tagger.parseToNode(text)
    while nodes:
        if nodes.feature.split(',')[0] == '名詞':
            word = nodes.surface.decode('utf-8')
            counter[word] += 1
        nodes = nodes.next
for word, cnt in counter.most_common():
    print word, cnt

名詞かどうかを判別する部分がダサかったり、記号が入り込んだりしますがひとまずいい感じに動きました。めでたしめでたし。


こういう小技っぽいものをまとめてみたのでよろしければどうぞ (覚えるだけでPythonのコードが少し綺麗になる頻出イディオム