データ処理のデータ分類の際、よく使われるのがqcut
関数とcut
関数です。
2つの関数は主に以下のような違いを持っています。
- qcut関数
指定した数だけデータを等分する - cut関数
指定した領域ごとにデータを分割する
どちらも元となるデータはSeriesまたは1次元データのみ となっているので、DataFrameで指定することはできないので注意しましょう。
qcut関数
区切り数を指定する
qcut
関数は基本的には元となるデータと区切り数を指定して使います。
In [1]: import pandas as pd
In [2]: sr = pd.Series([1,12,5,1,9,3,4,10,8])
In [3]: pd.qcut(sr, 3)
Out[3]:
0 (0.999, 3.667]
1 (8.333, 12.0]
2 (3.667, 8.333]
3 (0.999, 3.667]
4 (8.333, 12.0]
5 (0.999, 3.667]
6 (3.667, 8.333]
7 (8.333, 12.0]
8 (3.667, 8.333]
dtype: category
Categories (3, interval[float64]): [(0.999, 3.667] < (3.667, 8.333] < (8.333, 12.0]]
このような表示形式に戸惑う方がいるかもしれませんが、読み方が分かればそこまで難しくありません。
例えば、最初の0番目の要素である1
は(0.999,3.667]
と表示されていますが、これは0番目の要素が0.999より大きく、3.667以下の領域に属していることを示していることになります。
(
で表示されている部分は境界値を領域として含まないという意味で、]
は境界値を領域として含むという意味です。
これが9つの要素分入っていることになります。
value_counts
関数を使えばそれぞれ区切った区分ごとの件数を表示させることができます。
In [4]: pd.qcut(sr, 3).value_counts()
Out[4]:
(8.333, 12.0] 3
(3.667, 8.333] 3
(0.999, 3.667] 3
dtype: int64
ここの(7.667, 12.0]
というのは7.667より大きくて12.0以下の領域という意味になり、(
は境界となる値を含まず、]
は境界となる値を含む領域を示しています。
ラベルを表示する
例えば、先ほどの3つの区分で値を分類したので、それぞれ'low', 'middle', 'high'
のラベルをつけて表示させてみます。
In [5]: pd.qcut(sr, 3, labels=['low','middle','high'])
Out[5]:
0 low
1 high
2 middle
3 low
4 high
5 low
6 middle
7 high
8 middle
dtype: category
Categories (3, object): [low < middle < high]
ラベルを非表示にしたい場合はlabels=False
にすると、下から何番目の区分にあるかだけを表示させることができます。
In [7]: pd.qcut(sr, 3, labels=False) # 番号のみを表示させる
Out[7]:
0 0
1 2
2 1
3 0
4 2
5 0
6 1
7 2
8 1
dtype: int64
境界値を別途で取得する
retbins=True
にすると境界値を別途で取得できます。
In [8]: sr_cut, bins = pd.qcut(sr, 3, retbins=True)
In [9]: sr_cut
Out[9]:
0 (0.999, 3.667]
1 (8.333, 12.0]
2 (3.667, 8.333]
3 (0.999, 3.667]
4 (8.333, 12.0]
5 (0.999, 3.667]
6 (3.667, 8.333]
7 (8.333, 12.0]
8 (3.667, 8.333]
dtype: category
Categories (3, interval[float64]): [(0.999, 3.667] < (3.667, 8.333] < (8.333, 12.0]]
In [10]: bins
Out[10]: array([ 1. , 3.66666667, 8.33333333, 12. ])
精度を指定する
precision
引数で小数点以下いくつまで考えるかを指定することができます。
デフォルトではprecision=3
となっており、ここの数を増やしたり減らしたりすることで精度を調整できます。
In [11]: pd.qcut(sr, 3, precision=1) # 小数点以下1桁のみ
Out[11]:
0 (0.9, 3.7]
1 (8.3, 12.0]
2 (3.7, 8.3]
3 (0.9, 3.7]
4 (8.3, 12.0]
5 (0.9, 3.7]
6 (3.7, 8.3]
7 (8.3, 12.0]
8 (3.7, 8.3]
dtype: category
Categories (3, interval[float64]): [(0.9, 3.7] < (3.7, 8.3] < (8.3, 12.0]]
In [12]: pd.qcut(sr,3,precision=4) # 小数点以下4桁
Out[12]:
0 (0.9999, 3.6667]
1 (8.3333, 12.0]
2 (3.6667, 8.3333]
3 (0.9999, 3.6667]
4 (8.3333, 12.0]
5 (0.9999, 3.6667]
6 (3.6667, 8.3333]
7 (8.3333, 12.0]
8 (3.6667, 8.3333]
dtype: category
Categories (3, interval[float64]): [(0.9999, 3.6667] < (3.6667, 8.3333] < (8.3333, 12.0]]
cut関数
続いてcut
関数です。細かい引数の使い方はqcut
とほとんど変わりありません。
一番の大きな違いは冒頭でも触れたように、分割領域を指定できるというところです。
予め決めておいた区分分けが存在している場合はこちらを使うとよいでしょう。
区分を指定する
ではcut
関数の基本的な使い方からです。
例えば、以下のような配列があった場合、
これを以下のような区分に分けるとします。
この場合、以下のように境界値を指定する必要があります。
最小値を含まないような境界値を指定すれば良いのです。
では実際に関数を使って分類して行きます。
In [15]: age_list = pd.Series([0,20,32,21,15,40,12,35,32,39,24,58,57,11,52,54,19])
In [16]: age_list
Out[16]:
0 0
1 20
2 32
3 21
4 15
5 40
6 12
7 35
8 32
9 39
10 24
11 58
12 57
13 11
14 52
15 54
16 19
dtype: int64
In [17]: pd.cut(age_list, bins=[-1,19,39,59])
Out[17]:
0 (-1, 19]
1 (19, 39]
2 (19, 39]
3 (19, 39]
4 (-1, 19]
5 (39, 59]
6 (-1, 19]
7 (19, 39]
8 (19, 39]
9 (19, 39]
10 (19, 39]
11 (39, 59]
12 (39, 59]
13 (-1, 19]
14 (39, 59]
15 (39, 59]
16 (-1, 19]
dtype: category
Categories (3, interval[int64]): [(-1, 19] < (19, 39] < (39, 59]]
In [18]: pd.cut(age_list, bins=[-1,19,39,59]).value_counts() # それぞれの区分ごとのデータの個数を見る
Out[18]:
(19, 39] 7
(39, 59] 5
(-1, 19] 5
dtype: int64
ラベルを指定する
qcut
関数と同様、こちらもラベルを指定することができます。
'young', 'young-adult', 'adult'
という風にラベルづけしてみましょう。
In [19]: pd.cut(age_list,bins=[-1,19,39,59],labels=['young','young-adult','adult'])
Out[19]:
0 young
1 young-adult
2 young-adult
3 young-adult
4 young
5 adult
6 young
7 young-adult
8 young-adult
9 young-adult
10 young-adult
11 adult
12 adult
13 young
14 adult
15 adult
16 young
dtype: category
Categories (3, object): [young < young-adult < adult]
labels=False
にすれば下から何番目の区分に入っているかを見ることができます。
In [25]: pd.cut(age_list,bins=[-1,19,39,59],labels=False)
Out[25]:
0 0
1 1
2 1
3 1
4 0
5 2
6 0
7 1
8 1
9 1
10 1
11 2
12 2
13 0
14 2
15 2
16 0
dtype: int64
領域の開区間の方向を変える
データを分割する際に指定される領域は半開区間で、右側の区間が閉じており(境界を領域として含み)、左側の区間が開いている(境界を領域として含まない)状態となっています。
これを逆にしたい場合はright=False
(デフォルトではright=True
)と指定します。では、先ほどの区分けをright=False
にしてみましょう。
In [20]: pd.cut(age_list,bins=[-1,19,39,59],right=False) # right=Falseにする
Out[20]:
0 [-1, 19)
1 [19, 39)
2 [19, 39)
3 [19, 39)
4 [-1, 19)
5 [39, 59)
6 [-1, 19)
7 [19, 39)
8 [19, 39)
9 [39, 59)
10 [19, 39)
11 [39, 59)
12 [39, 59)
13 [-1, 19)
14 [39, 59)
15 [39, 59)
16 [19, 39)
dtype: category
Categories (3, interval[int64]): [[-1, 19) < [19, 39) < [39, 59)]
In [21]: pd.cut(age_list,bins=[-1,19,39,59],right=False).value_counts() # 人数の内訳を比較
Out[21]:
[19, 39) 7
[39, 59) 6
[-1, 19) 4
dtype: int64
In [22]: pd.cut(age_list,bins=[-1,19,39,59],right=True).value_counts() # 人数の内訳を比較
Out[22]:
(19, 39] 7
(39, 59] 5
(-1, 19] 5
dtype: int64
このように、区間の設定の仕方を変えたので、先ほどと異なった結果になっていることがわかります。この場合は、bins
で区間の指定の仕方を変更する必要があり、今度は区間ごとの最小値を境界値となるように値を指定します。
In [23]: bins = [0,20,40,60]
In [24]: pd.cut(age_list,bins=bins,right=False).value_counts()
Out[24]:
[20, 40) 7
[40, 60) 5
[0, 20) 5
dtype: int64
データの見た目としてはこちらの方が区切りが良く、みやすくなってますね。
分割した領域に沿ってグルーピング
ではqcut
関数やcut
関数を使って分割した値を元にgroupby
関数を使ってグルーピングを行なって行きます。
In [27]: import numpy as np
In [28]: score_list = np.random.randint(0,100,size=17) # 整数の乱数を17個生成
In [29]: df = pd.DataFrame({'age':age_list,'score':score_list})
In [30]: df
Out[30]:
age score
0 0 49
1 20 55
2 32 98
3 21 94
4 15 69
5 40 28
6 12 94
7 35 92
8 32 47
9 39 77
10 24 5
11 58 51
12 57 79
13 11 47
14 52 14
15 54 57
16 19 74
In [31]: cut = pd.qcut(df['age'], 4)
In [32]: df.groupby(cut).mean() # 分割した領域ごとの平均を求める
Out[32]:
age score
age
(-0.001, 19.0] 11.40 66.600000
(19.0, 32.0] 25.80 59.800000
(32.0, 40.0] 38.00 65.666667
(40.0, 58.0] 55.25 50.250000
cut
関数でも同様の処理ができます。
In [33]: cut = pd.cut(df['age'],bins=[-1,19,39,59],labels=['low','middle','high'
...: ])
In [34]: df.groupby(cut).mean()
Out[34]:
age score
age
low 11.4 66.600000
middle 29.0 66.857143
high 52.2 45.800000
まとめ
今回は、データを指定した領域に分類していくqcut
関数とcut
関数、2つの関数の使い方について開設しました。
qcut
関数は領域を指定するのではなく、いくつの全体を分割するのかを指定することによってデータを分割し、cut
関数は領域を予め指定することで、指定した領域にデータを当てはめて行きます。
どちらもデータの分割やヒストグラムの作成などでは良く使われる関数となっています。また、グルーピングでこれらの結果を用いることもできるので分類した領域ごとの統計量を知ることができたり、領域ごとに統計処理を行うことが可能となります。