ColumnTransformerで特徴ごとに異なる変換を行うパイプラインを構築する
データアナリティクス事業本部の鈴木です。
前回はscikit-learnのpipelineモジュールで簡単なパイプラインを構築しました。
今回はカラムごとに異なった前処理を適用するパイプラインの構築方法をご紹介します。
準備
検証した環境
- コンテナ:jupyter/datascience-notebook
- scikit-learn:0.24.2
データの作成
今回はseaborn-dataのpenguinsを利用します。
penguinsは、以下のpalmerpenguinsをもとにしたデータです。
以下のようにしてデータを作成しておきます。
import seaborn as sns from sklearn.model_selection import train_test_split # データのロード df = sns.load_dataset('penguins') # カラムの型と欠損値の有無を確認 df.info() ## <class 'pandas.core.frame.DataFrame'> ## RangeIndex: 344 entries, 0 to 343 ## Data columns (total 7 columns): ## # Column Non-Null Count Dtype ## --- ------ -------------- ----- ## 0 species 344 non-null object ## 1 island 344 non-null object ## 2 bill_length_mm 342 non-null float64 ## 3 bill_depth_mm 342 non-null float64 ## 4 flipper_length_mm 342 non-null float64 ## 5 body_mass_g 342 non-null float64 ## 6 sex 333 non-null object ## dtypes: float64(4), object(3) ## memory usage: 18.9+ KB df.isnull().sum() ## species 0 ## island 0 ## bill_length_mm 2 ## bill_depth_mm 2 ## flipper_length_mm 2 ## body_mass_g 2 ## sex 11 ## dtype: int64 # 特徴と推定対象に分離 X = df.drop("species", axis=1) y = df["species"] # 訓練データとテストデータに分離 X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
今回は、ペンギンの種類(speciesカラムの値)を推定したいです。
特徴に2つカテゴリー変数があることが分かります。欠損値は、量的変数のbill_length_mmなどに加え、カテゴリー変数である性別のカラムにもあります。
2つのカテゴリー変数のうち、片方は欠損値があるため、もう片方のカテゴリー変数とは異なった処理をする必要があるのが今回のポイントです。
パイプラインの作成
前処理の方針
データセットに含まれる値から、以下の3通りに分けて前処理をすることとしましょう。
- 量的変数:欠損値を補完し、標準化する。
-
カテゴリー変数
- 欠損値があるもの:欠損値を補完し、One-Hotエンコーディングする。
- 欠損値がないもの:One-Hotエンコーディングする。
これらを満たすパイプラインを作成してみましょう。
カラムごとに異なる処理を行うパイプラインの作成
カラムごとに異なるtransformersを適用するには、ColumnTransformerを使います。
ColumnTransformerのコンストラクタのtransformers引数に、名前、対象となるカラムの指定および各カラムに適用するtransformerのタプルのリストを渡します。
対象となるカラムの指定は、ColumnTransformerに渡されるオブジェクトがDataFrameであれば、列名のリストを使えるほか、make_column_selectorを使ってより柔軟な選択を行うことも可能です。
transformersには、OneHotEncoderのような単一のtransformerを渡すこともできます。また、今回のように、カラムに対して順番の決まった前処理が行いたい場合(例えば、欠損値を埋めた後に標準化したい)には、パイプラインのインスタンスを渡すことができます。
from sklearn.compose import ColumnTransformer from sklearn.impute import SimpleImputer from sklearn.pipeline import Pipeline from sklearn.preprocessing import OneHotEncoder from sklearn.preprocessing import StandardScaler from sklearn.experimental import enable_hist_gradient_boosting from sklearn.ensemble import HistGradientBoostingClassifier # 量的変数の前処理 # 量的変数に対して行うパイプラインを構築する numeric_features = ["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g"] numeric_transformer = Pipeline(steps=[ ('num_imputer', SimpleImputer(strategy='median')), ('scaler', StandardScaler())]) # カテゴリー変数の前処理1 # islandカラムに対して行うパイプラインを構築する categorical_transformer1 = Pipeline(steps=[ ('onehot1', OneHotEncoder(handle_unknown='ignore'))]) # カテゴリー変数の前処理2 # sexカラムに対して行うパイプラインを構築する categorical_transformer2 = Pipeline(steps=[ ('cat_imputer', SimpleImputer(strategy='constant', fill_value="unknown")), ('onehot2', OneHotEncoder(handle_unknown='ignore'))]) # ColumnTransformerの作成 preprocessor = ColumnTransformer( transformers=[ ('num', numeric_transformer, numeric_features), ('cat1', categorical_transformer1, ["island"]), ('cat2', categorical_transformer2, ["sex"])]) # 全体のパイプラインの作成 pipe = Pipeline([("preprocessor", preprocessor), ("Classifier", HistGradientBoostingClassifier())])
上記コードでは以下の4つのパイプラインを作成しました。
- 量的変数を処理するnumeric_transformer
- 欠損値のないカテゴリー変数を処理するcategorical_transformer1
- 欠損値のあるカテゴリー変数を処理するcategorical_transformer2
- 処理全体のパイプラインとなるpipe
特に1から3のパイプラインは、ColumnTransformerのコンストラクタ内で、それぞれどのカラムに適用するか指定しています。
作成したパイプラインを確認します。
from sklearn import set_config set_config(display='diagram') pipe
最後に参考までにscore
を実行し、推論ができることを確認しておきます。
# 学習 pipe.fit(X_train, y_train) # 推論結果のaccuracyを計算する。 pipe.score(X_test, y_test) ## 0.9767441860465116
最後に
ColumnTransformerを使って、カラムごとに異なるtransformersを適用するパイプラインを構築できました。
また、その前処理の順序を制御することができました。
実際の前処理では、このようにカラムごとに異なる前処理をすることがほとんどなので、ColumnTransformerを使いこなすことで、実戦でもさらにパイプラインが活躍しそうですね。