まずは蝋の翼から。

学んだことを書きながら確認・整理するためのメモブログ。こういうことなのかな?といったことをふわっと書いたりしていますが、理解が浅いゆえに的はずれなことも多々あると思うのでツッコミ歓迎

特徴量作成を楽にするライブラリいくつかまとめて試す②xfeat

この記事はなにか

機械学習の特徴量を作るときに色々とめんどくさい部分を楽にできるライブラリの紹介。

具体的には以下を紹介する、

  • featuretools
  • xfeat

①のfeaturetoolsの記事は以下。脳死で既存特徴量の四則演算や集約などを一括で楽に作れる。

knknkn.hatenablog.com

今回は脳死ではなくある程度考えつつ特徴量を作りたいときに利用するxfeatを紹介する。

github.com

何ができるか

主な機能は以下

  • 特徴量の変換
  • 特徴量の加工
  • GBDTの変数重要度を用いた特徴量選択(+optunaにより最適化)

となる。
featuretoolsも似た感じだったが、あっちは指定した加工ができそうな特徴量に対しては脳死で全特徴量の加工を一気に作成できた。xfeatは任意で指定した特徴量に対して楽に加工ができる、という使い分けかな?あとxfeatではカテゴリカル変数に対しての変換が楽にできる。

ちなみに、pandasではなくcuDF((pandasより高速のデータフレームを提供するライブラリっぽい。))という形式にも対応していて、そっちを使った方が爆速になるとのことだが今回は使わない。

github.com

acro-engineer.hatenablog.com

なお、今回データとしてみんな大好きtitanicデータを使った以下の記事のコードをお借りする(一部変更してます)。

acro-engineer.hatenablog.com

特徴量の変換

github.com

sklearn.piplineのように、Pipelineを使ってencode処理をつなげたencoder群を作り、それをfit_transformで適用していく。

knknkn.hatenablog.com

その際のencoder objectとして以下を提供している。

  • Categorical encoder:

    • xfeat.SelectCategorical
    • xfeat.LabelEncoder
    • xfeat.ConcatCombination
    • xfeat.TargetEncoder
    • xfeat.CountEncoder
    • xfeat.UserDefinedLabelEncoder.
  • Numerical encoder:

    • xfeat.SelectNumerical
    • xfeat.ArithmeticCombination.
  • User-defined encoder:

    • xfeat.LambdaEncoder

例えば、以下のようにすると、SelectCategoricalConcatCombinationTargetEncoderという順で処理するencoderをdfに対して適用、という意味になる。

from xfeat import Pipeline, SelectCategorical, ConcatCombination, TargetEncoder

encoder = Pipeline(
    [
        SelectCategorical(),
        ConcatCombination(),
        TargetEncoder(),
    ]
)
df_encoded = encoder.fit_transform(df)

特定型の特徴量のDFを作る

普通にやると以下のように①dtypesをみて手打ちでがんばる②指定した型の列listを作る、のいずれかで地味にめんどい。

train_df.dtypes
# => 型一覧

categorical_features = [col for col in train_df.columns if train_df[col].dtype == 'object']

train_df[categorical_features].head()

f:id:chito_ng:20210526155524p:plain

xfeatでは以下のようにかける。

カテゴリカルデータ列のみのDFを作成

# カテゴリカルデータのみ
from xfeat import SelectCategorical


encoder = SelectCategorical()
encoder.fit_transform(train_df).head()

# 以下でも同じ
# SelectCategorical().fit_transform(train_df).head()

数値データ列のみのDFを作成

# 数値データのみ
from xfeat import SelectNumerical

encoder = SelectNumerical()
encoder.fit_transform(train_df).head()

# 以下でも同じ
# SelectNumerical().fit_transform(train_df).head()

f:id:chito_ng:20210526155444p:plain

特徴量の加工

カテゴリカル変数

機械学習ではカテゴリカル変数はそのままの文字列値で渡すことはできないので、なにかしらの変換が必要となる。

nami3373.hatenablog.com

sklearn.preprocessingで変換処理はできるが、np.arrayで返ってくるのでDataFrameで色々やっていると対応がちぐはぐでめんどかったりするがxfeatではよしなにやってくれる。

ちなみに、Category Encodersというライブラリのも便利らしい。xfeatではOne-hot-Encodingみたいに多次元展開するものはなく、1次元変換のエンコーディングのみっぽいので他のも使いたい場合はCategory Encodersかな?

contrib.scikit-learn.org

www.takapy.work

qiita.com

Label Encoding

from xfeat import Pipeline, SelectCategorical, LabelEncoder

encoder = Pipeline([
    SelectCategorical(exclude_cols=["Name", "Ticket"]), # Name,Ticketを除くカテゴリカル列のDFを作成
    LabelEncoder(output_suffix=""), # 渡されたDFをlabel encodeする
])

encoded_df = encoder.fit_transform(train_df)
encoded_df.head()

f:id:chito_ng:20210526160541p:plain

Count Encoding

from xfeat import CountEncoder

# count encode
from xfeat import Pipeline, SelectCategorical, LabelEncoder

