Counter オブジェクト
Definition 1 Counter
Counter は,ハッシュ可能なオブジェクトの出現回数を数えるための辞書サブクラス
要素を辞書のキーとして保持し,その出現回数を値として保持するコレクション
カウント値には 0 や負の値を含む任意の整数が使用可能
リストの要素の出現回数を集計する場合,Dict を用いる場合
Code
words = ['red' , 'blue' , 'red' , 'green' , 'blue' , 'blue' ]
count_dict = {}
for word in words:
# dict.get(key, default) returns default if key does not exist
count_dict[word] = count_dict.get(word, 0 ) + 1
print (count_dict)
{'red': 2, 'blue': 3, 'green': 1}
Counterを用いることで以下のようにより簡単に集計することが出来ます
Code
from collections import Counter
word_counter = Counter(words)
print (word_counter)
Counter({'blue': 3, 'red': 2, 'green': 1})
また,すべての要素の出現頻度の合計は total() メソッドを用いて表示できます
Code
print (word_counter.total()) # 6 = 3 + 2 + 1
Example 1 (単語出現頻度の計測)
ハムレットの一節を内容とする hamlet.txt を例に,出現単語のTop 5の計測を行います.Counter クラスには most_common() メソッドがあり,要素を出現頻度に応じて降順で返してくれます.引数として整数を指定すると,最上位から数えてその個数分だけを出力してくれるので
Code
import re
from collections import Counter
# 単語を抽出(小文字化して正規表現でマッチ,句読点は除く)
hamlet_words = re.findall(r' \w + ' , open ('hamlet.txt' ).read().lower())
Counter(hamlet_words).most_common(5 )
[('to', 4), ('the', 3), ('be', 2), ('or', 2), ('and', 2)]
出現頻度下位Top 3を出力したい場合は
Code
hamlet_counter = Counter(hamlet_words)
hamlet_counter.most_common()[:- 4 :- 1 ]
[('them', 1), ('end', 1), ('opposing', 1)]
Counter インスタンスの作成方法
Code
from collections import Counter
# 1. 空の Counter を作る
c = Counter()
print (c) # Counter()
# 2. イテラブルから作る
c = Counter('gallahad' )
print (c) # Counter({'a': 3, 'l': 2, 'g': 1, 'h': 1, 'd': 1})
# 3. 辞書(マッピング)から作る
c = Counter({'red' : 1 , 'blue' : 2 })
print (c) # Counter({'red': 1, 'blue': 2})
# 4. キーワード引数から作る
c = Counter(cats= 4 , dogs= 8 )
print (c) # Counter({'dogs': 8, 'cats': 4})
Counter()
Counter({'a': 3, 'l': 2, 'g': 1, 'h': 1, 'd': 1})
Counter({'blue': 2, 'red': 1})
Counter({'dogs': 8, 'cats': 4})
存在しないキーの取り扱い
Counter では存在しないキーにアクセスしても KeyError にはならず,0 が返るのが特徴です
Code
from collections import Counter
# カウンター作成
c = Counter(['eggs' , 'ham' ])
# 存在するキー
print (c['eggs' ]) # 1
# 存在しないキー
print (c['bacon' ]) # 0 ← KeyError にはならない
Example 2 (dict と KeyError)
Code
foods = {'eggs' : 1 , 'ham' : 1 }
try :
print ("bacon:" , foods['bacon' ]) # 存在しないキーは KeyError
except KeyError as e:
print (f"KeyError: { e} " )
挿入順序の保持
Python 3.7 以降,Counter は内部的には挿入順を保持しています.print などでkey-valueを出力するとvalueに応じた降順でソートされたように見えますが,Counter.keys() などでkeyの順番を確認すると,挿入順となっています
Code
from collections import Counter
c = Counter('gallahad' )
# check display order
print (c) # 出力は頻度順
print (list (c)) # 内部の挿入順
print (c.keys()) # 内部の挿入順
print (list (c.elements())) # elements順
Counter({'a': 3, 'l': 2, 'g': 1, 'h': 1, 'd': 1})
['g', 'a', 'l', 'h', 'd']
dict_keys(['g', 'a', 'l', 'h', 'd'])
['g', 'a', 'a', 'a', 'l', 'l', 'h', 'd']
演算処理の順番
Counter オブジェクトは,+ を使った加算処理ができます.このとき,基本的なキーの順番は左オペラントとなり,新しいキーは右オペランドから追加されるという内部処理になります.
つまり,要素順は,左オペランドの出現順 → 右オペランドで新規に現れた順 で決まります.
Code
from collections import Counter
c1 = Counter('zbcaad' )
c2 = Counter('abcd' )
# 算術演算
c3 = c1 + c2
# 出力
print (c3)
print (c3.keys())
print (list (c3.elements()))
Counter({'a': 3, 'b': 2, 'c': 2, 'd': 2, 'z': 1})
dict_keys(['z', 'b', 'c', 'a', 'd'])
['z', 'b', 'b', 'c', 'c', 'a', 'a', 'a', 'd', 'd']
Counter と算術演算
TL;DRs
Code
from collections import Counter
# 2つのカウンターを作成
c = Counter(a= 3 , b= 1 , c= 1 )
d = Counter(a= 1 , b= 2 , c= 1 )
# -------------------------------
# 算術演算と集合演算
# -------------------------------
print ("c + d =" , c + d) # 足し算: 各キーの値を加算
print ("c - d =" , c - d) # 引き算: 正の値のみ保持, 0 は除外される
print ("c & d =" , c & d) # 積集合: min(c[x], d[x])
print ("c | d =" , c | d) # 和集合: max(c[x], d[x])
# -------------------------------
# 比較演算
# -------------------------------
print ("c == d ?" , c == d) # 等価判定
print ("c <= d ?" , c <= d) # 部分集合判定 (すべての要素が <=)
print ("c >= d ?" , c >= d) # 部分集合判定 (すべての要素が >=)
c + d = Counter({'a': 4, 'b': 3, 'c': 2})
c - d = Counter({'a': 2})
c & d = Counter({'a': 1, 'b': 1, 'c': 1})
c | d = Counter({'a': 3, 'b': 2, 'c': 1})
c == d ? False
c <= d ? False
c >= d ? False
subtract メソッド
Definition 2 Counter.subtract
ある Counter から別の Counter や iterable の要素を減算するメソッド
結果のカウント値は 負の値 になることもある
元の Counter が 変更される(新しいオブジェクトは返らない)
基本的な使い方は
Code
from collections import Counter
c1 = Counter('aabbcc' )
c2 = Counter('abc' )
# c1 から c2 を減算
c1.subtract(c2)
print (c1)
Counter({'a': 1, 'b': 1, 'c': 1})
Example 3 (iterableからの減算)
Code
c = Counter('banana' )
c.subtract('an' ) # 'a','n' をそれぞれ 1 ずつ減らす
print (c)
Counter({'a': 2, 'b': 1, 'n': 1})
Example 4 (値は 0 未満になる場合)
Code
c = Counter('banana2' )
c.subtract('and2' ) # 'a','n' , 'd'をそれぞれ 1 ずつ減らす
# 出力
print (c) # 演算結果の出力
print (+ c) # 0以上の要素のみ出力(0は含まない)
print (- c) # 0未満の要素のみ出力
Counter({'a': 2, 'b': 1, 'n': 1, '2': 0, 'd': -1})
Counter({'a': 2, 'b': 1, 'n': 1})
Counter({'d': 1})
実行順番の注意
Counter の 算術演算は順序によって出力のキー順序が変わることがある
負の値の処理が関わる場合,結果の値自体も変わる
Code
from collections import Counter
# 3つのカウンターを作成
x = Counter(a= 3 , b= 1 , c=- 2 )
y = Counter(a= 1 , b= 2 , c= 1 , d= 1 )
z = Counter(a= 1 , b= 2 , c= 2 , e= 1 )
print (x + y + z)
print (x + z + y)
Counter({'a': 5, 'b': 5, 'c': 2, 'd': 1, 'e': 1})
Counter({'a': 5, 'b': 5, 'e': 1, 'c': 1, 'd': 1})
ロバストな加算処理
値を置き換えるのではなく,順序ロバストにカウントを加算して計算したい場合は update メソッドを用いるほうが良いかもしれません
Code
from collections import Counter
x = Counter(a= 3 , b= 1 , c=- 2 )
y = Counter(a= 1 , b= 2 , c= 1 , d= 1 )
z = Counter(a= 1 , b= 2 , c= 2 , e= 1 )
c_total_xyz = Counter()
c_total_xzy = Counter()
for c1 in [x, y, z]:
c_total_xyz.update(c1)
for c2 in [x, z, y]:
c_total_xzy.update(c2)
# 計算結果
print (f"c_total_xyz: { c_total_xyz} " )
print (f"c_total_xzy: { c_total_xzy} " )
# 比較
print (c_total_xyz == c_total_xzy)
c_total_xyz: Counter({'a': 5, 'b': 5, 'c': 1, 'd': 1, 'e': 1})
c_total_xzy: Counter({'a': 5, 'b': 5, 'c': 1, 'e': 1, 'd': 1})
True