Kaggleの臨床検査データセットを使ってみた④　～外れ値の取り扱い～

## 概要
Kaggleの血液検査データセットを使ってデータ分析をしてみた。
いろいろ試してみた結果、量が多くなったので分割します。
今回はその④ (全5回)

**他の回はこちらから**
[①～モデルの性能比較をしてみた～](https://qiita.com/tuk19/items/b0df794d4d827511adf1)
[②～特徴選択をして、重要度を可視化してみた～](https://qiita.com/tuk19/items/dd759caded18d0aeeb20)
[③～アンサンブル学習をしてみた～](https://qiita.com/tuk19/items/b2d26c47ee51b5bad0b9)
[⑤～不均衡データの取り扱い～](https://qiita.com/tuk19/items/ade9a23ce80988aa6a30)

使用したデータセット：[Patient Treatment Classification (Electronic Health Record Dataset)](https://www.kaggle.com/datasets/saurabhshahane/patient-treatment-classification)
インドネシアの病院で集められた血液検査の結果から、患者に治療が必要かどうかを判定する

## モデル
今回はXGBoostで外れ値の影響を確認する
1. XGBoost

## データ確認
データは血液検査の結果。
データの検査方法が分からなかったため、正常値は[2016年度国立がん研究センターのデータ](https://www.ncc.go.jp/jp/ncch/division/clinical_laboratory/kensa.pdf)をお借りしました。（※ 正常値は測定方法などにより若干のばらつきあり）

| 列No   | 検査値       | 和訳                      |　正常値                                               |高いと|低いと|
|:-: |:-:           |:-:                       |:-:                                                       |:-:                                                       |:-:                                                       |
|  1 | HAEMATOCRIT  | ヘマトクリット            | 男性：40.7～50.1 ％, <BR>女性：35.1～44.4 ％                      |多血症など|貧血など|
|  2 | HAEMOGLOBINS | ヘモグロビン              | 男性：13.7～16.8 g/dl, <BR>女性：11.6～14.8 g/dl                 |多血症など|貧血など|
|  3 | ERYTHROCYTE  | 赤血球                    | 男性：4.35～5.55 x 10<sup>6</sup> /μＬ, <BR>女性：3.86～4.92 x 10<sup>6</sup> /μＬ |多血症など|貧血など|
|  4 | LEUCOCYTE    | 白血球                    | 3.3～8.6 x 10<sup>3</sup>/μL                            |感染症・白血病など|一部感染症・膠原病・貧血など|
|  5 | THROMBOCYTE	| 血小板                    | 158～348 x 10<sup>3</sup>/μL                            |血小板血症・白血病・多血症など|貧血・紫斑病など|
|  6 | MCH          | 平均赤血球ヘモグロビン量   | 27.5～33.2 pg                                            |巨赤芽球性貧血など|鉄欠乏性貧血など|
|  7 | MCHC	        | 平均赤血球ヘモグロビン濃度 | 31.7～35.3 g/dL                                          |脱水・多血症など|貧血など|
|  8 | MCV          | 平均赤血球容積            | 83.6～98.2 fL　                                          |巨赤芽球性貧血など|鉄欠乏性貧血など|
|  9 | AGE          | 年齢                     | ―                                                        |―                                                        |―                                                        |
| 10 | SEX          | 性別                     | ―                                                        |―                                                        |―                                                        |
| 11 | SOURCE       | 治療が必要か              | out: 治療不要, in: 治療が必要                             |―                                                        |―                                                        |



前処理は[①と同じ](https://qiita.com/tuk19/items/b0df794d4d827511adf1#%E5%89%8D%E5%87%A6%E7%90%86)なので割愛

## 外れ値検索
```python:LOFによる外れ値検索
from sklearn.neighbors import LocalOutlierFactor

outlier_LOF = []
clf = LocalOutlierFactor(n_neighbors=30)
predictions = clf.fit_predict(df_samp)
for index in df_samp[predictions == -1].index:
  outlier_LOF.append(df_samp.index.get_loc(index))

# 外れ値を除外
df_samp_LOF = df_samp.drop(df_samp.index[outlier_LOF])

# 除外後のデータ数を確認
print(f'{len(df_samp_LOF)}/{len(df_samp)}')

>>

print(f'{len(df_samp_LOF)}/{len(df_samp)}')
4348/4412
```
## 外れ値を除外
```python:学習と結果
X_GBDT_LOF, y_GBDT_LOF, df_GBDT_LOF = dframe_copy(df_samp_LOF, "SOURCE")

train_X, test_X, train_y, test_y = train_test_split(X_GBDT_LOF, y_GBDT_LOF, test_size=0.2, random_state=42)
train_X, val_X, train_y, val_y = train_test_split(train_X, train_y, test_size=0.1, random_state=42)

# GBDTで学習
dtrain = xgb.DMatrix(train_X, label=train_y)
dval = xgb.DMatrix(val_X, label=val_y)
dtest = xgb.DMatrix(test_X)

params = {'objective': 'binary:logistic', 'silent':1, 'random_state': 71, 'eval_metric': 'auc'}
num_round = 25

watchlist = [(dtrain, 'train'), (dval, 'eval')]
GBDT_LOF_model = xgb.train(params, dtrain, num_round, evals=watchlist)

val_pred = GBDT_LOF_model.predict(dval)
val_y = val_y.tolist()
score_val = log_loss(val_y, val_pred)
print(f'logloss_val: {score_val: 4f}')

test_pred = GBDT_LOF_model.predict(dtest)

test_y = test_y.tolist()
AUC_GBDT_LOF = create_ROCcurve(test_y, test_pred)
tn_GBDT_LOF, fp_GBDT_LOF, fn_GBDT_LOF, tp_GBDT_LOF, specificity_GBDT_LOF, recall_GBDT_LOF, f1_GBDT_LOF = create_cm(test_y, test_pred, True)
AUC_list.append(["AUC_GBDT_LOF", np.round(AUC_GBDT_LOF, 3)])
specificity_list.append(["specificity_GBDT_LOF", np.round(specificity_GBDT_LOF, 3)])
recall_list.append(["recall_GBDT_LOF", np.round(recall_GBDT_LOF, 3)])
f1_score_list.append(["f1_GBDT_LOF", np.round(f1_GBDT_LOF, 3)])
```

### 結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683664/34844447-93aa-a530-4014-a0466541713e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683664/57ba7425-5ae6-ac21-a2ba-0951efa3c8d6.png)

## 外れ値を除外し、特徴選択したパラメータ([②参照](https://qiita.com/tuk19/items/dd759caded18d0aeeb20))で学習
```python:学習と結果
# スコアが表示されないカラムを削除
X_GBDT_LOF, y_GBDT_LOF, df_GBDT_LOF = dframe_copy(df_samp_LOF, "SOURCE")
df_GBDT_LOF_dropzero = df_GBDT_LOF.drop(['HAEMATOCRIT_AB_1', 'HAEMOGLOBINS_AB_1', 'ERYTHROCYTE_AB_2',
       'LEUCOCYTE_AB_1', 'LEUCOCYTE_AB_2', 'THROMBOCYTE_AB_1',
       'THROMBOCYTE_AB_2', 'MCH_AB_1', 'MCH_AB_2', 'MCHC_AB_1', 'MCHC_AB_2'],axis=1)

X_GBDT_LOF_dropzero = df_GBDT_LOF_dropzero.values
# 変数を訓練用とテスト用に分割
train_X, test_X, train_y, test_y = train_test_split(X_GBDT_LOF_dropzero, y_GBDT_LOF, test_size=0.2, random_state=42)
train_X, val_X, train_y, val_y = train_test_split(train_X, train_y, test_size=0.1, random_state=42)

# GBDTで学習
dtrain = xgb.DMatrix(train_X, label=train_y)
dval = xgb.DMatrix(val_X, label=val_y)
dtest = xgb.DMatrix(test_X)

params = {'objective': 'binary:logistic', 'silent':1, 'random_state': 71, 'eval_metric': 'auc'}
num_round = 25

watchlist = [(dtrain, 'train'), (dval, 'eval')]
GBDT_LOF_dropzero_model = xgb.train(params, dtrain, num_round, evals=watchlist)

val_pred = GBDT_LOF_dropzero_model.predict(dval)
val_y = val_y.tolist()
score_val = log_loss(val_y, val_pred)
print(f'logloss_val: {score_val: 4f}')

pred_y = GBDT_LOF_dropzero_model.predict(dtest)

test_y = test_y.tolist()
AUC_GBDT_LOF_dropzero = create_ROCcurve(test_y, pred_y)
tn_GBDT_LOF_dropzero, fp_GBDT_LOF_dropzero, fn_GBDT_LOF_dropzero, tp_GBDT_LOF_dropzero, specificity_GBDT_LOF_dropzero, recall_GBDT_LOF_dropzero, f1_GBDT_LOF_dropzero = create_cm(test_y, pred_y, True)
AUC_list.append(["AUC_GBDT_LOF_IN", np.round(AUC_GBDT_LOF_dropzero, 3)])
specificity_list.append(["specificity_GBDT_LOF_IN", np.round(specificity_GBDT_LOF_dropzero, 3)])
recall_list.append(["recall_GBDT_LOF_IN", np.round(recall_GBDT_LOF_dropzero, 3)])
f1_score_list.append(["f1_GBDT_LOF_IN", np.round(f1_GBDT_LOF_dropzero, 3)])
```
### 結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683664/691bf4ca-057a-8f49-c79e-ed4c435e13b4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683664/73550b1c-59b0-ba08-2522-269f797db5ac.png)

## 評価指標の比較
```python:評価指標
AUC_list = sorted(AUC_list, reverse=True, key=lambda x: x[1])
specificity_list = sorted(specificity_list, reverse=True, key=lambda x: x[1])
recall_list = sorted(recall_list, reverse=True, key=lambda x: x[1])
f1_score_list = sorted(f1_score_list, reverse=True, key=lambda x: x[1])

print("---ROC_AUC---")
for i in range(len(AUC_list)):
  print(AUC_list[i])
print("---特異度---")
for i in range(len(specificity_list)):
  print(specificity_list[i])
print("----感度----")
for i in range(len(recall_list)):
  print(recall_list[i])
print("----f1値----")
for i in range(len(f1_score_list)):
  print(f1_score_list[i])
```
### 結果
XGBoostの結果は[こちら](https://qiita.com/tuk19/items/b0df794d4d827511adf1)を参照
外れ値除外(_LOF), 重要度の低い特徴量を除外(_NI)
|ROC_AUC|特異度|感度|f1値|
|:-:|:-:|:-:|:-:|
|['AUC_GBDT_LOF', 0.827]|['specificity_GBDT_LOF', 0.873]|['recall_GBDT_LOF_IN', 0.669]|['f1_GBDT_LOF', 0.71]|
|['AUC_GBDT_LOF_IN', 0.823]|['specificity_GBDT：', 0.855]|['recall_GBDT_LOF', 0.654]|['f1_GBDT_LOF_IN', 0.709]|
|['AUC_GBDT:', 0.816]|['specificity_GBDT_NI：', 0.855]|['recall_GBDT：', 0.634]|['f1_GBDT：', 0.692]|
|['AUC_GBDT_NI:', 0.816]|['specificity_GBDT_LOF_IN', 0.854]|['recall_GBDT_NI：', 0.634]|['f1_GBDT_NI：', 0.692]|

## 考察
外れ値を除くだけでも全体的に改善が見られた。
外れ値除外と特徴選択を組みあせた場合は、外れ値除外のみと比較して感度は若干改善したが、全体として大きな改善は見られなかった。

## 参考資料
[機械学習における前処理3　欠損値・外れ値・不均衡データ](https://qiita.com/ngayope330/items/7dce95abc42cfe6566bf)
[【外れ値,python】Local outlier Factor(LoF)の紹介【scikit-learn】](https://www.tcom242242.net/entry/ai-2/%E5%A4%96%E3%82%8C%E5%80%A4/local-outlier-factor/)
[Local Outlier Factor](https://axa.biopapyrus.jp/machine-learning/outlier-detection/local-outlier-factor.html)