FeatureUnionで特徴量結合するシンプルな方法

Scikit-LearnのFeatureUnion関数が初見で結構わかりにくかったのでメモしておきます。[「Marking imputed values」](https://scikit-learn.org/stable/modules/impute.html#marking-imputed-values)を見ていて？となり、学習しました。
これは**特徴量エンジニアリング時に特徴量を一括処理**をしてまとめてくれます。[`Pipleline`モジュール](https://scikit-learn.org/stable/modules/classes.html#module-sklearn.pipeline)に含まれており、有名な[`Pipeline`関数](https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html)の兄弟みたいなものです。

https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.FeatureUnion.html

FeatureUnionの簡易版で[`maike_union`関数](https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.make_union.html)があります。使うときは、どちらがいいか検討すべきですね。

# シンプルな使い方
irisデータを読み込み、1列目の「sepal length (cm)」だけをDataframeに設定します。DataFrame化せずにNumpy配列のままでもOKですが、単純に私がNumpy慣れしていないので入れているだけです。

```python
import pandas as pd
from sklearn import datasets
from sklearn.pipeline import FeatureUnion
from sklearn.preprocessing import FunctionTransformer

# Iris データセットを読み込み、1列目だけをDataFrameへ設定
iris = datasets.load_iris()
df = pd.DataFrame(iris.data[:, 0], columns=[iris.feature_names[0]])
print(df.head())
```

```:DataFrame最初の5行
   sepal length (cm)
0                5.1
1                4.9
2                4.7
3                4.6
4                5.0
```

[`FunctionTransformer`関数](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.FunctionTransformer.html)使って以下の2種類の特徴量に変換しています。通常、変換をするので「変換」という言葉を使っていますが、今回の例では「1. raw」は単純にするために変換すらしていません。

1. raw: ただ元の値を出すだけ
1. double: 元の値を2倍

```python
# 複数の特徴量を結合する
union = FeatureUnion([('1.raw', FunctionTransformer(lambda x: x)),
                      ('2.double', FunctionTransformer(lambda x: x*2))])
print(union.fit_transform(df)[:5])
```

結果セットを見るともとの値と2倍になった値が出ていますね。

```:FeatureUnion結果の5行
[[ 5.1 10.2]
 [ 4.9  9.8]
 [ 4.7  9.4]
 [ 4.6  9.2]
 [ 5.  10. ]]
```

以下の記事を見る限り、結果はDataFrameにはならないようです。Numpyに慣れていない私のような人間には使いにくい。

https://blog.amedama.jp/entry/scikit-featureunion-pandas-issue

# 実際の使い方
「シンプルな使い方」では、使い方に焦点を当てたので実際にどんな使い方をするかです。
mnistの手書き数字データを引っ張ってきます。

```python
from sklearn.datasets import load_digits
from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.naive_bayes import GaussianNB
from sklearn.pipeline import Pipeline, FeatureUnion
from sklearn.model_selection import cross_validate
import pprint

digits = load_digits()
print(digits.data.shape)
```

1797レコードの8×8の手書きデータがあります。

```:データshape
(1797, 64)
```

PCAとLDAで特徴量削減をして、Gaussianナイーブベイズで分類します。このときにPCAとLDAで同時並列で特徴量を生成し、結合します。今回は使っていませんが`FeatureUnion`には`n_jobs`というパラメータがあり、並列処理が可能です。

```python
pca = PCA(n_components=30)
lda = LDA()
gnb = GaussianNB()

pca_lda_gnb = Pipeline([("reduction", FeatureUnion([("pca", pca),
                                                    ("lda", lda)])),
                        ("gnb", gnb)])
pprint.pprint(cross_validate(pca_lda_gnb, digits.data, digits.target, cv=3))
```

で、結果は以下のようなscoreと時間が出ます。

```:結果
{'fit_time': array([0.21725082, 0.16855884, 0.08645368]),
 'score_time': array([0.01812887, 0.00961113, 0.00560999]),
 'test_score': array([0.92821369, 0.93489149, 0.92153589])}
```

# 参考
以下の記事を参考にしました。

https://www.haya-programming.com/entry/2018/05/15/144124

https://zerofromlight.com/blogs/detail/75/