Classification評価指標の表示あれこれ

# はじめに
sklearnなら、適合率（precision）、再現率（recall）、F1スコア、正解率（accuracy）などがまとまった**Classification Report**や、**混同行列（ConfusionMatrix）** が一発で表示できます。
とても簡単ですが、表示はとてもシンプルなので、すこし変えてみたいと思います。

##  実行条件など

>-Google colabで実行
-データは breast cancerデータセットを使用


##  実行内容
#### データセット読込み
以下は、データセット選択のためのFormのセットです。
```Python:データセット選択
#@title Select_Dataset { run: "auto" }
#@markdown  **<font color= "Crimson">注意</font>：かならず 実行する前に 設定してください。**</font>

dataset = 'Boston_housing :regression' #@param ['Boston_housing :regression', 'Diabetes :regression', 'Breast_cancer :binary','Titanic :binary', 'Titanic(seaborn) :binary', 'Iris :classification', 'Loan_prediction :binary','wine :classification', 'Occupancy_detection :binary', 'Upload']
```
以下は、選択したデータセット読込みとデータフレーム格納です。
```Python:データ読込み
#@title Load dataset

#ライブラリインポート
import numpy as np
import pandas as pd   #データを効率的に扱うライブラリ
import seaborn as sns #視覚化ライブラリ
import warnings       #警告を表示させないライブラリ
warnings.simplefilter('ignore')

'''
dataset（ドロップダウンメニュー）で選択したデータセットを読込み、データフレーム（df）に格納。
目的変数は、データフレームの最終列とし、FEATURES、TARGET、X、yを指定した後、データフレーム
に関する情報と先頭5列を表示。
任意のcsvデータを読込む場合は、datasetで'Upload'を選択。

'''

#任意のcsvデータ読込み及びデータフレーム格納、
if dataset =='Upload':
  from google.colab import files
  uploaded = files.upload()#Upload
  target = list(uploaded.keys())[0]
  df = pd.read_csv(target)

#Diabetes データセットの読込み及びデータフレーム格納、
elif dataset == "Diabetes :regression":
  from sklearn.datasets import load_diabetes
  diabetes = load_diabetes()
  df = pd.DataFrame(diabetes.data, columns = diabetes.feature_names)
  df['target'] = diabetes.target

#Breast_cancer データセットの読込み及びデータフレーム格納、
elif dataset == "Breast_cancer :binary":
  from sklearn.datasets import load_breast_cancer
  breast_cancer = load_breast_cancer()
  df = pd.DataFrame(breast_cancer.data, columns = breast_cancer.feature_names)
  df['target'] = breast_cancer.target  #目的変数をカテゴリー数値とする時
  #df['target'] = breast_cancer.target_names[breast_cancer.target]

#Titanic データセットの読込み及びデータフレーム格納、
elif dataset == "Titanic :binary":
  data_url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
  df = pd.read_csv(data_url)
  #目的変数 Survived をデータフレーム最終列に移動
  X = df.drop(['Survived'], axis=1)
  y = df['Survived'] 
  df = pd.concat([X, y], axis=1)    #X,yを結合し、dfに格納

#Titanic(seaborn) データセットの読込み及びデータフレーム格納、
elif dataset == "Titanic(seaborn) :binary":
  df = sns.load_dataset('titanic')
  #重複データをカットし、目的変数 alive をデータフレーム最終列に移動
  X = df.drop(['survived','pclass','embarked','who','adult_male','alive'], axis=1)
  y = df['alive']                   #目的変数データ
  df = pd.concat([X, y], axis=1)    #X,yを結合し、dfに格納

#iris データセットの読込み及びデータフレーム格納、
elif dataset == "Iris :classification":
  from sklearn.datasets import load_iris
  iris = load_iris()
  df = pd.DataFrame(iris.data, columns = iris.feature_names)
  #df['target'] = iris.target  #目的変数をカテゴリー数値とする時
  df['target'] = iris.target_names[iris.target]

#wine データセットの読込み及びデータフレーム格納、
elif dataset == "wine :classification":
  from sklearn.datasets import load_wine
  wine = load_wine()
  df = pd.DataFrame(wine.data, columns = wine.feature_names)
  #df['target'] = wine.target  #目的変数をカテゴリー数値とする時
  df['target'] = wine.target_names[wine.target]

#Loan_prediction データセットの読込み及びデータフレーム格納、 
elif dataset == "Loan_prediction :binary":
  data_url = "https://github.com/shrikant-temburwar/Loan-Prediction-Dataset/raw/master/train.csv"
  df = pd.read_csv(data_url)

#Occupancy_detection データセットの読込み及びデータフレーム格納、 
elif dataset =='Occupancy_detection :binary':
  data_url = 'https://raw.githubusercontent.com/hima2b4/Auto_Profiling/main/Occupancy-detection-datatest.csv'
  df = pd.read_csv(data_url)
  df['date'] = pd.to_datetime(df['date'])    #[date]のデータ型をdatetime型に変更

#Boston データセットの読込み及びデータフレーム格納 
else:
  from sklearn.datasets import load_boston
  boston = load_boston()
  df = pd.DataFrame(boston.data, columns = boston.feature_names)
  df['target'] = boston.target

#データフレーム表示
df.info(verbose=True)         #データフレーム情報表示（verbose=Trueで表示数制限カット）
df.head()                     #データフレーム先頭5行表示
```
#### 決定木モデル適用＆ハイパーパラメーターチューニング
```Python:決定木→GridSearch→score表示
#@title **決定木実行**（GridSearchによるパラメータ最適化Ver.）

dataset_type = 'Classification' #@param ["Classification", "Regression"]

#FEATURES、TARGET、X、yを指定 
FEATURES = df.columns[:-1]    #説明変数のデータ項目を指定
TARGET = df.columns[-1]       #目的変数のデータ項目を指定
X = df.loc[:, FEATURES]       #FEATURESのすべてのデータをXに格納
y = df.loc[:, TARGET]         #TARGETのすべてのデータをyに格納

#testとtrainを分割
from sklearn.model_selection import train_test_split

if dataset_type == 'Classification':
  X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 1, stratify = y)
else:
  X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 1, test_size=0.25)

#ライブラリインポート
from sklearn.tree import DecisionTreeRegressor
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV

#GridSearch実行
if dataset_type == 'Classification':
  dtv = DecisionTreeClassifier(random_state=0)
  pre_dtv = dtv.fit(X_train, y_train)
  print('Normal model')
  print('  score \t train : %.2f,\t test : %.2f'% (
      pre_dtv.score(X_train, y_train),
      pre_dtv.score(X_test, y_test)))
  
else:
  dtv = DecisionTreeRegressor(random_state=0)
  pre_dtv = dtv.fit(X_train, y_train)
  print('Normal model')
  print('  score \t train : %.2f,\t test : %.2f'% (
      pre_dtv.score(X_train, y_train),
      pre_dtv.score(X_test, y_test)))
  

gs_dtv = GridSearchCV(dtv,
                      param_grid = {'max_depth': [1, 2, 3, 4, 5, 6, 7],
                      'min_samples_leaf':[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
                      'min_samples_split':[2, 3, 4, 5]},
                      cv = 10)

gs_dtv.fit(X_train, y_train)

print('-----------------------------------------------------------------------------------------')
print('Optimal model')
print('  result\t',gs_dtv.best_estimator_)
print('  score \t train : %.2f,\t test : %.2f' % (
gs_dtv.score (X_train, y_train),
gs_dtv.score (X_test,y_test)))
print('-----------------------------------------------------------------------------------------')
```

