Evidentlyというデータドリフト検知ライブラリについて

# はじめに  
Evidentlyは[リファレンス](https://docs.evidentlyai.com/)が用意されているので、詳細を知りたい場合は公式のリファレンスを訪れましょう。ここでは、Evidentlyのデータドリフトに関する入り口程度を記載します。

## Evidentlyとは  
Evidentlyとはデータドリフトを検知するPythonライブラリになります。基本的にはDataFrame型のデータ形式を与えるだけで、様々な差分を見つけることができる便利なライブラリです。感想としては、PyCaretを使っている感覚でした。

## データドリフトとは  
データドリフトとは、初期段階のデータの分布が次第に変化していくことを指します。  
よく例に挙げられるのは機械学習の場面で、時期の古いデータで学習したモデルが最近のデータにも適切に対応できるだろうか、という場面ですかね。新古の2つのデータがどの程度ズレているかを見てあげることで、モデルの性能を確認することができます。  
あくまで一例を挙げましたが、こういった2つのデータの分布に対して、差分の検知をEvidentlyが実施してくれるという訳です。

## 実行してみる 
データセットには、scikit-learnに用意されているワインデータセットを使ってみます。本当は時系列データセットがデータドリフト的には良いでしょうけれど、特徴量がそこそこあるワインデータセットにしました(その方が出力結果に見応えがあったからです)。
```dataDrift.ipynb  
# wineデータセットの読み込み
from sklearn.datasets import load_wine
import pandas as pd

wine = load_wine()
# DataFrame型に整形
df = pd.concat([pd.DataFrame(wine.data, columns=wine.feature_names), pd.DataFrame(wine.target, columns=['target'])], axis=1)
df
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1939085/0289e29b-8273-8773-ef34-39e38e2cc5c6.png)
こんな感じで、DataFrame型でデータセットを用意できました。  
これからデータドリフトの検知をするために、適当にデータセットを2分割します。  
```dataDrift.ipynb
n = df.shape[0]
old_df = df.iloc[:n//2,:]
new_df = df.iloc[n//2:,:]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1939085/5c6ab053-de43-8650-73d8-b076df02f843.png)
インデックス番号を見た通り、読み込んだDataFrameを前後半で分けただけです。本来のデータドリフトとは筋が違いますが許してください...  

これからデータドリフトの検知を実施します。
```dataDrift.ipynb
# 数値データのカラム名を指定
numerical_features = ['alcohol', 'malic_acid', 'ash', 'alcalinity_of_ash', 'magnesium', 'total_phenols', 'flavanoids',
                      'nonflavanoid_phenols', 'proanthocyanins', 'color_intensity', 'hue', 'od280/od315_of_diluted_wines', 'proline']
# カテゴリカルデータのカラム名を指定
categorical_features = ['target']

# データドリフトの検知
column_mapping = ColumnMapping(numerical_features=numerical_features, categorical_features=categorical_features)
options = DataDriftOptions(nbinsx={'score': 10})
dashboard = Dashboard(tabs=[DataDriftTab(verbose_level=1)], options=[options])
dashboard.calculate(df1, df2, column_mapping=column_mapping)
dashboard.show(mode='inline') # notebookやcolabの方は`mode=inline`
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1939085/5f525b44-39b6-f687-72fd-aee19919de7a.png)
するとこんな感じで、HTML形式のダッシュボードが表示されます。ダッシュボードを保存したい場合は、 `dashboard.save(HTML形式のパス名)` の1行を加えることで指定したパスにHTML形式で保存できます。  
このダッシュボードのカラムを簡単に説明すると、
* Feature
    * 変数名
* Type
    * 変数名の示すデータ形式(numericalやcategoricalなど)
    * ここで注意点ですが、自作のDataFrameによるドリフト検知を実施する場合、**dtypeを指定するようにしてください。** というのも僕自身、自作したDataFrameでドリフト検知を実施した際に、 `numerical_features` に変数名を指定したにも関わらず、なぜかカテゴリ値として認識された経験があります。dtypeをfloat64に指定したことで解決できました。DataFrameで特定の列を指定する場合は `astype()` などあると思うので、もし数値データがカテゴリ値に認識された場合は、dtypeを確認してください。
* Reference Distribution
    * ベースとなるデータの分布(上でいうdf1が相当)
* Current Distribution
    * 現在のデータの分布(上でいうdf2が相当)
* Data Drift
    * Detected -> 検知された(差分が生じている)
    * Not Detected -> 検知されなかった(差分が許容範囲内だった)
    * あくまで、差分が完全にない場合のみを `Not Detected` にしているわけではないです。多少の許容があります。その辺りはリファレンスに詳細があるので、見てみてください
* Stat Test
    * ドリフト検知を計るための指標
    * 与えられたデータ数やデータの形式、特徴量の数などを要因として、指標が自動で変わります。もちろん、手動で設定することも可能なので、こちらも同様にリファレンスを見てみてください
    * 個人的には統計数学の分野を知らなくても簡単に扱えるので、自動で指標が変化してくれる点は非常に楽です
* Drift Score
    * 指標により計算された値
    * こちらは `Stat Test` にて選択された指標次第で数字の見方が異なるので気をつけてください。

公式リファレンスの[Data Drift](https://docs.evidentlyai.com/reports/data-drift)ページにて、ドリフト検知で使用されている指標の詳細があるので、気になった方は見てみてください。  

余談ですが、指標の違いを調べる中で、Wasserstein距離とJensen-Shannon情報量の違いが個人的にわかりやすかった[ページ](https://www.slideshare.net/joisino/ss-251328369)を掲載しておきます。スライドの前半30ページくらいまで目を通すことで、直感的に違いがわかると思います。

# おわりに  
Pythonに用意されているEvidentlyというライブラリの紹介でした。Qiitaに記事がほぼ無かったので書くことにしました(下手したらゼロだった気が...)。この記事では、てきとうにデータセットを用意して簡単に紹介しただけですが、機械学習のモデル性能を見ることに特化している `Numerical Target Drift` や `Categorical Target Drift` といった、説明変数と目的変数の関係をもとにドリフト検知してくれるものもあります。