欠損値の補間方法が与える時系列データのautoMLのパイプライン構築とその予測率に与える影響について

　おはこんばんにちは。未だにマークダウン記法を覚えられない初心者ブロガー兼プログラマー（笑）です。欠損値の補間方法について、色々思うところがあったので自分で記事を書くことにしました。コードは省いて仮説（笑）と検証結果（笑）と考察（笑）だけ書きます。言語はPythonで環境はWSL2ブンツです。再現性はわざと持たせていないので技術記事ではなく芸術記事です。（再現性のあるものを技術、ないものを芸術と定義する）深堀りしたい方はどうぞやってみて下さい。出来れば連絡を下さい（結果だけ知りたいので）。連絡先はプロフにある通りです。色々な事情があるので、待機時間1ヶ月は見積もっておいて頂けると大変助かります。
 　全部終わってから学習関数を入れ忘れたことに気がついておほーんとなりましたが、まあ多分大丈夫。うん。趣味だし。うん。このまま行きます❗❗❗（ヤバすぎ）

# はじめに
あ、出来た。データは時系列の金融データを使用しました。内容は伏せたほうが良いと言われたため、伏せます。人為的に欠損値をランダムで作成（デフォ欠損に加えて適当にランダムで消す。人力ではない）した後に、データ全体をカラム別で欠損値のあるカラム列、ない列に分割し、ある列のみ補間を行ったあと、元データに上書きします。後述しますが人工知能を使う際には、カラムごとに正常値を用いて学習をさせ、補間を行います。正常値はカラムを分けた後、インデックスで分類器にかけて欠損値を叩き落とすことで探し出します。ぱわー。
　例外処理として、カラム列全体が欠損値の場合は削除します。またこの時点で欠損値が存在する場合にはすべて0に置換します。
　その後、特徴量の生成を行い同様にします。
　その後、特徴量選択とランダムフォレストclassifireを用いて、時系列解析を行い、各補間方法について比較します。
　その後、生成した諸々のデータを元にtpot（ステータスはマシンスペックの関係でgeneration=20 popuration=100あとはデフォ）でアンサンブルモデルを構築し、それぞれのモデル選択への影響、正確率、f1値（、混同行列）を比較します。アンサンブルモデルの構築は2回ずつ行います。

# 選択した補間方法について
・カテゴリカル変数の場合は最頻値、連続値の場合は中央値で補間。（あんま分ける意味はないが、保守性的な意味で一応）
・ベイジアンリッジ回帰（基本デフォでmax_iterは100）
・K近傍法（基本デフォでn_neighbor=100）
・2層アフィン化LSTM　ユニットは64,32,1。　活性化関数はadam。（モーメンタムも良いかなーとおもったりした。ここはデータの特性によると思う）損失は中央値のやつ。（lookback = 100の前方補間）
・同様の後方補間
・同様の双方向補間（50ずつ）

# （この時点で検討したこと）
4層、8層アフィン化LSTMモデルは？
→金がねぇ（マシンスペックが足りねえ）

他の活性化関数は？
→あんまやるメリットを感じなかった。（）

autoMLではなく、モデルを限定したほうが良かったのではないか（XGboostやロジスティクス回帰など）
→何故それを選んだのかということを検討すると面倒なのでautoMLにした。決定木は時系列解析でやっているし、ある程度参考にはなると思う。（要するにめんどくさかった）

探索範囲を広げないのか？
→だから金がn

何故時系列データなのか。
→自分が興味を持ったのとある程度周期性があると考えたため。参考文献は載せない。めんどくさいから。多すぎる。

なぜ2回ずつなのか。確率を収束させるためには……
→検討はしましたが、マシンのほうが保たないのでやめました。もし本気でやる方がいれば各1000回はおすすめしておきますっと。

# 検証結果
こんな感じ(読み飛ばしたほうがいい)

## テストデータでの結果N2:
    Accuracy: 0.6038
    F1 Score: 0.5858


## 時系列交差検証結果:
    Accuracy: mean = 0.4860, std = 0.0363
    F1: mean = 0.4368, std = 0.0185
    Returns: mean = nan, std = nan

```php
# Average CV score on the training set was: 0.5661284624994615
exported_pipeline = make_pipeline(
    StackingEstimator(estimator=LogisticRegression(C=100.0, solver="liblinear")),
    StackingEstimator(estimator=LogisticRegression(C=100.0, solver="liblinear")),
    LogisticRegression(C=10.0, solver="liblinear")
)
```


## テストデータでの結果N2:
    Accuracy: 0.5813
    F1 Score: 0.5735


## 時系列交差検証結果:
    Accuracy: mean = 0.4840, std = 0.0395
    F1: mean = 0.4428, std = 0.0318
    Returns: mean = nan, std = nan

```php
# Average CV score on the training set was: 0.5754738006002384
exported_pipeline = make_pipeline(
    StackingEstimator(estimator=LogisticRegression(C=100.0, solver="liblinear")),
    StackingEstimator(estimator=LogisticRegression(C=100.0, solver="liblinear")),
    DecisionTreeClassifier(max_depth=3, min_samples_leaf=4, min_samples_split=8)
)
```