#### 評価指標算出と表示
```Python:カラムをデータ型で区分

#指標関連ライブラリインストール
from sklearn.metrics import r2_score   # 決定係数
from sklearn.metrics import mean_squared_error  # RMSE
from sklearn.metrics import mean_absolute_error  #MAE
from sklearn.metrics import confusion_matrix  #混同行列
from sklearn.metrics import classification_report  #classification report

# 予測値
y_train_pred = gs_dtv.predict(X_train)
y_test_pred = gs_dtv.predict(X_test)

if dataset_type == 'Classification':
  print('Confusion matrix_test')
  #混同行列
  confusion_matrix(y_test,y_test_pred)
  print('-----------------------------------------------------------------------------------------')
  print('Classification report_test')
  print(classification_report(y_true=y_test, y_pred=y_test_pred))

else:
  print('Regression report')
  print('  RMSE\t train: %.2f,\t test: %.2f' % (
      mean_squared_error(y_train, y_train_pred, squared=False), 
      mean_squared_error(y_test, y_test_pred, squared=False)))
  print('  MAE\t train: %.2f,\t test: %.2f' % (
      mean_absolute_error(y_train, y_train_pred), 
      mean_absolute_error(y_test, y_test_pred)))
  print('  R²\t train: %.2f,\t test: %.2f' % (
        r2_score(y_train, y_train_pred), # 学習
        r2_score(y_test, y_test_pred)    # テスト
      ))
print('-----------------------------------------------------------------------------------------')
```
※上記コードは、classification/regressionいずれにも対応しています。
***
前置きが長くなりましたが、今回は、breast cancerデータセット（classification）に決定木を適用し、ハイパーパラメータ調整を行った後の 混合行列表示をいろいろ変更してみたいと思います。

