カテゴリ値を含むデータでのLightGBMを使った分析

# はじめに

時系列データを使った回帰問題にチャレンジしたことはあったのですが、分類問題はまだない。。。

というわけで、本稿では、**二値分類問題**にチャレンジします。
しかも、（カテゴリ値の処理方法も体験してみたかったので）カテゴリ値を含むデータで。

過去の投稿記事でもLightGBMを利用しているので、同じくLightGBMを利用して分析してゆきます。

## 本稿で紹介すること

- データの収集および整形
- 予測モデルの作成
- 予測精度の確認
- 特徴量重要度の確認（可視化）

## 本稿で紹介しないこと

- 特徴量設計
- ハイパーパラメータのチューニング

# 分析環境の各種情報

:::note info
筆者は、いつもながらDockerコンテナとしてJupyterを起動しています。
:::

- Python 3.8.8
- pip 21.3.1
- pandas 1.4.0
- numpy 1.22.1
- scikit-learn 1.0.2
- lightgbm 3.3.2 

# データの収集

こちらのサイトで公開されているCSVファイルを利用しました。

https://www.kaggle.com/wenruliu/adult-income-dataset/version/2

以下、上述サイトから抜粋、データに関する記載です。目的変数はIncome列、二値です。

```
An individual’s annual income results from various factors. Intuitively, it is influenced by the individual’s education level, age, gender, occupation, and etc.

This is a widely cited KNN dataset. I encountered it during my course, and I wish to share it here because it is a good starter example for data pre-processing and machine learning practices.

Fields
The dataset contains 16 columns
Target filed: Income
-- The income is divide into two classes: <=50K and >50K
Number of attributes: 14
-- These are the demographics and other features to describe a person

We can explore the possibility in predicting income level based on the individual’s personal information.
```

# データの整形

最も時間を割いたのは、このStepでした。有志の記事をいくつか参考にしながら、何とか動かせたというのが正直な感想です。

結局、どうするべきか否かがいまいち分からず。。。途方に暮れました。

いろいろ調べる過程[^1]で、Categorical Feature、one-hot encoding、Label Encodingというキーワードは出てきたのですが、、、

最終的な方針として、**「手作業でカテゴリ変数（String）を0始まりの整数（Int）に変換」**としました。

https://qiita.com/sinchir0/items/b038757e578b790ec96a

例えば、性別に対しての整形処理はこんな感じ。これをベースに説明変数の計8カラムを整形しました。

```python
gender = ['Female', 'Male']
{v:i for i, v in enumerate(gender)}
#{'Female': 0, 'Male': 1}

df['gender'] = df['gender'].map({v:i for i, v in enumerate(gender)}).astype(int)
```

ちなみに、目的変数も考え方は同じ。正解（'>50K'なら1、'<=50K'なら0）を割り当てました。

```python
income_map = {'<=50K':0, '>50K':1}
df['income'] = df['income'].map(income_map).astype(int)
```

# 予測モデルの作成

データの分割ですが、時系列データではないためsklearn.model_selection.train_test_splitを使いました。

https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html

LightGBMを利用した大きな流れは、以下の記事に掲載のPythonコード[^2]を参考にさせていただきました。

https://qiita.com/d_desuyon/items/807e01311ad08570ee78

```python
X = df.drop(['income'], axis=1)
y = df['income']

X_train, X_test , y_train, y_test  = train_test_split(X      , y      , test_size=0.2, shuffle=False)
X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.2, shuffle=False)

# データセットを生成する
lgb_train = lgb.Dataset(X_train, y_train)
lgb_valid = lgb.Dataset(X_valid, y_valid, reference=lgb_train)

# LightGBM のハイパーパラメータ
params = {
    # 二値分類問題
    'objective': 'binary',
    # AUC の最大化を目指す
    'metric': 'auc',
    # Fatal の場合出力
    'verbosity': -1,
    # 乱数シード
    'seed': 31,
    # 学習率
    'learning_rate': 0.02,
}

# 上記のパラメータでモデルを学習する
model = lgb.train(params, lgb_train, valid_sets=lgb_valid,
                  verbose_eval=50,  # 50イテレーション毎に学習結果出力
                  num_boost_round=10000,  # 最大イテレーション回数指定
                  early_stopping_rounds=100
                 )
```

# 予測精度の確認

こちらのサイトで公開されているPythonコード[^3]を参考にしました。

https://qiita.com/kt38k/items/3c0ee4251475b6407007

以下のPythonコードでROC曲線と混合行列を確認しました。
ほぼ原文ままで、変数名とAPI参照を直したのみです。

