機械学習の評価に便利なROC曲線の描画ライブラリを作ってみた

# はじめに
機械学習において性能評価は欠かせない手順のひとつですが、
**分類タスクにおける性能評価によく使われる**のが、**ROC曲線**です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/b3eb4459-94f5-5a77-1c93-cec852193dfe.png)
**PythonでROC曲線を描画**するには、Scikit-Learnの[~~plot_roc_curve~~](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.plot_roc_curve.html)（←Scikit-Learn1.2で削除されました）[RocCurveDisplay.from_estimator](https://scikit-learn.org/1.0/modules/generated/sklearn.metrics.RocCurveDisplay.html#sklearn.metrics.RocCurveDisplay.from_estimator())というメソッドを使用するのが一般的ですが、このメソッド、多クラス分類やクロスバリデーションでの描画が出来ない等、**制約が多い**です。

そこで今回、これらの制約をクリアすべく、
**・多クラス分類のROC曲線描画**
**・クロスバリデーションのROC曲線描画**
を実現するライブラリを作成しました。
![output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/f55f5788-9658-0dc5-d94e-6c20ffe3581b.png)

本機能は[こちらの記事](https://qiita.com/c60evaporator/items/8cb774e65d4423fd93ee)で紹介した[seaborn-analyzer](https://pypi.org/project/seaborn-analyzer/)ライブラリに、`plot_roc_curve_multiclass()`メソッドおよび`roc_plot()`メソッドとして追加しております。

**本ツールを有用だと感じられたら、[GitHubにスター頂けると有難いです！](https://github.com/c60evaporator/seaborn-analyzer)**

# インストール方法
インストールは以下のようにpipから行います

```
pip install seaborn-analyzer
```

# 使用法
`plot_roc_curve_multiclass()`メソッドと`roc_plot()`メソッドの使い分けは、以下のフローにより判断してください
![roc_plotチートシート.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/df9462f3-9b54-0d9c-783d-5528ced4dbf6.png)

### 多クラス分類のROC曲線描画
**多クラス分類でのROC曲線**描画は、`plot_roc_curve_multiclass()`メソッドで実行します
（学習データと評価データを分けない場合、後述の`roc_plot()`メソッドでも描画可能）

```python:多クラス分類(irisデータセット)でのROC曲線描画
import seaborn as sns
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
import numpy as np
import matplotlib.pyplot as plt
from seaborn_analyzer import classplot
# Load dataset
iris = sns.load_dataset("iris")
OBJECTIVE_VARIALBLE = 'species'  # 目的変数
USE_EXPLANATORY = ['petal_width', 'petal_length', 'sepal_width', 'sepal_length']  # 説明変数
y = iris[OBJECTIVE_VARIALBLE].values
X = iris[USE_EXPLANATORY].values
# ノイズ追加(ROC曲線を見やすくするための処理。実使用時は実施しないで下さい)
random_state = np.random.RandomState(0)
n_samples, n_features = X.shape
X = np.c_[X, random_state.randn(n_samples, 10 * n_features)]
# ROC曲線描画
X_train, X_test, y_train, y_test = train_test_split(X, y, shuffle=True, random_state=42)
estimator = SVC(probability=True, random_state=42)
classplot.plot_roc_curve_multiclass(estimator, X_train, y_train, 
                                    X_test=X_test, y_test=y_test)
plt.plot([0, 1], [0, 1], label='Chance', alpha=0.8,
         lw=2, color='red', linestyle='--')
plt.legend(loc='lower right')
```
![image.png](https://user-images.githubusercontent.com/59557625/132558369-c6bfee32-156b-4043-bedb-5b1854b00660.png)
引数一覧は[GitHubの該当項目](https://github.com/c60evaporator/seaborn-analyzer#%E5%BC%95%E6%95%B0%E4%B8%80%E8%A6%A7-5)を、機能詳細は[後述の機能解説](https://qiita.com/c60evaporator/items/401806703543689936ec#%E6%A9%9F%E8%83%BD%E8%A7%A3%E8%AA%AC)を参照ください。

### クロスバリデーションでのROC曲線描画
**クロスバリデーションでのROC曲線**描画は、`roc_plot()`メソッドで実行します。
本メソッドは内部で`plot_roc_curve_multiclass()`メソッドを呼び出しているので、「クロスバリデーションかつ多クラス分類」のROC曲線も描画可能です。

```python:クロスバリデーション(かつ多クラス分類)でのROC曲線描画
from lightgbm import LGBMClassifier
import seaborn as sns
import matplotlib.pyplot as plt
from seaborn_analyzer import classplot
# Load dataset
iris = sns.load_dataset("iris")
OBJECTIVE_VARIALBLE = 'species'  # 目的変数
USE_EXPLANATORY = ['petal_width', 'petal_length', 'sepal_width', 'sepal_length']  # 説明変数
y = iris[OBJECTIVE_VARIALBLE].values
X = iris[USE_EXPLANATORY].values
fit_params = {'verbose': 0,
              'early_stopping_rounds': 10,
              'eval_metric': 'rmse',
              'eval_set': [(X, y)]
              }
# ROC曲線描画
estimator = LGBMClassifier(random_state=42, n_estimators=10000)
fig, axes = plt.subplots(4, 1, figsize=(6, 24))
classplot.roc_plot(estimator, X, y, ax=axes, cv=3, fit_params=fit_params)
```
![image.png](https://user-images.githubusercontent.com/59557625/132708291-99f7bda0-eb24-4fc0-8994-a976d097908e.png)
引数一覧は[GitHubの該当項目](https://github.com/c60evaporator/seaborn-analyzer#%E5%BC%95%E6%95%B0%E4%B8%80%E8%A6%A7-5)を、機能詳細は[後述の機能解説](https://github.com/c60evaporator/seaborn-analyzer#%E5%BC%95%E6%95%B0%E4%B8%80%E8%A6%A7-6)を参照ください。

# 必要要件
本ツールには以下のライブラリが必要となります

Python >=3.6
Numpy >=1.20.3
Pandas >=1.2.4
Matplotlib >=3.3.4
Seaborn >=0.11.0
Scipy >=1.6.3
Scikit-learn >=0.24.2

# 機能解説
本ライブラリの描画対象であるROC曲線の意味と、ライブラリで実現できる機能を解説します。
## ROC曲線とは？
機械学習の分類タスク（2クラス分類）では、PositiveクラスとNegativeクラスを分類します。この分類結果の正誤を集計したものが、下図の混同行列です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/ec86c29d-5a4f-efa6-d311-99da07ddc1d8.png)
この混同行列において、見すぎ（FPが多い）と見逃し（FNが多い）はトレードオフ関係にあり、片方が良化すると基本的にはもう片方が悪化します。

このような**トレードオフ関係を可視化し、性能を評価するためのグラフ**が、**ROC曲線**です。
([参考](https://tips-memo.com/python-roc))
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/5d2ef673-086f-f246-24f5-b27fae65760b.png)
ROC曲線は、閾値を変えた際の、TPR(見逃しの少なさ)とFPR(見すぎの多さ)の変化をプロットした曲線です。

Scikit-Learnにおいては、学習器から[クラス確率](https://qiita.com/c60evaporator/items/43866a42e09daebb5cc0#%E3%83%A6%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%82%B92%E3%81%AE%E8%A7%A3%E6%B1%BA%E7%AD%96)を求め、その閾値を離散的に変化させた際のTPR、FPRを求めて線でつなぐことで、ROC曲線を描画します。

## RocCurveDisplay.from_estimator()メソッドの制約
先ほど、「Scikit-Learnの`RocCurveDisplay.from_estimator()`メソッドは制約が多い」と言いましたが、実用上は以下の3点が問題となります

**1. クロスバリデーションでは使えない**
**2. 多クラス分類では使えない**
**3. fit_paramsを渡せない**

詳細と本ツールでの解決策を下記します

## 制約の内容と解決策
上記1～3の制約の内容と、どのように解決したかを詳説します

### 制約1. クロスバリデーション時のROC曲線が描画できない

`RocCurveDisplay.from_estimator()`メソッドは、学習済の分類器を渡して実行するため、以下のような1本のROC曲線のみを描画することができます

```python:RocCurveDisplay.from_estimator()メソッドでのROC曲線描画例
import seaborn as sns
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from sklearn.metrics import RocCurveDisplay
import numpy as np
# Load dataset
iris = sns.load_dataset("iris")
iris = iris[iris['species'] != 'setosa'] 
OBJECTIVE_VARIALBLE = 'species'  # 目的変数
USE_EXPLANATORY = ['petal_width', 'petal_length', 'sepal_width', 'sepal_length']  # 説明変数
y = iris[OBJECTIVE_VARIALBLE].values
X = iris[USE_EXPLANATORY].values
# ノイズ追加(ROC曲線を見やすくするため)
random_state = np.random.RandomState(0)
n_samples, n_features = X.shape
X = np.c_[X, random_state.randn(n_samples, 10 * n_features)]
# ROC曲線描画
X_train, X_test, y_train, y_test = train_test_split(X, y, shuffle=True, random_state=42)
estimator = SVC(probability=True, random_state=42)
estimator.fit(X_train, y_train)
RocCurveDisplay.from_estimator(estimator, X_test, y_test)
```
![plot_roc_curve.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/7926a72f-601d-104f-71bf-365094f4a377.png)
以下のようなクロスバリデーション時のROC曲線（FoldごとのROC曲線＋平均ROC曲線）を描画することはできません。
![output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/d1b5a54b-2fc7-0d1d-0721-cfc3e5d504de.png)
分類の性能評価にはクロスバリデーションを使う事が多いので、これが可視化できない事は大きな問題です。

#### 解決策
[scikit-learnでの解説記事](https://scikit-learn.org/stable/auto_examples/model_selection/plot_roc_crossval.html)を参考に、クロスバリデーションでのROC曲線描画機能を実装し、ライブラリ中の`roc_plot()`メソッドに組み込みました。

`cv`引数にクロスバリデーション用のインスタンスを渡すことで、以下のようにクロスバリデーションでのROC曲線描画を実現できます
（`cv`引数に数値を指定すれば、指定した数でKFold分割します）

```python:roc_plotメソッドによるクロスバリデーションでのROC曲線描画
from sklearn.svm import SVC
from sklearn.model_selection import KFold
import seaborn as sns
import matplotlib.pyplot as plt
from seaborn_analyzer import classplot
import numpy as np
# Load dataset
iris = sns.load_dataset("iris")
iris = iris[iris['species'] != 'setosa'] 
OBJECTIVE_VARIALBLE = 'species'  # 目的変数
USE_EXPLANATORY = ['petal_width', 'petal_length', 'sepal_width', 'sepal_length']  # 説明変数
y = iris[OBJECTIVE_VARIALBLE].values
X = iris[USE_EXPLANATORY].values
# ノイズ追加(ROC曲線を見やすくするため)
random_state = np.random.RandomState(0)
n_samples, n_features = X.shape
X = np.c_[X, random_state.randn(n_samples, 10 * n_features)]
# ROC曲線描画
estimator = SVC(probability=True, random_state=42)
fig, axes = plt.subplots(4, 1, figsize=(6, 24))
cv = KFold(n_splits=3, shuffle=True, random_state=42)
classplot.roc_plot(estimator, X, y, ax=axes, cv=cv)
```
![roc_plot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/1e623580-aa17-fb13-acc4-22727470665b.png)

### 制約2. 多クラス分類でのROC曲線が描画できない

各クラスごとに、残りのクラスをひとまとめにした2クラス分類（One vs Rest, OVRと言います。[こちらが分かりやすいです](https://ai-laboratory-column.com/accuracy_verification_of_multi_class_problems)）を実施し、それぞれ求めた混同行列から、TPRの平均値を求めます（横軸となるFPRは最初に定めた固定値のリストを使用）。

平均の求め方は、以下のミクロ平均とマクロ平均があります。

#### ミクロ平均
クラスごとのデータ数で重みづけしたTPRとFPRの平均を求めます。
以下の式で求められます。

```math
TPR_{micro} = \frac{\sum_{i=1}^{n} TP_i}{\sum_{i=1}^{n} TP_i+\sum_{i=1}^{n} FN_i}
```

```math
FPR_{micro} = \frac{\sum_{i=1}^{n} FP_i}{\sum_{i=1}^{n} FP_i+\sum_{i=1}^{n} TN_i}
```

#### マクロ平均
クラスごとに求めたTPRをそのまま平均します

クラスごとの添字をi、クラス数をnで表すと、以下のように求められます

```math
TPR_{macro} = \frac{\sum_{i=1}^{n} TPR_i}{n} = \frac{\sum_{i=1}^{n} \frac{TP_i}{TP_i+FN_i}}{n}
```

```math
FPR_{macro} = \frac{\sum_{i=1}^{n} FPR_i}{n} = \frac{\sum_{i=1}^{n} \frac{FP_i}{FP_i+TN_i}}{n}
```
クラスごとのTPRを求める際にFPRの値を揃えておけば、TPRのマクロ平均だけを求めればよくなり、計算が楽になります（Scikit-Learnでもこのような求め方をしているようです）

#### ミクロ平均とマクロ平均の使い分け
ミクロ平均とマクロ平均の使い分けは、以下のようになります

|用途|選択する平均法|
|---|---|
|データ数の少ないクラスを重視したい場合|マクロ平均|
|データ数の多いクラスを重視したい場合|ミクロ平均|

詳細は[こちら](https://qiita.com/TaskeHAMANO/items/120cabf01b299b8f35ce)や[こちら](https://note.nkmk.me/python-sklearn-confusion-matrix-score/)が詳しいですが、クラスごとにデータ数の偏りが大きい**不均衡データにおいては**、小さいクラスの影響を大きく評価する**macroの方がmicroより良い**と言われています。
（逆に、大きなクラスさえ正解していれば小さいクラスでの性能は問わない、という場合はf1_microを選択するのも可です）

#### 問題と解決策
前置きが長くなりましたが、`RocCurveDisplay.from_estimator`メソッドには上記のようなミクロ平均、マクロ平均を求める機能は存在せず、**多クラス分類でのROC曲線を描画できません**（エラーが出ます）

機械学習の可視化ライブラリ[YellowBrick](https://www.scikit-yb.org/en/latest/api/classifier/rocauc.html#multi-class-rocauc-curves)や、これを利用した[PyCaret](https://pycaret.org/plot-model/)ではミクロ平均やマクロ平均のROC曲線を描画できますが、これらを利用すると前述のクロスバリデーションや後述のfit_paramsを適用する事が困難となるため、やはり課題は残ります。

そこで[こちらのScikit-Learnの記事](https://scikit-learn.org/stable/auto_examples/model_selection/plot_roc.html#example-model-selection-plot-roc-py)
を参考に**多クラス分類でのROC曲線描画機能**を組み込んだ、`plot_roc_curve_multiclass()`メソッドを実装しました

```python:plot_roc_curve_multiclassメソッドによる多クラス分類のROC曲線描画
import seaborn as sns
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
import numpy as np
import matplotlib.pyplot as plt
from seaborn_analyzer import classplot
# Load dataset
iris = sns.load_dataset("iris")
OBJECTIVE_VARIALBLE = 'species'  # 目的変数
USE_EXPLANATORY = ['petal_width', 'petal_length', 'sepal_width', 'sepal_length']  # 説明変数
y = iris[OBJECTIVE_VARIALBLE].values
X = iris[USE_EXPLANATORY].values
# ノイズ追加(ROC曲線を見やすくするため)
random_state = np.random.RandomState(0)
n_samples, n_features = X.shape
X = np.c_[X, random_state.randn(n_samples, 10 * n_features)]
# ROC曲線描画
X_train, X_test, y_train, y_test = train_test_split(X, y, shuffle=True, random_state=42)
estimator = SVC(probability=True, random_state=42)
classplot.plot_roc_curve_multiclass(estimator, X_train, y_train, 
                                    X_test=X_test, y_test=y_test)
plt.plot([0, 1], [0, 1], label='Chance', alpha=0.8,
         lw=2, color='red', linestyle='--')
plt.legend(loc='lower right')
```
![roc_multiclass.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/5b64a81b-0640-e3a5-f06d-122d4b4a9e89.png)
また、前述の`roc_plot()`メソッドを使用すれば、「多クラス分類かつクロスバリデーションでのROC曲線」を描画することも可能です

```python:roc_plotメソッドによる多クラス分類かつクロスバリデーションでのROC曲線描画
from sklearn.svm import SVC
import seaborn as sns
import matplotlib.pyplot as plt
from seaborn_analyzer import classplot
import numpy as np
# Load dataset
iris = sns.load_dataset("iris")
OBJECTIVE_VARIALBLE = 'species'  # 目的変数
USE_EXPLANATORY = ['petal_width', 'petal_length', 'sepal_width', 'sepal_length']  # 説明変数
y = iris[OBJECTIVE_VARIALBLE].values
X = iris[USE_EXPLANATORY].values
# ノイズ追加(ROC曲線を見やすくするため)
random_state = np.random.RandomState(0)
n_samples, n_features = X.shape
X = np.c_[X, random_state.randn(n_samples, 10 * n_features)]
# ROC曲線描画
estimator = SVC(probability=True, random_state=42)
fig, axes = plt.subplots(4, 1, figsize=(6, 24))
classplot.roc_plot(estimator, X, y, ax=axes, cv=3)
```
![roc_multiclass_cv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/b9e14032-969d-56ab-d509-51c5bf3fdedf.png)

### 制約3. fit_paramsを渡せない
上記の多クラス分類でOVR学習器の作成に使用している[`OneVsRestClassifier`](https://scikit-learn.org/stable/modules/generated/sklearn.multiclass.OneVsRestClassifier.html)クラスは、学習時の`fit()`メソッドに引数（以下`fit_params`と呼びます）を渡すことができません。

これは、XGBoostやLightGBMで多用される`early_stopping_round`が使用できないことを意味しており、これらのアルゴリズムを使用する際に大きな支障が出ます。

#### 解決策
OneVsRestClassifierクラスを継承して、`fit()`メソッドを適用可能な[OneVsRestClassifierPatchedクラス](https://github.com/c60evaporator/seaborn-analyzer/blob/master/seaborn_analyzer/multiclass_fitparams.py)を作成しました（[こちらのIssues](https://github.com/scikit-learn/scikit-learn/issues/10882)を参考にさせて頂きました）

ですので`plot_roc_curve_multiclass()`メソッド、`roc_plot()`メソッド共に、**`fit_params`引数にパラメータを渡せば、学習時に適用されます**

```python:クロスバリデーション＆多クラス分類＆fit_params適用でのROC曲線
from lightgbm import LGBMClassifier
import seaborn as sns
import matplotlib.pyplot as plt
from seaborn_analyzer import classplot
# Load dataset
iris = sns.load_dataset("iris")
OBJECTIVE_VARIALBLE = 'species'  # Objective variable
USE_EXPLANATORY = ['petal_width', 'petal_length', 'sepal_width', 'sepal_length']  # Explantory variables
y = iris[OBJECTIVE_VARIALBLE].values
X = iris[USE_EXPLANATORY].values
fit_params = {'verbose': 0,
              'early_stopping_rounds': 10,
              'eval_metric': 'multi_logloss',
              'eval_set': [(X, y)]
              }
# Plot ROC curve with cross validation in multiclass classification
estimator = LGBMClassifier(random_state=42, n_estimators=10000)
fig, axes = plt.subplots(4, 1, figsize=(6, 24))
classplot.roc_plot(estimator, X, y, ax=axes, cv=3, fit_params=fit_params)
```
![roc_lgbm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/3259f4d5-85ea-8fbd-169f-d60a8b4ce2e2.png)

# ROC曲線の活用例：
**ROC曲線を性能評価に使う**事は、他の評価指標（LogLoss、Accuracy等）と比較して以下のような**メリット**があります。

**・見すぎと見逃しのバランスを連続的に評価できる**
**・グラフで可視化できるため、直感的な理解の助けとなる**

上記を踏まえて、ROC曲線の活用例を紹介します。

## 活用例1：見逃しに強いか、見すぎに強いかの確認
適切なデータセットが見付からなかったので実装例はありませんが、
以下の記事のように見すぎと見逃しどちらに強いモデルかの評価に使われることがあります。

https://note.com/okonomiyaki011/n/n7fb16f5bede1

上記の特性から、検査機のように見逃しを極力許容したくない用途（逆に見すぎは許容する）においては、**見逃しに強いモデル**を判断できるROC曲線はモデル選択や閾値決定の助けとなるでしょう。

## 活用例2：ノイズへの強さ（余分な説明変数の追加に対する頑強さ）の比較
以下の例では、ノイズ列（ランダムに生成した意味を持たない説明変数）を元の列数の10倍加えた場合の、
・LightGBM
・SVM
・RandomForest
の3種類の分類器における、ROC曲線の変化をプロットしています。

### ノイズ付加前
```python:ノイズ付加前のROC曲線
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from lightgbm import LGBMClassifier
import seaborn as sns
import matplotlib.pyplot as plt
from seaborn_analyzer import classplot
# Load dataset
iris = sns.load_dataset("iris")
OBJECTIVE_VARIALBLE = 'species'  # Objective variable
USE_EXPLANATORY = ['petal_width', 'petal_length', 'sepal_width', 'sepal_length']  # Explantory variables
y = iris[OBJECTIVE_VARIALBLE].values
X = iris[USE_EXPLANATORY].values
fit_params = {'verbose': 0,
              'early_stopping_rounds': 10,
              'eval_metric': 'multi_logloss',
              'eval_set': [(X, y)]
              }
# Plot ROC curve with three classifiers
estimator1 = LGBMClassifier(random_state=42, n_estimators=10000)
estimator2 = SVC(probability=True, random_state=42)
estimator3 = RandomForestClassifier(random_state=42)
fig, axes = plt.subplots(4, 3, figsize=(18, 24))
ax_pred = [[row[i] for row in axes] for i in range(3)]
classplot.roc_plot(estimator1, X, y, ax=ax_pred[0], cv=3, fit_params=fit_params)
classplot.roc_plot(estimator2, X, y, ax=ax_pred[1], cv=3)
classplot.roc_plot(estimator3, X, y, ax=ax_pred[2], cv=3)
# Add etimator name to the graph
ax_pred[0][0].set_title(f'LightGBM\n\n{ax_pred[0][0].title._text}')
ax_pred[1][0].set_title(f'SVM\n\n{ax_pred[1][0].title._text}')
ax_pred[2][0].set_title(f'RandomForest\n\n{ax_pred[2][0].title._text}')
```
![roc_without_noise.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/81400c92-f8d9-7159-6fe0-f41931a7f9af.png)

### ノイズ付加後
```python:
# Load dataset
iris = sns.load_dataset("iris")
OBJECTIVE_VARIALBLE = 'species'  # Objective variable
USE_EXPLANATORY = ['petal_width', 'petal_length', 'sepal_width', 'sepal_length']  # Explantory variables
y = iris[OBJECTIVE_VARIALBLE].values
X = iris[USE_EXPLANATORY].values
# Add random noise features
random_state = np.random.RandomState(0)
n_samples, n_features = X.shape
X = np.c_[X, random_state.randn(n_samples, 10 * n_features)]
fit_params = {'verbose': 0,
              'early_stopping_rounds': 10,
              'eval_metric': 'multi_logloss',
              'eval_set': [(X, y)]
              }
# Plot ROC curve with three classifiers
estimator1 = LGBMClassifier(random_state=42, n_estimators=10000)
estimator2 = SVC(probability=True, random_state=42)
estimator3 = RandomForestClassifier(random_state=42)
fig, axes = plt.subplots(4, 3, figsize=(18, 24))
ax_pred = [[row[i] for row in axes] for i in range(3)]
classplot.roc_plot(estimator1, X, y, ax=ax_pred[0], cv=3, fit_params=fit_params)
classplot.roc_plot(estimator2, X, y, ax=ax_pred[1], cv=3)
classplot.roc_plot(estimator3, X, y, ax=ax_pred[2], cv=3)
# Add etimator name to the graph
ax_pred[0][0].set_title(f'LightGBM\n\n{ax_pred[0][0].title._text}')
ax_pred[1][0].set_title(f'SVM\n\n{ax_pred[1][0].title._text}')
ax_pred[2][0].set_title(f'RandomForest\n\n{ax_pred[2][0].title._text}')
```
![roc_with_noise.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/6c079fde-8631-7e34-b3dc-4ef9737abf05.png)
LightGBM、RandamForestはノイズ付加前後でROC曲線があまり変化していないのに対し、
SVMはノイズ付加によりROC曲線が大きく悪化していることが分かります。

現実のデータでは、上記ノイズ列のように目的変数にほぼ影響しない（意味のない）説明変数が大量に含まれている事例が多く、このようなデータでは[特徴量選択](https://qiita.com/FukuharaYohei/items/db88a8f4c4310afb5a0d#3-embedded-method)をゴリゴリ頑張って変数を絞る必要があるのですが、上記ノイズ列評価の結果から、

**・LightGBMやRandomForestは特徴量選択が不十分でも高い性能が出せる**
**・SVMは特徴量選択が不十分だと性能が落ちる**

ことが分かります。
アルゴリズムの特性の違いをグラフで可視化できるので、ROC曲線の利便性が実感できたかと思います。