Online Retailのデータを用いて、RFM分析を実装してみた

# はじめに
今回は、「ChatGPTにハンズオンを作らせてみた」の第11弾で、Online Retailのオープンデータを用いて、RFM分析の実装を練習しました。

第10弾はこちら↓

https://qiita.com/UKI_datascience/items/c2f7127c5028d732b5a2

# RFM分析
「**最近の購入**（Recency）」、「**購入頻度**（Frequency）」、「**購入金額**（Monetary）」の3つの軸を用いて顧客を評価し、マーケティング戦略を最適化するための手法です。

# 使用データ
今回は、UCI Machine Learning Repositoryから[Online Retail](https://archive.ics.uci.edu/dataset/352/online+retail)をダウンロードして、それを利用しました。

各変数の説明はこちらです。

| 変数  | 説明    |
|------|---------|
| **InvoiceNo** | 注文番号  |
| **StockCode** | 商品コード  |
| **Description** | 商品名  |
| **Quantity** | 購入数量  |
| **InvoiceDate** | 注文日時  |
| **UnitPrice** | 単価  |
| **CustomerID** | 顧客ID  |
| **Country** | 国  |

# 使用コード・分析結果
```python
import pandas as pd

# データの読み込み
data = pd.read_csv('Online Retail.csv')
```

顧客IDがないレコードを削除したうえで、「最終購入からの経過日数」「購入回数」「総購入金額」を算出します。

```python
df = data.dropna(subset=['CustomerID'])
df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate'])

df = df[~df['InvoiceNo'].astype(str).str.contains('C', na=False)]

df['TotalPrice'] = df['Quantity'] * df['UnitPrice']

latest_date = df['InvoiceDate'].max()

rfm = df.groupby('CustomerID').agg({
    'InvoiceDate': lambda x: (latest_date - x.max()).days,  # Recency（最終購入からの経過日数）
    'InvoiceNo': 'nunique', # Frequency（購入回数）
    'TotalPrice': 'sum' # Monetary（総購入金額）
})

rfm.columns = ['Recency', 'Frequency', 'Monetary']
```

RFMスコアを算出します。

```python
# RFMスコアのランク（1〜5のスコア付け）
rfm['R_score'] = pd.qcut(rfm['Recency'], 10, labels=[10, 9, 8, 7, 6, 5, 4, 3, 2, 1])
rfm['F_score'] = pd.qcut(rfm['Frequency'].rank(method='first'), 10, labels=[1, 2, 3, 4, 5, 6, 7, 8, 9,10])
rfm['M_score'] = pd.qcut(rfm['Monetary'], 10, labels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
```

標準化したうえで、階層クラスタリングを実装します。今回は、4つに分類しました。

```python
from sklearn.preprocessing import StandardScaler
import scipy.cluster.hierarchy as sch
import matplotlib.pyplot as plt

# RFMスコアに基づいたデータの前処理
# 必要なカラムだけ抽出して標準化
rfm_data = rfm[['R_score', 'F_score', 'M_score']].astype(int)  # RFMスコアを整数型に変換

# 標準化（スケールを統一するため）
scaler = StandardScaler()
rfm_scaled = scaler.fit_transform(rfm_data)

# 階層クラスタリングを実行（距離行列を計算）
linked = sch.linkage(rfm_scaled, method='ward')  # 'ward'は分散最小化法

# デンドログラムの作成
plt.figure(figsize=(10, 7))
sch.dendrogram(linked)
plt.title("Dendrogram")
plt.xlabel("Customer Index")
plt.ylabel("Distance")
plt.show()

# クラスタ数を決めてクラスタリングを行う
# ここでは4つのクラスタに分ける例です
from scipy.cluster.hierarchy import fcluster
clusters = fcluster(linked, 4, criterion='maxclust')

# クラスタ情報を元のデータフレームに追加
rfm['Cluster'] = clusters

# 結果の確認
rfm[['R_score', 'F_score', 'M_score', 'Cluster']].head()
```

クラスタごとの統計量から、それぞれの特徴を考察します。

```python
# RFMスコアを整数型に変換
rfm['R_score'] = rfm['R_score'].astype(int)
rfm['F_score'] = rfm['F_score'].astype(int)
rfm['M_score'] = rfm['M_score'].astype(int)

# クラスタごとの統計量を確認
cluster_summary = rfm.groupby('Cluster').agg({
    'R_score': 'mean', 
    'F_score': 'mean', 
    'M_score': 'mean',
    'Cluster': 'count'
})

cluster_summary
```

| Cluster | R_score  | F_score  | M_score  | Count |
|---------|---------|---------|---------|-------|
| 1       | 7.6287  | 3.2004  | 3.0844  | 474   |
| 2       | 2.6947  | 2.4148  | 2.8105  | 1261  |
| 3       | 4.1413  | 6.4543  | 6.3308  | 1182  |
| 4       | 8.4810  | 8.2089  | 7.9986  | 1422  |

- Cluster 1（R：7.63, F：3.20, M：3.08）
    - Recencyが高く、比較的最近購入していることを示しています。
    - FrequencyとMonetaryはやや低めで、購入頻度や金額は低めだと考えられます。
    - Cluster 1の顧客は、最近入ってきた新規顧客である可能性が高いです。
- Cluster 2（R：2.69, F：2.41, M：2.82）
    - Recency、Frequency、Monetaryともに最も低い。
    - Cluster 2の顧客は、しばらく購入していない離脱顧客である可能性が高いです。
- Cluster 3（R：4.14, F：6.45, M：6.33）
    - Recencyは中程度ですが、FrequencyとMonetaryはやや高めであることから、頻繁に高額購入を行っている可能性があり、非常に価値の高い顧客である可能性が高いです。
    - Cluster 3は、中程度の頻度で高額購入をしている優良顧客である可能性が高いです。
- Cluster 4（R：8.48, F：8.21, M：8.00）
    - Recency、Frequency、Monetaryともに最も高く、頻繁に購入しているグループになります。
    - Cluster 4は、頻繁に購入しているリピーターである可能性が高いです。

# おわりに
今回は、マーケティング領域で有用なRFM分析を練習しました。
各クラスタの考察がまだまだ浅い気がしますが、概念の理解と実装ができたので、ひとまずOKとしておこうと思います