list型(リスト)の中にどの要素が何個あるのか知りたい時は、collections.Counter()を使うと便利です。リストに含まれる要素とその数が辞書形式でまとめて取得できます。
また、この関数によって生成されるCounterオブジェクトは、頻度の高い要素順に並べたり、別のリストの要素のカウントを後から加えたり、特定の要素のカウントを除いたりもできます。
今回はこれらCounterオブジェクトの使い方についてまとめます。
確認した環境
- OS: Ubuntu16.04LTS
- Python3.7.2@Anaconda
リストに含まれる要素とカウント数を取得(Counterオブジェクト)
collections.Counter()を使います。
- 標準の組込みライブラリなので、新たにモジュールをインストールする必要はありません。
- リストやタプル、文字列等のハッシュ可能なオブジェクトを引数に渡します。
- 戻り値は、要素をkey、カウント数をvalueとした(key, value)ペアを格納したCounterオブジェクトです。
以下に具体例を示します。
# collectionsモジュールをインポート
>>> import collections
# 文字列に含まれる文字の個数を数える
>>> collections.Counter('abcd')
Counter({'a': 1, 'b': 1, 'c': 1, 'd': 1})
# リストのそれぞれの要素が何個あるか数える
>>> collections.Counter(['a', 'b', 'c', 'a', 'b', 'a'])
Counter({'a': 3, 'b': 2, 'c': 1})
また、引数としてmapping(辞書型やキーワード引数の形式)を設定すると、Counterオブジェクトを直接生成することができます。
以下に例を示します。
# collectionsモジュールをインポート
>>> import collections
# 辞書型で設定
>>> collections.Counter({'a':1, 'b':2})
Counter({'b': 2, 'a': 1})
# キーワード引数で設定
>>> collections.Counter(c=3,d=4)
Counter({'d': 4, 'c': 3})
# 空の場合は空のCounterオブジェクトが返ります。
>>> collections.Counter()
Counter()
<参考> 公式リファレンス: collections — コンテナデータ型 Counter オブジェクト
Counterオブジェクトの特徴
Counterオブジェクトの特徴をいくつか挙げます。
- カウントは、0 や負のカウントを含む整数値も可能。
>>> l = collections.Counter({'a':0, 'b':-2})
>>> l
Counter({'a': 0, 'b': -2})
- 存在しない要素に対しては、KeyErrorを送出する代わりに0を返します。
>>> l = collections.Counter({'a':1, 'b':2})
>>> l
Counter({'b': 2, 'a': 1})
>>> l['c']
0
- カウントを0に設定しても要素はCounterオブジェクトから取り除かれません。削除するにはdelを使います。
>>> l = collections.Counter({'a':1, 'b':2})
>>> l['a'] = 0
>>> l
Counter({'b': 2, 'a': 0})
# 要素('a')のカウントを削除する
>>> del l['a']
>>> c
Counter({'b': 2})
Counterオブジェクトで使えるメソッド
辞書型と同じメソッド
>>> l = collections.Counter({'a':1, 'b':2})
# keyのリストを取得
>>> l.keys()
dict_keys(['a', 'b'])
# valueのリストを取得
>>> l.values()
dict_values([1, 2])
# (key, value)ペアを取得
>>> l.items()
dict_items([('a', 1), ('b', 2)])
要素をカウントが多いものから小さいものまで順に並べたリストを返す
most_common([n])を使います。
引数nは、要素を何個返すかを表します。省略かNoneの場合は全部返します。
等しい場合は任意の順序になります。
>>> import collections
# Counterオブジェクトを生成(どの要素が何個あるか)
>>> collections.Counter('abbcabbbccca')
Counter({'b': 5, 'c': 4, 'a': 3})
# 引数nを省略すると全部出力
>>> collections.Counter('abbcabbbccca').most_common()
[('b', 5), ('c', 4), ('a', 3)]
# 引数n=2と設定すると、個数の多い順に2個出力
>>> collections.Counter('abbcabbbccca').most_common(2)
[('b', 5), ('c', 4)]
要素から他のイテラブルの要素をひく
subtract([iterable-or-mapping])を使います。
カウントは置き換えではなく、引かれます。
# 元のCounterオブジェクト
>>> c1 = collections.Counter('abbcabbbccca')
>>> c1
Counter({'b': 5, 'c': 4, 'a': 3})
# 要素のカウンターを引きます
>>> c1.subtract({'b':1, 'c':1, 'a':1})
>>> c1
Counter({'b': 4, 'c': 3, 'a': 2})
# カウンター値は0やマイナスも可能。存在しない要素を除算するとマイナスになる
>>> c1.subtract({'e':1, 'c':3, 'a':3})
>>> c1
Counter({'b': 4, 'c': 0, 'a': -1, 'e': -1})
同じ動作はマイナス(‘-‘)演算子を使っても出来ます
>>> c1 = collections.Counter({'b': 5, 'c': 4, 'a': 3})
>>> c2 = collections.Counter({'b': 1, 'c': 1, 'a': 1})
# マイナス演算子を使っても可能
>>> c1 - c2
Counter({'b': 4, 'c': 3, 'a': 2})
要素に他のイテラブルの要素を足す
update([iterable-or-mapping])を使います。
要素のカウントが加算されます。
# 元のCounterオブジェクト
>>> c1 = collections.Counter('abbcabbbccca')
>>> c1
Counter({'b': 5, 'c': 4, 'a': 3})
# 足し算します(mappingを引数に設定)
>>> c1.update({'a':1, 'b':1, 'c':1})
>>> c1
Counter({'b': 6, 'c': 5, 'a': 4})
# 上記と等価(iterableを引数に設定)
>>> c1.update('abc')
>>> c1
Counter({'b': 7, 'c': 6, 'a': 5})
また、同じ動作は+演算子を使っても実現できます。
>>> c1 = collections.Counter({'b': 5, 'c': 4, 'a': 3})
>>> c2 = collections.Counter({'b': 1, 'c': 1, 'a': 1})
>>> c1 + c2
Counter({'b': 6, 'c': 5, 'a': 4})
Counterオブジェクトから、要素をカウント分だけ繰り返すイテレータを作る
elements()を使います。
要素は任意の順序で返されます。ある要素のカウントが 1 未満なら、 elements() はそれを無視します。
ちょっと分かり辛いので、具体的に見てみます。
>>> c1
Counter({'b': 5, 'c': 4, 'a': 3})
# リストに変換します
>>> list(c1.elements())
['b', 'b', 'b', 'b', 'b', 'c', 'c', 'c', 'c', 'a', 'a', 'a']
応用例
テクノロジー系メディアで有名なTechCrunchのある記事に出現する単語の頻度を大きい順に10個出力してみました。
尚、今回は簡単のため、単語の変化形(過去形、過去分詞や複数形)は無視しており、これらは別単語としてカウントしています。
# -*- coding: utf-8 -*-
import requests
import bs4
import collections
import pprint
# 記事のURL
url = 'https://techcrunch.com/2019/03/09/the-other-smartphone-business/'
# webサイトデータの取得
res = requests.get(url)
# BeautifulSoupで記事を抽出
# 記事の構成上、一つの段落を要素としたリストが出力されます
soup = bs4.BeautifulSoup(res.text, 'lxml')
script = soup.select("div.article-content")
# Counterオブジェクトの生成
count = collections.Counter()
# 要素(この場合は段落)毎の各単語のカウント数を順次加算
for l in script:
count += collections.Counter(l.getText().lower().split())
# pprintで見やすく出力
pprint.pprint(count.most_common(10))
出力結果は以下です。
[('the', 179),
('to', 140),
('and', 95),
('in', 94),
('a', 89),
('of', 86),
('is', 68),
('—', 57),
('that', 53),
('it', 47)]
結果の内容の有用性はともかく、、それっぽい結果が得られました。
実際には、今回無視した単語の変化形や、冠詞・前置詞の扱いなどをうまく考慮すると、もう少し有用な結果が得られると思います。
参考までに、上記例で使った内容は以下の記事でもまとめています。
- reqestsモジュールを使ったWebサイトのデータ取得
- BeautifuleSoupを使ったWebスクレイピング
- pprintを使って見やすく出力する
まとめ
collections.Counterオブジェクトを用いて、シーケンスデータの要素がそれぞれ何個あるかを簡単に求める方法についてまとめました。