データ分析の現場では、連続的な数値データを特定のグループに分類して分析したい場面が多々あります。
例えば、顧客の年齢を「20代」「30代」といった年代別に分けたり、売上金額を「低・中・高」のランクに分類したりする操作です。
Pythonのデータ解析ライブラリであるPandasには、このような「ビニング(階級分け)」を簡単に行うための関数が用意されています。
本記事では、主要な手法であるpd.cutとpd.qcutの具体的な使い方と、それぞれの使い分けについて詳しく紹介します。
データの特徴に合わせて最適なビニング手法を選択できるようになることで、分析の質を一層高めることが可能になります。
ビニング処理が必要とされる理由
連続的な数値データは、そのままでは特徴を捉えにくい場合があります。
例えば、1円単位の売上データをそのまま集計しても、ノイズが多く傾向が見えてきません。
数値をカテゴリ化することで、特定のセグメントごとの傾向を可視化しやすくなります。
また、機械学習のモデル構築においても、数値をカテゴリ変数に変換することで精度が向上するケースがあります。
Pandasでは、このカテゴリ化を「ビニング」と呼び、非常にシンプルなコードで実装できます。
pd.cut:指定した値の間隔で分割する
pd.cutは、データの値そのものに基づいて区間を区切る手法です。
最大の特徴は、各ビンの「幅(間隔)」が等しくなるように分割することです。
例えば、0歳から100歳までのデータを10歳刻みで分けたい場合に適しています。
pd.cutの基本的な使い方
まずは、リスト形式のデータを4つのビンに等間隔で分割する例を見てみましょう。
import pandas as pd
# サンプルデータの作成
data = [10, 20, 25, 40, 55, 60, 85, 90, 100]
# 3つのビンに等間隔で分割
bins = pd.cut(data, bins=3)
print(bins)
[(9.91, 40.0], (9.91, 40.0], (9.91, 40.0], (9.91, 40.0], (40.0, 70.0], (40.0, 70.0], (70.0, 100.0], (70.0, 100.0], (70.0, 100.0]]
Categories (3, interval[float64, right]): [(9.91, 40.0] < (40.0, 70.0] < (70.0, 100.0]]
実行結果を見ると、最小値付近から最大値までを3等分した区間が自動的に作成されていることがわかります。
境界値を指定して分割する
自動分割だけでなく、任意の数値を境界として指定することも可能です。
実務では、ビジネスルールに則った区切りを指定することが多いため、この方法が頻繁に使われます。
# 境界値をリストで指定(0-30, 30-60, 60-100)
cut_bins = [0, 30, 60, 100]
result = pd.cut(data, bins=cut_bins)
print(result)
[(0, 30], (0, 30], (0, 30], (30, 60], (30, 60], (30, 60], (60, 100], (60, 100], (60, 100]]
Categories (3, interval[int64, right]): [(0, 30] < (30, 60] < (60, 100]]
pd.qcut:データ件数で等分に分割する
pd.qcutは、各グループに含まれるデータの「個数」が同じになるように分割する手法です。
「上位25%」「中位50%」「下位25%」といった分位点(クオンタイル)に基づいた分析を行いたい場合に強力な武器となります。
データに偏りがある場合でも、各ビンのサンプルサイズを均一に保てるメリットがあります。
pd.qcutの基本的な使い方
データを4つのグループ(四分位)に分けるコードを確認しましょう。
# サンプルデータの作成(値に偏りがある場合)
data_q = [1, 2, 2, 3, 3, 3, 4, 10, 50, 100]
# データを4等分(件数ベース)
q_bins = pd.qcut(data_q, q=4)
print(q_bins.value_counts())
(0.999, 2.25] 3
(2.25, 3.0] 3
(3.0, 8.5] 2
(8.5, 100.0] 2
dtype: int64
このように、値の大きさではなく、データの出現頻度に合わせて境界が自動調整されます。
ラベルを付与して分かりやすくする
デフォルトでは「(0, 30]」のような数学的な区間名がラベルとして付与されます。
これではレポート作成時に直感的ではないため、labels引数を使って独自の名称を付けましょう。
# ラベル名のリストを作成
group_names = ['Low', 'Medium', 'High']
# ラベルを指定してビニング
labeled_bins = pd.cut(data, bins=3, labels=group_names)
print(labeled_bins)
['Low', 'Low', 'Low', 'Low', 'Medium', 'Medium', 'High', 'High', 'High']
Categories (3, object): ['Low' < 'Medium' < 'High']
ラベルを指定することで、分析結果の解釈が非常にスムーズになります。
cutとqcutの比較表
どちらの手法を使うべきか迷った際は、以下の比較表を参考にしてください。
| 機能 | pd.cut | pd.qcut |
|---|---|---|
| 分割の基準 | 値の間隔(等幅) | データの個数(等頻度) |
| 主な用途 | 年代別、スコアランク別など | 偏差値、上位・下位グループ分け |
| 外れ値の影響 | 受けやすい(ビンが空になる可能性) | 受けにくい(個数は維持される) |
| 境界の指定 | 明示的にリストで指定可能 | パーセンタイルで指定可能 |
実践例:売上データのランク付け
実際のビジネスデータを想定した活用例を紹介します。
以下のコードでは、売上金額に応じて顧客を「シルバー」「ゴールド」「プラチナ」の3つのランクに分類しています。
import pandas as pd
# 顧客売上データ
df = pd.DataFrame({
'customer_id': range(1, 11),
'sales': [1500, 12000, 8000, 45000, 2000, 30000, 5000, 18000, 60000, 9000]
})
# 売上に基づいたランク分け(等頻度)
df['rank'] = pd.qcut(df['sales'], q=3, labels=['Silver', 'Gold', 'Platinum'])
# ランクごとの平均売上を集計
summary = df.groupby('rank')['sales'].mean()
print(df)
print("\n--- Summary ---")
print(summary)
customer_id sales rank
0 1 1500 Silver
1 2 12000 Gold
2 3 8000 Silver
3 4 45000 Platinum
4 5 2000 Silver
5 6 30000 Platinum
6 7 5000 Silver
7 8 18000 Gold
8 9 60000 Platinum
9 10 9000 Gold
--- Summary ---
rank
Silver 4125.0
Gold 13000.0
Platinum 45000.0
Name: sales, dtype: float64
この手法を用いれば、「上位1/3のプラチナ顧客層の平均売上」といった指標を即座に算出できます。
注意点:重複する境界値
pd.qcutを使用する際、データに同じ値が大量に含まれていると「Bin edges must be unique」というエラーが発生することがあります。
これは、データの個数で分けようとした結果、境界値が重複してしまうために起こります。
その場合は、duplicates='drop'という引数を指定することで、重複した境界値を一つにまとめてエラーを回避できます。
ただし、指定したグループ数よりも実際のビン数が少なくなる点には注意が必要です。
まとめ
Pandasのビニング手法であるpd.cutとpd.qcutは、データの性質を理解するための強力なツールです。
値の範囲で区切りたい場合はpd.cutを、データの分布密度で区切りたい場合はpd.qcutを選択しましょう。
また、適切なラベルを付与することで、その後の集計操作や可視化が格段に扱いやすくなります。
日々のデータ分析業務において、まずは手元のデータをこれらの関数でグループ化し、新たなインサイトを探ってみてください。
