UPDATE: 2022-12-17 14:07:59
tidymodels
パッケージの使い方をいくつかのノートに分けてまとめている。tidymodels
パッケージは、統計モデルや機械学習モデルを構築するために必要なパッケージをコレクションしているパッケージで、非常に色んなパッケージがある。ここでは、今回はrecipes
というパッケージの使い方をまとめていく。モデルの数理的な側面や機械学習の用語などは、このノートでは扱わない。
下記の公式ドキュメントやtidymodels
パッケージに関する書籍を参考にしている。
recipes
パッケージの目的recipes
パッケージは、dplyr
パッケージのようなパイプ演算子を使って特徴量エンジニアリングを行うことを可能にするパッケージ。特徴量エンジニアリングの処理をステップに分けて管理することで、学習や評価の際のモデリング、予測の際のモデリングをスムーズに統一した処理を利用して実行できる。
recipes
パッケージの実行例recipes
パッケージの関数群の基本的な利用方法は下記の通り。お役所の書類の決済手続きのような感じでモデルに必要な情報を決定していく。
recipe
: モデルおよび学習データを定義するstep_a
: データの特徴量エンジニアリングの設計を行うprep
:
処理内容を決定する(必要な統計量などを推定する)bake
: 特徴量エンジニアリングや処理内容を適用する# recipe(y ~ x + z, data = train) %>%
# step_a() %>%
# step_b() %>%
# step_c() %>%
# step_d() %>%
# prep() %>%
# bake(new_data = train)
recipe
関数の引数に含まれるdata
は学習データである必要はなく、このデータは、変数名や型の情報をカタログ化するためにのみ使用される。そのため、モデルを対数変換したいからといって、この段階で変換してはいけない。
# recipe(log(Sepal.Length) ~ ., data = iris)
# Error in `inline_check()`:
# ! No in-line functions should be used here; use steps to define baking actions.
また、特徴量エンジニアリングをステップに分けて記述していくことになるが、下記の通りかなり多くの特徴量エンジニアリングの関数が用意されている。Function referenceには説明付きで一覧があるので、こちらを見るのが良い。
library(recipes)
grep("step_", ls("package:recipes"), value = TRUE)
## [1] "step_arrange" "step_bagimpute"
## [3] "step_bin2factor" "step_BoxCox"
## [5] "step_bs" "step_center"
## [7] "step_classdist" "step_corr"
## [9] "step_count" "step_cut"
## [11] "step_date" "step_depth"
## [13] "step_discretize" "step_dummy"
## [15] "step_dummy_extract" "step_dummy_multi_choice"
## [17] "step_factor2string" "step_filter"
## [19] "step_filter_missing" "step_geodist"
## [21] "step_harmonic" "step_holiday"
## [23] "step_hyperbolic" "step_ica"
## [25] "step_impute_bag" "step_impute_knn"
## [27] "step_impute_linear" "step_impute_lower"
## [29] "step_impute_mean" "step_impute_median"
## [31] "step_impute_mode" "step_impute_roll"
## [33] "step_indicate_na" "step_integer"
## [35] "step_interact" "step_intercept"
## [37] "step_inverse" "step_invlogit"
## [39] "step_isomap" "step_knnimpute"
## [41] "step_kpca" "step_kpca_poly"
## [43] "step_kpca_rbf" "step_lag"
## [45] "step_lincomb" "step_log"
## [47] "step_logit" "step_lowerimpute"
## [49] "step_meanimpute" "step_medianimpute"
## [51] "step_modeimpute" "step_mutate"
## [53] "step_mutate_at" "step_naomit"
## [55] "step_nnmf" "step_nnmf_sparse"
## [57] "step_normalize" "step_novel"
## [59] "step_ns" "step_num2factor"
## [61] "step_nzv" "step_ordinalscore"
## [63] "step_other" "step_pca"
## [65] "step_percentile" "step_pls"
## [67] "step_poly" "step_poly_bernstein"
## [69] "step_profile" "step_range"
## [71] "step_ratio" "step_regex"
## [73] "step_relevel" "step_relu"
## [75] "step_rename" "step_rename_at"
## [77] "step_rm" "step_rollimpute"
## [79] "step_sample" "step_scale"
## [81] "step_select" "step_shuffle"
## [83] "step_slice" "step_spatialsign"
## [85] "step_spline_b" "step_spline_convex"
## [87] "step_spline_monotone" "step_spline_natural"
## [89] "step_spline_nonnegative" "step_sqrt"
## [91] "step_string2factor" "step_time"
## [93] "step_unknown" "step_unorder"
## [95] "step_window" "step_YeoJohnson"
## [97] "step_zv"
では、実際にレシピを組み立てていく。使用するデータはrsample
パッケージのノートで使用したデータをここでも利用する。数値変換、カテゴリ変換、欠損値補完の例を通じて、receipe
パッケージへの理解を深める。
library(tidymodels)
library(tidyverse)
<- read_csv("https://raw.githubusercontent.com/SugiAki1989/statistical_note/main/note_TidyModels00/df_past.csv")
df_past set.seed(1989)
<- df_past %>% initial_split(prop = 0.8, strata = "Status")
df_initial <- df_initial %>% training()
df_train <- df_initial %>% testing() df_test
まずは基本的な数値変換手法である、標準化を行ってみる。step_normalize
関数を利用して、prep
関数、bake
関数とつなげていく。他にも、対数変換を行うstep_log()
関数、
ロジット変換を行うstep_logit
関数、平方根変換を行うstep_sqrt
関数、離散化のためのstep_discretize
関数、step_cut
関数もある。
recipe(Status ~ ., data = df_train) %>%
step_normalize(Age) %>%
prep() %>%
bake(new_data = df_train) %>%
select(Age)
## # A tibble: 3,206 × 1
## Age
## <dbl>
## 1 0.354
## 2 -1.45
## 3 -0.0977
## 4 -1.09
## 5 -1.27
## 6 -1.27
## 7 -0.0977
## 8 -0.550
## 9 0.535
## 10 -0.911
## # … with 3,196 more rows
複数のカラムを同時に指定することもできる。
recipe(Status ~ ., data = df_train) %>%
step_normalize(Age, Income) %>%
prep() %>%
bake(new_data = df_train) %>%
select(Age, Income)
## # A tibble: 3,206 × 2
## Age Income
## <dbl> <dbl>
## 1 0.354 -0.756
## 2 -1.45 -1.13
## 3 -0.0977 -0.135
## 4 -1.09 -0.421
## 5 -1.27 -0.694
## 6 -1.27 -0.234
## 7 -0.0977 -0.520
## 8 -0.550 -0.632
## 9 0.535 -0.868
## 10 -0.911 -0.160
## # … with 3,196 more rows
ここでは全ての数値型を変換したい。そんな時はall_numeric_predictors
関数を利用する。all_**
関数も沢山用意されており、特定の型のカラムを処理対象ししてまとめて決定できる。
all_numeric_predictors
とall_numeric
の違いは、前者が数値型の説明変数を選択するのに対し、後者は数値型を選択するという違いがある。その他も同様である。
grep("all_", ls("package:recipes"), value = TRUE)
## [1] "all_date" "all_date_predictors"
## [3] "all_datetime" "all_datetime_predictors"
## [5] "all_double" "all_double_predictors"
## [7] "all_factor" "all_factor_predictors"
## [9] "all_integer" "all_integer_predictors"
## [11] "all_logical" "all_logical_predictors"
## [13] "all_nominal" "all_nominal_predictors"
## [15] "all_numeric" "all_numeric_predictors"
## [17] "all_ordered" "all_ordered_predictors"
## [19] "all_outcomes" "all_predictors"
## [21] "all_string" "all_string_predictors"
## [23] "all_unordered" "all_unordered_predictors"
それでは標準化を行う。まずは変換前のdf_train
の中身を見ておく。標準化する前なので、オリジナルのスケールでデータが記録されている。
%>%
df_train select_if(is.numeric)
## # A tibble: 3,206 × 9
## Seniority Time Age Expenses Income Assets Debt Amount Price
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0 48 41 90 80 0 0 1200 1468
## 2 0 18 21 35 50 0 0 400 500
## 3 0 48 36 45 130 750 0 1100 1511
## 4 2 60 25 46 107 0 0 1500 2189
## 5 3 24 23 75 85 5000 0 600 1600
## 6 0 36 23 45 122 2500 0 400 400
## 7 1 54 36 70 99 0 0 950 950
## 8 5 48 31 44 90 0 0 1300 1700
## 9 2 60 43 75 71 3000 0 1500 1552
## 10 2 36 27 48 128 0 0 450 545
## # … with 3,196 more rows
レシピの手順に従って、データを標準化すると、数値型の列が標準化されていることがわかる。
recipe(Status ~ ., data = df_train) %>%
step_normalize(all_numeric_predictors()) %>%
prep() %>%
bake(new_data = df_train) %>%
select_if(is.numeric)
## # A tibble: 3,206 × 9
## Seniority Time Age Expenses Income Assets Debt Amount Price
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 -0.978 0.112 0.354 1.76 -0.756 -0.504 -0.265 0.353 0.0239
## 2 -0.978 -1.95 -1.45 -1.06 -1.13 -0.504 -0.265 -1.32 -1.48
## 3 -0.978 0.112 -0.0977 -0.545 -0.135 -0.433 -0.265 0.144 0.0906
## 4 -0.735 0.937 -1.09 -0.494 -0.421 -0.504 -0.265 0.980 1.14
## 5 -0.613 -1.54 -1.27 0.991 -0.694 -0.0311 -0.265 -0.902 0.229
## 6 -0.978 -0.712 -1.27 -0.545 -0.234 -0.268 -0.265 -1.32 -1.63
## 7 -0.857 0.525 -0.0977 0.735 -0.520 -0.504 -0.265 -0.170 -0.779
## 8 -0.369 0.112 -0.550 -0.596 -0.632 -0.504 -0.265 0.562 0.384
## 9 -0.735 0.937 0.535 0.991 -0.868 -0.220 -0.265 0.980 0.154
## 10 -0.735 -0.712 -0.911 -0.392 -0.160 -0.504 -0.265 -1.22 -1.41
## # … with 3,196 more rows
次はカテゴリ変換についてもいくつかの関数をまとめておく。ワンホットエンコーディングはstep_dummy(one_hot = TRUE)
で実行できる。FALSE
で使用すると、ベースカテゴリをおいた上で、ダミー変数化が行われる。下記の例ではfixed
がベースラインカテゴリとなっている。
recipe(Status ~ ., data = df_train) %>%
step_dummy(Job, one_hot = FALSE) %>%
prep() %>%
bake(new_data = df_train) %>%
select(starts_with("Job")) %>%
bind_cols(df_train %>% select(Job))
## # A tibble: 3,206 × 4
## Job_freelance Job_others Job_partime Job
## <dbl> <dbl> <dbl> <chr>
## 1 0 0 1 partime
## 2 0 0 1 partime
## 3 0 0 1 partime
## 4 0 0 0 fixed
## 5 0 0 0 fixed
## 6 0 0 1 partime
## 7 0 0 0 fixed
## 8 0 0 0 fixed
## 9 0 0 1 partime
## 10 0 0 0 fixed
## # … with 3,196 more rows
ワンホットエンコーディングの例はこちら。
recipe(Status ~ ., data = df_train) %>%
step_dummy(Job, one_hot = TRUE) %>%
prep() %>%
bake(new_data = df_train) %>%
select(starts_with("Job")) %>%
bind_cols(df_train %>% select(Job))
## # A tibble: 3,206 × 5
## Job_fixed Job_freelance Job_others Job_partime Job
## <dbl> <dbl> <dbl> <dbl> <chr>
## 1 0 0 0 1 partime
## 2 0 0 0 1 partime
## 3 0 0 0 1 partime
## 4 1 0 0 0 fixed
## 5 1 0 0 0 fixed
## 6 0 0 0 1 partime
## 7 1 0 0 0 fixed
## 8 1 0 0 0 fixed
## 9 0 0 0 1 partime
## 10 1 0 0 0 fixed
## # … with 3,196 more rows
お次はラベルエンコーディングを行う。ラベルエンコーディングはラベルを数値にする変換。因子型を数値にスコア化するstep_ordinalscore
関数も用意されているが、ここでは文字型を数値に直し、文字型を因子型にしてから数値型に戻す。まずは、文字型を選択できるall_nominal_predictors
関数で文字を因子型に変換する。
recipe(Status ~ ., data = df_train) %>%
step_string2factor(all_nominal_predictors()) %>%
prep() %>%
bake(new_data = df_train) %>%
str()
## tibble [3,206 × 14] (S3: tbl_df/tbl/data.frame)
## $ Seniority: num [1:3206] 0 0 0 2 3 0 1 5 2 2 ...
## $ Home : Factor w/ 6 levels "ignore","other",..: 4 2 1 6 3 3 6 6 3 6 ...
## $ Time : num [1:3206] 48 18 48 60 24 36 54 48 60 36 ...
## $ Age : num [1:3206] 41 21 36 25 23 23 36 31 43 27 ...
## $ Marital : Factor w/ 5 levels "divorced","married",..: 2 4 2 4 2 4 2 4 2 3 ...
## $ Records : Factor w/ 2 levels "no","yes": 1 2 1 1 1 1 1 1 1 1 ...
## $ Job : Factor w/ 4 levels "fixed","freelance",..: 4 4 4 1 1 4 1 1 4 1 ...
## $ Expenses : num [1:3206] 90 35 45 46 75 45 70 44 75 48 ...
## $ Income : num [1:3206] 80 50 130 107 85 122 99 90 71 128 ...
## $ Assets : num [1:3206] 0 0 750 0 5000 2500 0 0 3000 0 ...
## $ Debt : num [1:3206] 0 0 0 0 0 0 0 0 0 0 ...
## $ Amount : num [1:3206] 1200 400 1100 1500 600 400 950 1300 1500 450 ...
## $ Price : num [1:3206] 1468 500 1511 2189 1600 ...
## $ Status : Factor w/ 2 levels "bad","good": 1 1 1 1 1 1 1 1 1 1 ...
これにstep_mutate_at
関数で数値型への変換を追加する。
recipe(Status ~ ., data = df_train) %>%
step_string2factor(all_nominal_predictors()) %>%
step_mutate_at(Job, fn = ~ as.numeric(.)) %>%
prep() %>%
bake(new_data = df_train) %>%
select(starts_with("Job")) %>%
bind_cols(df_train %>% select(Job))
## # A tibble: 3,206 × 2
## Job...1 Job...2
## <dbl> <chr>
## 1 4 partime
## 2 4 partime
## 3 4 partime
## 4 1 fixed
## 5 1 fixed
## 6 4 partime
## 7 1 fixed
## 8 1 fixed
## 9 4 partime
## 10 1 fixed
## # … with 3,196 more rows
最後は、欠損値補完の方法をまとめおく。学習データには下記の通り、欠損値がいくつかあることがわかる。
map_int(.x = df_train, .f = function(x){sum(is.na(x))})
## Status Seniority Home Time Age Marital Records Job
## 0 0 3 0 0 1 0 1
## Expenses Income Assets Debt Amount Price
## 0 265 33 13 0 0
欠損値が埋まっているかを確認するために、欠損しているレコードをいくつかサンプリングしておく。
<- df_train %>%
df_train_imp mutate(idx = row_number()) %>%
filter(idx %in% c(350, 385, 391, 413, 497, 512, 539, 590, 1007, 1040)) %>%
select(Status, Marital, Home, Expenses, Income, Assets)
df_train_imp
## # A tibble: 10 × 6
## Status Marital Home Expenses Income Assets
## <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 bad married <NA> 45 NA NA
## 2 bad separated rent 41 58 NA
## 3 bad married priv 45 102 NA
## 4 bad married owner 150 NA 8000
## 5 bad married owner 75 60 NA
## 6 bad married owner 45 66 NA
## 7 bad married owner 35 NA NA
## 8 bad married owner 60 NA NA
## 9 good married owner 45 110 15000
## 10 good single <NA> 35 337 NA
まずは基本的な代表値補間方法である平均値補完から始める。平均値補間はstep_impute_mean
関数を利用する。step_impute_median
関数であれば中央値補間、step_impute_mode
関数であれば最頻値補間となる。
recipe(Status ~ ., data = df_train_imp) %>%
step_impute_mean(Assets) %>%
step_impute_median(Income) %>%
step_impute_mode(Home) %>%
prep() %>%
bake(new_data = df_train_imp)
## # A tibble: 10 × 6
## Marital Home Expenses Income Assets Status
## <fct> <fct> <dbl> <dbl> <dbl> <fct>
## 1 married owner 45 84 11500 bad
## 2 separated rent 41 58 11500 bad
## 3 married priv 45 102 11500 bad
## 4 married owner 150 84 8000 bad
## 5 married owner 75 60 11500 bad
## 6 married owner 45 66 11500 bad
## 7 married owner 35 84 11500 bad
## 8 married owner 60 84 11500 bad
## 9 married owner 45 110 15000 good
## 10 single owner 35 337 11500 good
予測補間を行う関数もいくつか用意されているので、ここでは線形回帰予測補間(step_impute_linear
)、決定木予測補間(step_impute_bag
)、k近傍予測補間(step_impute_knn
)の例をまとめておく。説明変数に欠損値が含まれるとエラーになるので注意。
ここではIncome
の欠損値をMarital, Expenses
を説明変数としてモデルを作成し、予測値で補間している。
recipe(Status ~ ., data = df_train_imp) %>%
step_impute_linear(Income, impute_with = imp_vars(Marital, Expenses)) %>%
prep() %>%
bake(new_data = df_train_imp)
## # A tibble: 10 × 6
## Marital Home Expenses Income Assets Status
## <fct> <fct> <dbl> <dbl> <dbl> <fct>
## 1 married <NA> 45 92.7 NA bad
## 2 separated rent 41 58 NA bad
## 3 married priv 45 102 NA bad
## 4 married owner 150 -21.7 8000 bad
## 5 married owner 75 60 NA bad
## 6 married owner 45 66 NA bad
## 7 married owner 35 104. NA bad
## 8 married owner 60 76.3 NA bad
## 9 married owner 45 110 15000 good
## 10 single <NA> 35 337 NA good
step_impute_linear
関数でIncome
の欠損値を埋めたので、step_impute_bag
関数では、Income
を説明変数として利用できる。
recipe(Status ~ ., data = df_train_imp) %>%
step_impute_linear(Income, impute_with = imp_vars(Marital, Expenses)) %>%
step_impute_bag(Assets, impute_with = imp_vars(Marital, Expenses, Income)) %>%
prep(stringsAsFactors = TRUE) %>%
bake(new_data = df_train_imp)
## # A tibble: 10 × 6
## Marital Home Expenses Income Assets Status
## <fct> <fct> <dbl> <dbl> <dbl> <fct>
## 1 married <NA> 45 92.7 11556 bad
## 2 separated rent 41 58 11556 bad
## 3 married priv 45 102 11556 bad
## 4 married owner 150 -21.7 8000 bad
## 5 married owner 75 60 11556 bad
## 6 married owner 45 66 11556 bad
## 7 married owner 35 104. 11556 bad
## 8 married owner 60 76.3 11556 bad
## 9 married owner 45 110 15000 good
## 10 single <NA> 35 337 11556 good
k近傍予測補間(step_impute_knn
)であればカテゴリ変数の欠損値も埋めることができる。
recipe(Status ~ ., data = df_train_imp) %>%
step_impute_linear(Income, impute_with = imp_vars(Marital, Expenses)) %>%
step_impute_bag(Assets, impute_with = imp_vars(Marital, Expenses, Income)) %>%
step_impute_knn(Home, impute_with = imp_vars(Expenses, Income, Assets)) %>%
prep() %>%
bake(new_data = df_train_imp)
## # A tibble: 10 × 6
## Marital Home Expenses Income Assets Status
## <fct> <fct> <dbl> <dbl> <dbl> <fct>
## 1 married owner 45 92.7 11920 bad
## 2 separated rent 41 58 11920 bad
## 3 married priv 45 102 11920 bad
## 4 married owner 150 -21.7 8000 bad
## 5 married owner 75 60 11920 bad
## 6 married owner 45 66 11920 bad
## 7 married owner 35 104. 11920 bad
## 8 married owner 60 76.3 11920 bad
## 9 married owner 45 110 15000 good
## 10 single owner 35 337 11920 good
他にもcaret
パッケージにもあったフィルター系の関数もある。相関が高い列を削除するstep_corr
関数、分散がほぼ0の列を削除するstep_nzv
関数、線形結合を除くstep_lincomb
関数などもある。
まとめきれないので、下記recipes
パッケージの公式サイトを参照のこと。