## テストデータでの結果N3:
    Accuracy: 0.5062
    F1 Score: 0.4316


## 時系列交差検証結果:
    Accuracy: mean = 0.5047, std = 0.0104
    F1: mean = 0.4573, std = 0.0149
    Returns: mean = nan, std = nan

```php
# Average CV score on the training set was: 0.5440804721492267
exported_pipeline = make_pipeline(
    StackingEstimator(estimator=LogisticRegression(C=100.0, solver="liblinear")),
    AdaBoostClassifier(learning_rate=0.01, n_estimators=100)
)
```


## テストデータでの結果N4:
    Accuracy: 0.5732
    F1 Score: 0.5490


## 時系列交差検証結果:
    Accuracy: mean = 0.5027, std = 0.0232
    F1: mean = 0.4510, std = 0.0179
    Returns: mean = nan, std = nan
```php
# Average CV score on the training set was: 0.5689946725254528
exported_pipeline = make_pipeline(
    StackingEstimator(estimator=LogisticRegression(C=100.0, solver="liblinear")),
    StackingEstimator(estimator=LogisticRegression(C=100.0, solver="liblinear")),
    AdaBoostClassifier(learning_rate=0.01, n_estimators=50)
)
```


## テストデータでの結果N4:
    Accuracy: 0.5639
    F1 Score: 0.5614
    
    
## 時系列交差検証結果:
    Accuracy: mean = 0.4826, std = 0.0496
    F1: mean = 0.4461, std = 0.0346
    Returns: mean = nan, std = nan
```php
# Average CV score on the training set was: 0.5819244963310789
exported_pipeline = make_pipeline(
    StackingEstimator(estimator=DecisionTreeClassifier(max_depth=2, min_samples_leaf=2, min_samples_split=18)),
    StackingEstimator(estimator=LogisticRegression(C=100.0, solver="liblinear")),
    StackingEstimator(estimator=LogisticRegression(C=100.0, solver="liblinear")),
    LogisticRegression(C=10.0, solver="liblinear")
)
```

## テストデータでの結果N5:
    Accuracy: 0.4112
    F1 Score: 0.3083


## 時系列交差検証結果:
    Accuracy: mean = 0.4763, std = 0.0136
    F1: mean = 0.4113, std = 0.0210
    Returns: mean = nan, std = nan
```php
# Average CV score on the training set was: 0.4587972256924998
exported_pipeline = DecisionTreeClassifier(max_depth=2, min_samples_leaf=2, min_samples_split=18)
```

## テストデータでの結果N5:
    Accuracy: 0.4149
    F1 Score: 0.3116


## 時系列交差検証結果:
    Accuracy: mean = 0.4763, std = 0.0136
    F1: mean = 0.4113, std = 0.0210
    Returns: mean = nan, std = nan
    
```php
# Average CV score on the training set was: 0.4587972256924998
exported_pipeline = DecisionTreeClassifier(max_depth=2, min_samples_leaf=2, min_samples_split=18)
```

## テストデータでの結果N6:
    Accuracy: 0.4149
    F1 Score: 0.3116


## 時系列交差検証結果:
    Accuracy: mean = 0.4840, std = 0.0199
    F1: mean = 0.4148, std = 0.0281
    Returns: mean = nan, std = nan
```php
# Average CV score on the training set was: 0.4587972256924998
exported_pipeline = DecisionTreeClassifier(max_depth=2, min_samples_leaf=6, min_samples_split=19)
```
## テストデータでの結果N6:
    Accuracy: 0.4149
    F1 Score: 0.3116
        

## 時系列交差検証結果:
    Accuracy: mean = 0.4840, std = 0.0199
    F1: mean = 0.4148, std = 0.0281
    Returns: mean = nan, std = nan
```php
# Average CV score on the training set was: 0.4587972256924998
exported_pipeline = DecisionTreeClassifier(max_depth=2, min_samples_leaf=13, min_samples_split=13)
```

## テストデータでの結果N7:
    Accuracy: 0.4167
    F1 Score: 0.3132


## 時系列交差検証結果:
    Accuracy: mean = 0.4840, std = 0.0199
    F1: mean = 0.4148, std = 0.0281
    Returns: mean = nan, std = nan
```php
# Average CV score on the training set was: 0.4587972256924998
exported_pipeline = DecisionTreeClassifier(max_depth=2, min_samples_leaf=13, min_samples_split=13)
```
## テストデータでの結果N7:
    Accuracy: 0.4130
    F1 Score: 0.3099


## 時系列交差検証結果:
    Accuracy: mean = 0.4840, std = 0.0199
    F1: mean = 0.4148, std = 0.0281
    Returns: mean = nan, std = nan
```php
# Average CV score on the training set was: 0.4587972256924998
exported_pipeline = DecisionTreeClassifier(max_depth=2, min_samples_leaf=6, min_samples_split=19)
```


