データ処理のデータ分類の際、よく使われるのが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関数の基本的な使い方からです。

例えば、以下のような配列があった場合、

[0,20,32,21,15,40,12,35,32,39,24,58,57,11,52,54,19]

これを以下のような区分に分けるとします。

0 \cdots 19,20\cdots 39,40\cdots 59

この場合、以下のように境界値を指定する必要があります。

-1, 19, 39, 59

最小値を含まないような境界値を指定すれば良いのです。

では実際に関数を使って分類して行きます。

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関数は領域を予め指定することで、指定した領域にデータを当てはめて行きます。

どちらもデータの分割やヒストグラムの作成などでは良く使われる関数となっています。また、グルーピングでこれらの結果を用いることもできるので分類した領域ごとの統計量を知ることができたり、領域ごとに統計処理を行うことが可能となります。

参考