リファクタリングをPythonで実践!基本手法とコード例・注意点まで
Pythonで配列(list)や辞書型(dict)を使うようになった時、知っておくと便利な機能にCounterがあります。
データサイエンス、Web、ホビー、いずれの場合でもCounterの知識があるとデータ整理のストレスが低減するでしょう。
Counterに関する基礎をご紹介します。
1.PythonのCounterとは?
まずはCounterの概要をご紹介します。
① Counterとは
Counterは、Pythonに標準で用意されているサブクラスの一つ。
Counter()にリスト型やタプル型のデータを渡すと、 Counterオブジェクトを返してくれます。
返り値には、キーとして元データの要素、キーの値には出現回数がセットされます。
②count()メソッドとの違い
すでに配列やタプルを学習された方は、count()メソッドをご存知の方もいらっしゃるでしょう。
count()とCounter、どちらも要素数に関係した処理で似ています。
count()は、特定の要素数を返してくれるメソッドに対して、Counterは元データ全体の要素とその数を紹介してくれます。
どちらもデータ処理には欠かせない機能なので、基礎はおさえておきましょう。
2.Counterの基本的な使い方
Counterの使い方、書き方をご紹介します。
① ライブラリのimport
Counterは、モジュール:collections内にあるdictのサブクラスです。
import文の書き方としては、
import collections
my_counter = collections.Counter(my_array)
もしくは
from collections import Counter
my_counter = Counter(my_array)
という書き方をします。
②基本構文
実際にCounterを使う前後でデータがどのように変わるかを確認してみましょう。
from collections import Counter
my_array = ['きゅうり', 'トマト', 'レタス', 'レタス', 'コーン', 'オニオン', 'きゅうり', 'きゅうり']
my_counter = Counter(my_array)
print(type(my_counter))
print(my_counter)
結果
<class 'collections.Counter'>
Counter({'きゅうり': 3, 'レタス': 2, 'トマト': 1, 'コーン': 1, 'オニオン': 1})
配列内の要素とその数が紹介されていることが確認できますね。
3.さまざまなデータ型の要素を数える
Counter()で処理できるデータ型は、「配列型(list)」「辞書型(dict)」「文字型(str)」「タプル(tuple)」の4種類。
それぞれのデータ型をCounterで処理してみます。
①list
from collections import Counter
my_array = [90, 100, 92, 87, 100, 93, 91, 90, 90]
my_counter = Counter(my_array)
print(type(my_counter))
print(my_counter)
結果
<class 'collections.Counter'>
Counter({90: 3, 100: 2, 92: 1, 87: 1, 93: 1, 91: 1})
配列データの特徴を掴むときに役立ちそうです。
②dict
from collections import Counter
my_dict = {"okayama":1800000, "iwate":1210000, "tottori":550000, "okayama":1200000}
my_counter = Counter(my_dict)
print(type(my_counter))
print(my_counter)
結果
<class 'collections.Counter'>
Counter({'iwate': 1210000, 'okayama': 1200000, 'tottori': 550000})
辞書型データをCounterに入れると、キーは一つに集約されます。
上記の場合は、キー:okayamaが2つありますが、後方の値が割り当てられました。
また、キーの順番はabc順なことがわかります。
③str
from collections import Counter
my_str = "I Love Python"
my_counter = Counter(my_str)
print(type(my_counter))
print(my_counter)
結果
<class 'collections.Counter'>
Counter({' ': 2, 'o': 2, 'I': 1, 'L': 1, 'v': 1, 'e': 1, 'P': 1, 'y': 1, 't': 1, 'h': 1, 'n': 1})
文字列をCounterで処理すると、どの文字が何回使われていたかを確認できます。
④tuple
from collections import Counter
my_tuple = (90, 100, 92, 87, 100, 93, 91, 90, 90)
my_counter = Counter(my_tuple)
print(type(my_counter))
print(my_counter)
結果
<class 'collections.Counter'>
Counter({90: 3, 100: 2, 92: 1, 87: 1, 93: 1, 91: 1})
タプルの場合は、配列と同じ結果になっていますね。
4.メソッドを使って処理する
Counterは、単にキーとその値を一覧表示するだけでなく、最もよく使われているキーを取得するなど、より応用的な使い方もできます。
Counterで使用できるメソッドを紹介します。
①keys()メソッドでキーを取得
Counterで取得したデータのキー一覧を確認する際は、keys()が便利です。
from collections import Counter
my_array = ['きゅうり', 'トマト', 'レタス', 'レタス', 'コーン', 'オニオン', 'きゅうり', 'きゅうり']
my_counter = Counter(my_array)
my_counter_keys = my_counter.keys()
print(type(my_counter_keys))
print(my_counter_keys)
結果
<class 'dict_keys'>
dict_keys(['きゅうり', 'トマト', 'レタス', 'コーン', 'オニオン'])
keys()メソッドを使うことで、データ型が「<class ‘collections.Counter’>」から「<class ‘dict_keys’>」に変わっていますね。
②values()メソッドで値を取得
Counterで取得したデータの値一覧を確認する際は、values()を使用できます。
from collections import Counter
my_array = ['きゅうり', 'トマト', 'レタス', 'レタス', 'コーン', 'オニオン', 'きゅうり', 'きゅうり']
my_counter = Counter(my_array)
my_counter_values = my_counter.values()
print(type(my_counter_values))
print(my_counter_values)
結果
<class 'dict_values'>
dict_values([3, 1, 2, 1, 1])
元データ内にキーが何個含まれているかを確認できました。
② items()メソッドでキーと値のセットを取得
キーと値をセットで取得したい場合は、items()が便利です。
from collections import Counter
my_array = ['きゅうり', 'トマト', 'レタス', 'レタス', 'コーン', 'オニオン', 'きゅうり', 'きゅうり']
my_counter = Counter(my_array)
my_counter_items = my_counter.items()
print(type(my_counter_items))
print(my_counter_items)
print(list(my_counter_items)[0])
結果
<class 'dict_items'>
dict_items([('きゅうり', 3), ('トマト', 1), ('レタス', 2), ('コーン', 1), ('オニオン', 1)])
('きゅうり', 3)
元データ内で一番使われているキーとその値を、セットで取得しました。
④elements()で元の要素を持つイテレータを取得
配列やタプルをCounter処理した後に、元のデータに変換する方法としてelements()があります。
from collections import Counter
my_array = [90, 100, 92, 87, 100, 93, 91, 90, 90]
my_counter = Counter(my_array)
print(my_counter)
my_counter_elements = my_counter.elements()
print(type(my_counter_elements))
print( list(my_counter_elements) )
結果
Counter({90: 3, 100: 2, 92: 1, 87: 1, 93: 1, 91: 1})
<class 'itertools.chain'>
[90, 90, 90, 100, 100, 92, 87, 93, 91]
Counter後、元のデータに戻っていますが、順番は変わります。
基本的に、元データを変数で保持しておけば、elements()を使う必要はないかもしれませんね。
⑤most_common()でカウントが多い順に並べたリストを取得
配列内で一番よく使われているキーとその値を簡単に取得できる方法として、most_common()があります。
from collections import Counter
my_array = ['きゅうり', 'トマト', 'レタス', 'レタス', 'コーン', 'オニオン', 'きゅうり', 'きゅうり']
my_counter = Counter(my_array)
my_counter_items = my_counter.most_common(1)
print(type(my_counter_items))
print(my_counter_items)
結果
<class 'list'>
[('きゅうり', 3)]
most_common(1)内の数字を2や3に変更すると、1から2、1から3のデータを配列型で取得できます。
⑥subtract()で要素の引き算
複数のCounterオブジェクトの統計を求めることもできます。
差を求める場合は、subtractメソッドを使用します。
from collections import Counter
my_array_1 = ['きゅうり', 'トマト', 'レタス', 'レタス', 'コーン', 'オニオン', 'きゅうり', 'きゅうり']
my_array_2 = ['きゅうり', 'トマト', 'きゅうり', 'きゅうり', 'きゅうり']
my_counter_1 = Counter(my_array_1)
my_counter_2 = Counter(my_array_2)
my_counter_1.subtract(my_counter_2)
print(my_counter_1)
結果
Counter({'レタス': 2, 'コーン': 1, 'オニオン': 1, 'トマト': 0, 'きゅうり': -1})
配列1(my_array_1)と配列2(my_array_2)の要素数の差が確認できました。
⑦update()で要素の足し算
複数のCounterオブジェクトの和を求める場合は、updateメソッドを使いましょう。
from collections import Counter
my_array_1 = ['きゅうり', 'トマト', 'レタス', 'レタス', 'コーン', 'オニオン', 'きゅうり', 'きゅうり']
my_array_2 = ['きゅうり', 'たまご', 'きゅうり', 'きゅうり']
my_counter_1 = Counter(my_array_1)
my_counter_2 = Counter(my_array_2)
my_counter_1.update(my_counter_2)
print(my_counter_1)
結果
Counter({'きゅうり': 6, 'レタス': 2, 'トマト': 1, 'コーン': 1, 'オニオン': 1, 'たまご': 1})
要素数の統計が確認できます。
5.演算子を使って処理する
複数のCounterオブジェクトの和や差は、updateメソッドやsubtractメソッド以外に+や-などの演算子でも処理できます。
① + で要素の足し算
2つのCounterオブジェクトを足してみます。
from collections import Counter
my_array_1 = ['きゅうり', 'トマト', 'レタス', 'レタス', 'きゅうり', 'きゅうり']
my_array_2 = ['きゅうり', 'トマト', 'ハム']
my_counter_1 = Counter(my_array_1)
my_counter_2 = Counter(my_array_2)
my_counter = my_counter_1 + my_counter_2
print(my_counter)
結果
Counter({'きゅうり': 4, 'トマト': 2, 'レタス': 2, 'ハム': 1})
要素数が加算されていることが確認できました。
② – で要素の引き算
2つのCounterオブジェクトの差を求めてみます。
from collections import Counter
my_array_1 = ['きゅうり', 'トマト', 'レタス', 'レタス', 'きゅうり', 'きゅうり']
my_array_2 = ['きゅうり', 'トマト', 'レタス', 'きゅうり', 'きゅうり']
my_counter_1 = Counter(my_array_1)
my_counter_2 = Counter(my_array_2)
my_counter = my_counter_1 - my_counter_2
print(my_counter)
結果
Counter({'レタス': 1})
要素数が削減されていますね。
subtract()メソッドとの違い
先程のCounterの引き算の処理を見てみると、元は存在したキー自体も消えていることがわかります。
(元)
my_array_1 = ['きゅうり', 'トマト', 'レタス', 'レタス', 'きゅうり', 'きゅうり']
my_array_2 = ['きゅうり', 'トマト', 'レタス', 'きゅうり', 'きゅうり']
(処理後)
Counter({'レタス': 1})
Counter演算の結果、要素数が0以下になる場合は、キーが消えます。
subtractの場合は、キーの値が0以下でもマイナス値で残りますので、演算子(-)とsubtractの違い、把握しておきましょう。
③ & で積集合を取得
Counter①にもCounter②にも両方共に含まれているキーとその値を求める場合は、積集合を意味する演算子&が使えます。
from collections import Counter
my_array_1 = ['きゅうり', 'トマト', 'レタス', 'レタス', 'きゅうり', 'きゅうり']
my_array_2 = ['きゅうり', 'ハム']
my_counter_1 = Counter(my_array_1)
my_counter_2 = Counter(my_array_2)
my_counter = my_counter_1 & my_counter_2
print(my_counter)
結果
Counter({'きゅうり': 1})
ハムは、my_counter_1には含まれていませんので、演算結果には含まれないことが確認できますね。
④ | で和集合を取得
Counter①とCounter②のキーを合わせたいときは、和集合が便利です。
from collections import Counter
my_array_1 = ['きゅうり', 'トマト', 'レタス', 'レタス', 'きゅうり', 'きゅうり']
my_array_2 = ['きゅうり', 'ハム']
my_counter_1 = Counter(my_array_1)
my_counter_2 = Counter(my_array_2)
my_counter = my_counter_1 | my_counter_2
print(my_counter)
結果
Counter({'きゅうり': 3, 'レタス': 2, 'トマト': 1, 'ハム': 1})
キーは合わさっても、その値は合わさらない点に注意しましょう。
③ == で等しいか判定
2つのCounterオブジェクトが、同じキーと値をもつか確認したいときは、==演算子を使用できます。
from collections import Counter
my_array_1 = ['きゅうり', 'トマト', 'レタス', 'レタス', 'きゅうり', 'きゅうり']
my_array_2 = ['きゅうり', 'ハム']
my_array_3 = ['ハム', 'きゅうり']
my_counter_1 = Counter(my_array_1)
my_counter_2 = Counter(my_array_2)
my_counter_3 = Counter(my_array_3)
my_counter_boolean_1 = my_counter_1 == my_counter_2
print(my_counter_boolean_1)
my_counter_boolean_2 = my_counter_2 == my_counter_3
print(my_counter_boolean_2)
結果
False
True
if文の条件分岐処理と併せて使うときに便利ですね。
⑥ <= でオブジェクトを内包しているか判定
2つのCounterオブジェクトを比較するときに、①のCounterオブジェクト内に②のCounterオブジェクトは、含まれるか確認したいときもあるでしょう。
そのときは内包演算子(<=)が便利です。
結果
Python version
3.10.4 (main, Mar 31 2022, 08:41:55) [GCC 7.5.0]
True
Counterオブジェクト:my_counter_1内に、Counterオブジェクト:my_counter_2は含まれますのでTrueが出力されています。
今回に限りPythonバージョンを出力しているのは、Counterオブジェクトの内包演算子(<=)は、Python3.10からサポートしている機能だからです。
Python3.9などではエラーになりますのでご注意ください。