encoder = Pipeline([
    SelectCategorical(exclude_cols=["Name", "Ticket"]), # Name,Ticketを除くカテゴリカル列のDFを作成
    CountEncoder(output_suffix=""), # 渡されたDFをcount encodeする
])

encoded_df = encoder.fit_transform(train_df)
encoded_df.head()

f:id:chito_ng:20210528110039p:plain

Target Encoding

# target encoding
# 以下のHoldout TS
# https://blog.amedama.jp/entry/target-mean-encoding-types
from sklearn.model_selection import KFold
from xfeat import TargetEncoder

fold = KFold(n_splits=5, shuffle=False)
encoder = TargetEncoder(
    input_cols=["Cabin"], # 変換対象
    target_col="Survived", # target
    fold=fold, # foldの取り方
    output_suffix="_re" # 変換後のsuffix
    )

encoded_df = encoder.fit_transform(train_df)
encoded_df[["Survived", "Cabin", "Cabin_re"]].head()

f:id:chito_ng:20210526160913p:plain

列組み合わせ(文字列)

また、カテゴリカル変数同士を組み合わせて新たな特徴量を作成することもできる。

# 2変数の組み合わせ
from xfeat import SelectCategorical, ConcatCombination, Pipeline

encoder = Pipeline([
    SelectCategorical(exclude_cols=["Ticket", "Name"]),
    ConcatCombination(
        # drop_origin=True, 
        output_suffix="_combine", 
        r=2), # 組み合わせは3つ
])

encoder.fit_transform(train_df).head()

f:id:chito_ng:20210526161128p:plain

# 3変数の組み合わせ
from xfeat import SelectCategorical, ConcatCombination, Pipeline

encoder = Pipeline([
    SelectCategorical(exclude_cols=["Ticket", "Name"]),
    ConcatCombination(
        # drop_origin=True, 
        output_suffix="_combine", 
        r=3), # 組み合わせは3つ
])

encoder.fit_transform(train_df).head()

f:id:chito_ng:20210526161150p:plain

数値変数

集約関数

featuretoolsと同様に集約関数の適用ができる。ただし、こちらでは指定した列に対して適用する。以下ではAgeとPclass列それぞれをSex単位で集約している。

from xfeat import aggregation

aggregated_df, aggregated_cols = aggregation(train_df,
                     group_key="Sex", # group key
                     group_values=["Age", "Pclass"], # 集約対象
                     agg_methods=["mean", "max"], # 集約関数
                     )

display(aggregated_cols, aggregated_df.head())

f:id:chito_ng:20210526161551p:plain

ちなみに、これはライブラリを使わない場合は以下で面倒

aggregated_df = train_df.copy()
 
# 性別ごとの年齢の平均値を特徴量に追加
sex_mean_df = train_df.groupby('Sex')['Age'].mean()
aggregated_df.loc[aggregated_df['Sex'] == 'female', 'agg_mean_Age_grpby_Sex'] = sex_mean_df['female']
aggregated_df.loc[aggregated_df['Sex'] == 'male', 'agg_mean_Age_grpby_Sex'] = sex_mean_df['male']

# 他の結合はagg特徴量は略

aggregated_df.head()

また、agg_methodsで自作関数を使うことはできるが処理後の列名がおかしくなるので少し工夫が必要とのこと。

www.smartbowwow.com

列組み合わせ(数値)

これもfeaturetoolsにもあるが、こちらでは指定した列に対して適用する。

# 2変数の組み合わせ
from xfeat import Pipeline, ArithmeticCombinations, SelectNumerical

encoder = Pipeline([
    SelectNumerical(),
    ArithmeticCombinations(
        # 兄弟/配偶者(SibSp)、両親/子供の数(Parch)を加算した特徴量を作成する
        input_cols=["SibSp", "Parch"], 
        #drop_origin=True, 
        output_suffix="_combine", 
        operator="+", # 加算
        r=2),
])

encoder.fit_transform(train_df).head()

Lambda処理

処理に対してLambda関数を書くことで自作の処理ができる。なお、この際のxは処理対象となるDFの全列(Pipelineの場合は直前までの処理結果)となるが、input_colsを使うと特定列のみが対象となる。

例えば、先程の列組み合わせ(数値)に対して更に四捨五入する処理を追加する。

# 2変数の組み合わせ + lambda処理
from xfeat import Pipeline, ArithmeticCombinations, SelectNumerical, LambdaEncoder

encoder = Pipeline([
    SelectNumerical(),
    ArithmeticCombinations(
        # 兄弟/配偶者、両親/子供の数を加算した特徴量を作成する
        input_cols=["Age", "Parch"], 
        #drop_origin=True, 
        output_suffix="_combine", 
        operator="+", # 加算
        r=2),
    LambdaEncoder(
        lambda x: round(float(x), -1), # 1の位で四捨五入
        input_cols=["Age", "Fare"], 
        output_suffix="_round",
        drop_origin=False,)
])


encoder.fit_transform(train_df).head()

f:id:chito_ng:20210528105156p:plain

もちろん、lambda処理なので文字列に対しても可能。