[Select_Dataset] で [Breast_cancer :binary] を選択し、実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/2c11fbec-9b9c-1d4f-cd3e-f6ba2e15acd0.png)

実行すると、以下の混同行列が表示されます。

>array([[88,  2],
　　         [11, 42]])

ちょっと、シンプル過ぎる感じなので、データフレーム表示に変えてみましょう。

```Python:混同行列をデータフレーム表示
cm = confusion_matrix(y_test,y_test_pred)
cm = pd.DataFrame(data = cm)
display(cm)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/564e3610-5cf7-a38d-a394-4dffef5dc31c.png)
すこし見やすくなりました。

次は、行ラベル・列ラベル名を入れてみましょう。
```Python:混同行列をデータフレーム表示（行列名追加）
cm =confusion_matrix(y_test,y_test_pred)
index1 = ["Actual_0", "Actual_1"]
columns1 =["Predicted_0", "Predicted_1"]
cm =pd.DataFrame(data = cm, index=index1, columns=columns1)
cm
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/64fd8f09-85e9-cdfa-2b22-ae43f4603086.png)
少しわかりやすくなりました。

次は、混同行列をヒートマップ表示に変えてみます。
```Python:混同行列をヒートマップ表示
sns.set(rc = {'figure.figsize':(1.5,1.5)})
sns.heatmap(confusion_matrix(y_test,y_test_pred),
            square=True, cbar=True, annot=True, cmap='Blues')
plt.xlabel('Predicted', fontsize=11)
plt.ylabel('Actual', fontsize=11);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/4f110958-11b4-4bab-e177-df0cd9b59b4e.png)
グラフサイズを小さくしたこともありますが、かわいいですね。
ヒートマップ表示にした方が、見やすく、わかりやすいです。

次は、yellowbrickライブラリで混同行列（ヒートマップ）を描いてみます。
```Python:混同行列をyellowbrickで表示
from yellowbrick.classifier import confusion_matrix

sns.set(rc = {'figure.figsize': (1.5,1.5)})

confusion_matrix(
    gs_dtv.best_estimator_,
    X_train, y_train, X_test, y_test,
    classes=list(y.unique()),
    cmap='Blues'
)
plt.tight_layout();
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/b9d51afd-4e07-1586-66b8-ebae88f1fdef.png)
タイトルもついていい感じです。

**yellowbrickは、Classification reportのヒートマップ表示にも対応**していますので、次は 混同行列とClassification reportをセットで描いてみます。

```Python:混同行列とClassification reportをyellowbrickで描く
from yellowbrick.classifier import confusion_matrix

sns.set(rc = {'figure.figsize': (1.5,1.5)})

confusion_matrix(
    gs_dtv.best_estimator_,
    X_train, y_train, X_test, y_test,
    classes=list(y.unique()),
    cmap='Blues')
plt.show()

from yellowbrick.classifier import ClassificationReport

sns.set(rc = {'figure.figsize': (4.5,2.5)})
        
visualizer = ClassificationReport(gs_dtv.best_estimator_, classes= list(y.unique()), support=True, cmap='Blues', fontsize=10)

visualizer.fit(X_train, y_train)        # Fit the visualizer and the model
visualizer.score(X_test, y_test)        # Evaluate the model on the test data
print('-----------------------------------------------------------------------------------------')
visualizer.show();                      # Finalize and show the figure
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/c119c296-8e30-8019-ca1f-8fb0f9235e5c.png)
視認性があがりましたね。
わずかなことですが、ただただ見た目をよくする...ということではなく、把握・判断しやすくするという意味での視覚化は大事と思います。

##  最後に
[**yellowbrick**](https://www.scikit-yb.org/en/latest/) はいいですね。
よろしければ、以下の記事も参照ください。

https://qiita.com/hima2b4/items/bf0fa0a405898dcacfb8