# 検討事項と考察
　全体的に強弱はあれど、下方バイアスが発生している可能性が極めて高い。（逃げ）ベイジアン、K近傍法、LSTMモデルに関してはその限りではない。
　では真面目に、この結果を深く分析し、欠損値の補間方法が時系列データのautoMLパイプライン構築と予測性能に与える影響について、詳細な考察をします。

## 1. 補間方法の影響:

各補間方法（最頻値/中央値、ベイジアンリッジ回帰、KNN法、LSTMによる前方/後方/双方向補間）は、データの特性や欠損パターンに応じて異なる影響を与えると考えられる。

- 最頻値/中央値補間: 
  シンプルで計算コストが低いが、時系列の連続性や傾向を考慮しないため、時系列データの特性を損なう可能性がある。

- ベイジアンリッジ回帰:
  線形関係を仮定し、過学習を抑制する効果がある。時系列データの傾向を捉えられる可能性があるが、非線形な関係を見逃す可能性もある。

- KNN法:
  局所的な類似性を利用するため、複雑なパターンを捉えられる可能性がある。しかし、適切なn_neighborsの選択が重要で、外れ値の影響を受けやすい。

- LSTM補間（前方/後方/双方向）:
  時系列データの長期依存性を学習できるため、複雑な時系列パターンを捉えられる可能性が高い。ただし、学習に時間がかかり、ハイパーパラメータの調整が難しい。

## 2. モデル選択への影響:

TPOTによって選択されたモデルを見ると、主にLogistic Regression、Decision Tree、AdaBoostが選ばれています。これは補間方法によってデータの特性が変化し、それに応じて最適なモデルが変わっていることを示唆している。

- LogisticRegression: 線形の決定境界を持つ。補間方法によってデータの線形性が強調された場合に選択されやすい。

- DecisionTree: 非線形の関係性を捉えられる。補間によってデータに階層的な構造が生まれた場合に有効。

- AdaBoost: 弱学習器の組み合わせで複雑なパターンを学習。補間によってデータに多様な特徴が生まれた場合に選択されやすい。

## 3. 予測性能への影響:

結果を見ると、Accuracyは0.41から0.60の範囲、F1スコアは0.31から0.58の範囲で変動しています。この変動は補間方法の違いによる影響を示唆している。

- 最も高い性能（N2）: Accuracy 0.6038, F1 Score 0.5858
- 最も低い性能（N5,N6,N7）: Accuracy 約0.41, F1 Score 約0.31

この差は補間方法の選択が予測性能に大きな影響を与えることを示唆している。

## 4. 時系列交差検証の結果:

時系列交差検証の結果を見ると、ほとんどの場合でテストデータでの結果よりも低いパフォーマンスを示しています。これは以下の理由が考えられる：

- データの時間的依存性: 時系列データの特性上、未来のデータを使って過去を予測することはできないため、交差検証では性能が低下する。

- モデルの過学習: テストデータに対して過度に最適化されている可能性がある。

- データの非定常性: 時間とともにデータの特性が変化している可能性がある。

## 5. まとめ:

) 補間方法の組み合わせ:
単一の補間方法ではなく、複数の手法を組み合わせることで、よりロバストな結果が得られる可能性がある。例えば、短期的な欠損にはKNN、長期的な欠損にはLSTMを使用するなど。また欠損値フラグの検討も十分に土台に上る可能性があると考えられる。

) モデルのアンサンブル:
TPOTの結果を見ると、単一のモデルよりもStackingEstimatorを使用したアンサンブルモデルの方が高い性能を示しています。異なる補間方法で生成したデータセットごとにモデルを学習し、それらを組み合わせることで、よりロバストな予測が可能になると考えられる。


) モデルの解釈可能性:
決定木やロジスティック回帰など、解釈しやすいモデルが選択されているので良かったが、より複雑なモデルを使用する場合は、SHAP値などの手法を用いて解釈可能性を担保することを検討すべき。


　結論として、欠損値の補間方法は時系列データのautoMLパイプライン構築と予測性能に大きな影響を与えると考えられる。単一の「最適」な方法はなく、データの特性、欠損パターン、予測タスクの性質に応じて、適切な補間方法を選択または組み合わせることが重要である。また、補間は予測パイプラインの一部に過ぎず、特徴量エンジニアリング、モデル選択、ハイパーパラメータ最適化など、他の要素も総合的に考慮する必要がある。さらに、時系列データの特性（非定常性、周期性、季節性など）を十分に考慮し、適切な評価指標を用いてモデルの性能を評価することが、よりロバストで信頼性の高い予測システムの構築につながると考える。（無難　is the best.）
 
 以下参考文献
・https://ir.soken.ac.jp/records/5284

・https://ismrepo.ism.ac.jp/records/33459

・https://kopaprin.hatenadiary.jp/entry/2019/08/08/021749

・https://qiita.com/m-morohashi/items/47b2e812689f9b6adaf7

　上記を踏まえたうえで、ふーんとなった。良いマシンが欲しいなー。あとお金が欲しいなー。ナルコレプシー治んないかなー。以上。ではまた。