```python
# テストデータを予測する
y_pred = model.predict(X_test, num_iteration=model.best_iteration)

# AUCを計算
fpr, tpr, thresholds = roc_curve(np.asarray(y_test), y_pred)
print("AUC", auc(fpr, tpr))

# ROC曲線をプロット
plt.plot(fpr, tpr, label='ROC curve (area = %.2f)' %auc(fpr, tpr))
plt.legend()
plt.title('ROC curve')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.grid(True)
plt.show()

# accuracy, precisionを計算
acc = accuracy_score(np.asarray(y_test), np.round(y_pred))
precision = precision_score(np.asarray(y_test), np.round(y_pred))
print("accuracy", acc)
print("precision", precision)

# 混同行列をプロット
y_pred = np.round(y_pred)
cm = confusion_matrix(np.asarray(y_test), np.where(y_pred < 0.5, 0, 1))
cmp = ConfusionMatrixDisplay(cm, display_labels=[0,1])
cmp.plot(cmap=plt.cm.Blues)
plt.show()
```

実行すると、こんな感じ。想像以上に精度が良かった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/f1c6bc99-be45-d0a5-8f89-e5e799f44aaf.png)

# 特徴量重要度の確認（可視化）

特徴量重要度の確認ですが、lightgbm.plot_importanceを使いました。

https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.plot_importance.html

https://mathmatical22.xyz/2020/04/12/%E3%80%90%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91%E3%80%91%E7%89%B9%E5%BE%B4%E9%87%8F%E9%87%8D%E8%A6%81%E5%BA%A6%E3%81%AE%E7%AE%97%E5%87%BA-lightgbm-%E3%80%90python%E3%80%91%E3%80%90%E6%A9%9F/

以下のPythonコードで重要度の高い特徴量から可視化しました。

```python
# 重要度としては「特徴量が分岐（ノード）の条件式で使用された回数」（＝デフォルト）
lgb.plot_importance(model, figsize=(30, 15), max_num_features=30, importance_type='split')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/c51fc2a2-1462-8e16-47e1-fbdb77daf6c3.png)

```python
# 重要度としては「特徴量がある分岐（ノード）において目的関数の改善に寄与した度合い」
lgb.plot_importance(model, figsize=(30, 15), max_num_features=30, importance_type='gain')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/3277039a-f24e-f9f3-6574-643458cea7f9.png)

特に、2つ目の方式、gainの方をもう少し詳しく確認しました[^4]。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/9da592ce-4d48-e883-fb38-459bba9ae6e3.png)

# 結果について少し考察

上位の特徴（説明変数[^5]）についてです。
改めてデータの概説を確認すると、家族構成（婚姻関係の有無）が大まかに分かる様子です。つまり、家庭があるから収入が高いのではなく、収入が高いから家庭をもてたのだろうな、、、と思いました。
> relationship: Wife, Own-child, Husband, Not-in-family, Other-relative, Unmarried.
> marital-status: Married-civ-spouse, Divorced, Never-married, Separated, Widowed, Married-spouse-absent, Married-AF-spouse.

他だと、
投資で利益を得ている人が多いのかな、、、と思いました。（これも、収入があるから投資に回しているのでは。。。）
教育を受けた期間が長いほど、収入の高い職に就いた人が多いのかな、、、これは当然というか納得感がありますね。（学歴社会だから。。。）

# Notebook（Pythonコード）

githubで公開しました。このタイミングですが、参考サイトの方々に感謝申し上げます。

https://github.com/Blaster36/Script/blob/main/Kaggle/pred50k-1.ipynb

# まとめ

カテゴリ値を含むデータでのLightGBMを使った分析の例をご紹介。
別ケース、「手作業でカテゴリ変数（String）を0始まりの整数（Int）に変換」後に「DataFrameの当該列のデータ型をカテゴリ（category）に変更」した場合でも予測モデルを作成しましたが、優位な精度差はありませんでした。（LightGBMの動きとしてはどうなんでしょう。。。もう少し深追いしてみた方が良さそう。）

Pythonプログラムだけでデータ分析を試みていますが、正直手間ですね。だからこそ、AutoMLという製品・サービスに一定の需要があるのだろうな、、、と感じました。

[^1]: 全体のまとめ | https://qiita.com/sinchir0/items/b038757e578b790ec96a#%E5%85%A8%E4%BD%93%E3%81%AE%E3%81%BE%E3%81%A8%E3%82%81
[^2]: コード | https://qiita.com/d_desuyon/items/807e01311ad08570ee78#%E3%82%B3%E3%83%BC%E3%83%89
[^3]: モデルの評価 | https://qiita.com/kt38k/items/3c0ee4251475b6407007#%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AE%E8%A9%95%E4%BE%A1
[^4]: 特徴量重要度の算出 (2値分類編) | https://mathmatical22.xyz/2020/04/12/%E3%80%90%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91%E3%80%91%E7%89%B9%E5%BE%B4%E9%87%8F%E9%87%8D%E8%A6%81%E5%BA%A6%E3%81%AE%E7%AE%97%E5%87%BA-lightgbm-%E3%80%90python%E3%80%91%E3%80%90%E6%A9%9F/
[^5]: Adult Data Set | https://archive.ics.uci.edu/ml/datasets/adult