こんにちは,shun(@datasciencemore)です!!
今回はtidymodelsの{rsample}についてやっていきます.
目次
0.{rsample}ってなに??
{rsample}は,データ分割に関するパッケージだよ!!
{rsample}を使えばデータを様々な形に分割することができるんだ!!
交差検証について覚えているでしょうか?
交差検証をするとき,データを分割する必要がありました.
その分割の方法に色々なパターンがありましたね.
{rsample}を使用すると色々なパターンに分割することができるのです!!
{rsample}の主な関数がこちらです.
これらの関数を使用して,データを様々な形へ分割してみましょう!
0.データ
これからの講座で使用するデータは,ローン情報に関するデータを使用していきます.
ローンを組む,要するにお金を借りるときは,色々な情報を金融機関に提示する必要があります.
色々な情報というのは,例えば,自分の職業,勤めている会社,収入などが該当します
このデータはそういう情報であると考えていただければOKです.
No1のStatusにローンの審査結果が,No2~No14に審査結果に影響する項目が記載されています.
tidymodels編では,基本的にNo1のStatusを目的変数に,No2~No14を説明変数として,このデータを使用していきます.
なので,分類問題ということになります.
このデータは次のようにすれば使用することができます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# パッケージ読み込み library(tidymodels) library(withr) # データ定義:クレジットデータ data("credit_data") credit_data = tibble(credit_data) # 既知データ known = credit_data %>% initial_split(prop = 0.8, strata = "Status") %>% with_seed(1234, .) %>% training() # 未知データ unknown = credit_data %>% initial_split(prop = 0.8, strata = "Status") %>% with_seed(1234, .) %>% testing() |
既知データというのは,目的変数の値がわかるデータ,未知データというのは目的変数の値がわからないデータのことです.
予測モデリングでやりたいことは既知データからデータの傾向を学習し,未知データの目的変数を予測することです.
なお,今回は練習なので未知データの目的変数の値はわかっていますが,実際はわからないものとして考えてみてください.
1.様々な分割
1.0.初期分割
既知データを学習データと評価データに分割します.
1 2 |
# 0.初期分割 initial_split = initial_split(known, prop = 0.80, strata = "Status") |
ここで,propは,学習データの割合,strataは,指定した列の比率を同じにすることを示しています.
この例の場合,学習データの割合は全体の80%,「Status」列の割合が学習データと評価データで同じになるように分割するということを示しています.
定義したinitial_splitを実行すると次のように出力されます.
1 2 |
<Analysis/Assess/Total> <3563/891/4454> |
これは,学習データ3563件,評価データ891件,合わせて4454件ということです.
ここから,学習データ,評価データを抽出するには,training関数,testing関数を使用すればOKです.
1 2 |
train = training(initial_split) test = testing(initial_split) |
以下,交差検証に使用する分割を紹介しますが,分割元のデータは上で定義したtrainを使用していきます.
1.1.K分割
K分割はこんな感じに分割することでした.
これを{rsample}でコーディングするとこのようになります.
1 2 3 4 |
# 1.K分割 kfold_splits = vfold_cv(train, v = 4) %>% with_seed(1234, .) |
vで分割数を設定できます.
この場合だと4つに分割しているってことです.
ちなみにwith_seed(1234, .)は,seed:1234の乱数を使用するということです.
K分割は,乱数を使用し分割方法を決定しています.
なので,分割が実行するたびに異なるのです.
分割を固定するにはseedを固定してあげる必要があります.
with_seedという関数を使用することでseedを固定することができるので,常に同じ分割のされ方で分割されるのです!
定義したkfold_splitsを実行すると,出力はこのようになります.
1 2 3 4 5 6 7 8 |
# 4-fold cross-validation # A tibble: 4 x 2 splits id <list> <chr> 1 <split [2672/891]> Fold1 2 <split [2672/891]> Fold2 3 <split [2672/891]> Fold3 4 <split [2673/890]> Fold4 |
こんな感じで4行にそれぞれのFoldが表示されます.
上の図との対応を確認してください.
このように分割した後にanalysis,assessmentを抽出したい場合は,それぞれのsplitにanalysis関数,assessment関数を適用することで実現できます.
1 2 3 4 |
# splits列1行目のanalysis kfold_splits %>% pluck("splits", 1) %>% analysis() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# A tibble: 2,672 x 14 Status Seniority Home Time Age Marital Records Job <fct> <int> <fct> <int> <int> <fct> <fct> <fct> 1 bad 0 other 18 21 single yes parti… 2 bad 0 ignore 48 36 married no parti… 3 bad 1 owner 60 45 married no parti… 4 bad 0 NA 48 37 single no NA 5 bad 2 owner 60 43 married no parti… 6 bad 2 rent 36 27 separa… no fixed 7 bad 1 rent 48 29 married yes freel… 8 bad 4 paren… 42 27 single no fixed 9 bad 1 owner 36 29 married no fixed 10 bad 0 rent 60 25 single no parti… # … with 2,662 more rows, and 6 more variables: # Expenses <int>, Income <int>, Assets <int>, Debt <int>, # Amount <int>, Price <int> |
1 2 3 4 |
# splits列1行目のassessment kfold_splits %>% pluck("splits", 1) %>% assessment() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# A tibble: 891 x 14 Status Seniority Home Time Age Marital Records Job <fct> <int> <fct> <int> <int> <fct> <fct> <fct> 1 bad 2 rent 60 25 single no fixed 2 bad 5 rent 48 31 single no fixed 3 bad 0 owner 60 32 married yes fixed 4 bad 2 owner 36 32 married no fixed 5 bad 0 paren… 60 23 single no parti… 6 bad 0 other 36 48 married yes freel… 7 bad 2 owner 36 33 married no freel… 8 bad 13 rent 36 28 married no fixed 9 bad 27 rent 60 50 married yes fixed 10 bad 3 other 60 25 single no fixed # … with 881 more rows, and 6 more variables: # Expenses <int>, Income <int>, Assets <int>, Debt <int>, # Amount <int>, Price <int> |
これを利用して各行のsplitに対して,analysisとassessmentを求めたい場合は,rowwiseとmutateを使用してこうするのがいいでしょう.
1 2 3 4 5 6 7 |
# 各行のsplitに対するanalysisとassessment kfold_splits %>% rowwise() %>% mutate( .analysis = list(splits %>% analysis()), .assessment = list(splits %>% assessment()) ) |
1 2 3 4 5 6 7 8 |
# A tibble: 4 x 4 # Rowwise: splits id .analysis .assessment <list> <chr> <list> <list> 1 <split [2672/891]> Fold1 <tibble [2,672 × 14]> <tibble [891 × 14]> 2 <split [2672/891]> Fold2 <tibble [2,672 × 14]> <tibble [891 × 14]> 3 <split [2672/891]> Fold3 <tibble [2,672 × 14]> <tibble [891 × 14]> 4 <split [2673/890]> Fold4 <tibble [2,673 × 14]> <tibble [890 × 14]> |
ちなみに.analysis,.assessmentと列名の頭に「.」をつけたのは,グローバル変数との競合を避けるためです.
別にどのような列名でもいいのですが,自分の意図しない変数を使用したくないので列名の頭に「.」を付けました.
単なるテクニックですのであまり気にしなくてOKです笑
1.2.hold-out分割
hold-out分割は,学習データを単純に2分割にし,analysisとassessmentに分ける方法です.
コーディングはこんな感じです.
1 2 3 4 |
# 2.hold-out分割 hold_out_split = validation_split(train, prop = 0.7) %>% with_seed(1234, .) |
1 2 3 4 5 |
# Validation Set Split (0.7/0.3) # A tibble: 1 x 2 splits id <list> <chr> 1 <split [2494/1069]> validation |
1.3.leave-one-out分割
leave-one-out分割は,K分割交差検証のKを学習データ数にする方法です.
コーディングはこうです.
1 2 3 |
# 3.leave-one-out分割 leave_one_out_splits = loo_cv(train) #seedは固定する必要ない |
leave-one-out分割は,上述のK分割やhold-out分割と違い,分割方法が一意に定まるので,seedを固定する必要はありません.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Leave-one-out cross-validation # A tibble: 3,563 x 2 splits id <list> <chr> 1 <split [3562/1]> Resample1 2 <split [3562/1]> Resample2 3 <split [3562/1]> Resample3 4 <split [3562/1]> Resample4 5 <split [3562/1]> Resample5 6 <split [3562/1]> Resample6 7 <split [3562/1]> Resample7 8 <split [3562/1]> Resample8 9 <split [3562/1]> Resample9 10 <split [3562/1]> Resample10 # … with 3,553 more rows |
1.4.stratified分割
分類タスクの場合に,foldごとに含まれるカテゴリの割合を等しくしてデータを分割することをstratified分割(日本語だと層化抽出)と呼びのでした.
コーディングは,K分割の場合とほとんど一緒です.
引数strataに割合を等しくするカテゴリを設定すればOKです.
1 2 3 4 |
# 4.stratified分割 stratified_splits = vfold_cv(train, v = 4, strata = "Status") %>% with_seed(1234, .) |
1 2 3 4 5 6 7 8 |
# 4-fold cross-validation using stratification # A tibble: 4 x 2 splits id <list> <chr> 1 <split [2672/891]> Fold1 2 <split [2672/891]> Fold2 3 <split [2672/891]> Fold3 4 <split [2673/890]> Fold4 |
上のようにするとStatus列のカテゴリ(goodとbad)の割合が分割しても等しくなります.
ほんとにそうなっているか,実際に確認してみましょう.
trainのStatusの割合を確認します.
1 2 3 4 5 6 |
# trainのStatusの割合確認 train %>% count(Status) %>% mutate( prop = n / sum(n) ) |
1 2 3 4 5 |
# A tibble: 2 x 3 Status n prop <fct> <int> <dbl> 1 bad 1003 0.282 2 good 2560 0.718 |
badが0.282,goodが0.718の割合であることがわかりました.
それでは,stratified分割した場合,各foldのStatusの割合を見てみましょう.
簡単に考えるため,analysisとassessmentについてgoodの割合を見てみましょう.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# stratified分割による各foldのStatusのgoodの割合 stratified_splits %>% rowwise() %>% mutate( .analysis = list(splits %>% analysis()), .assessment = list(splits %>% assessment()), .analysis_prop_good = .analysis %>% count(Status) %>% mutate( prop = n / sum(n) ) %>% pull(prop) %>% last(), .assessment_prop_good = .assessment %>% count(Status) %>% mutate( prop = n / sum(n) ) %>% pull(prop) %>% last() ) |
1 2 3 4 5 6 7 8 |
# A tibble: 4 x 6 # Rowwise: splits id .analysis .assessment .analysis_prop_good .assessment_prop_good <list> <chr> <list> <list> <dbl> <dbl> 1 <split [2672/891]> Fold1 <tibble [2,672 × 14]> <tibble [891 × 14]> 0.719 0.718 2 <split [2672/891]> Fold2 <tibble [2,672 × 14]> <tibble [891 × 14]> 0.719 0.718 3 <split [2672/891]> Fold3 <tibble [2,672 × 14]> <tibble [891 × 14]> 0.719 0.718 4 <split [2673/890]> Fold4 <tibble [2,673 × 14]> <tibble [890 × 14]> 0.718 0.719 |
はい,こんな感じでどのfoldでもanalysisとassessmentのgoodの割合が約0.718ということが確認できました!
ちなみに引数strataを使用しない(いわゆる普通のK分割の場合)と,分割前と分割後で割合がずれます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# K分割による各foldのStatusのgoodの割合 kfold_splits %>% rowwise() %>% mutate( .analysis = list(splits %>% analysis()), .assessment = list(splits %>% assessment()), .analysis_prop_good = .analysis %>% count(Status) %>% mutate( prop = n / sum(n) ) %>% pull(prop) %>% last(), .assessment_prop_good = .assessment %>% count(Status) %>% mutate( prop = n / sum(n) ) %>% pull(prop) %>% last() ) |
1 2 3 4 5 6 7 8 |
# A tibble: 4 x 6 # Rowwise: splits id .analysis .assessment .analysis_prop_good .assessment_prop_good <list> <chr> <list> <list> <dbl> <dbl> 1 <split [2672/891]> Fold1 <tibble [2,672 × 14]> <tibble [891 × 14]> 0.713 0.735 2 <split [2672/891]> Fold2 <tibble [2,672 × 14]> <tibble [891 × 14]> 0.723 0.706 3 <split [2672/891]> Fold3 <tibble [2,672 × 14]> <tibble [891 × 14]> 0.722 0.709 4 <split [2673/890]> Fold4 <tibble [2,673 × 14]> <tibble [890 × 14]> 0.717 0.724 |
こんな感じでtrain, analysis, assessmentと一緒ではないことが確認できます.
分類タスクの場合は,stratified分割にしたほうがいいでしょう.
1.5.時系列分割
時系列分割はこんな感じに分割することでした.
左のように分割したい場合は,引数cumulativeをFALSEにします.
1 2 3 4 5 6 7 8 9 |
# 5.時系列分割 左 timeseries_splits = rolling_origin( train %>% rowid_to_column(), initial = 100, assess = 20, skip = 0, lag = 0, cumulative = FALSE) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Rolling origin forecast resampling # A tibble: 3,444 x 2 splits id <list> <chr> 1 <split [100/20]> Slice0001 2 <split [100/20]> Slice0002 3 <split [100/20]> Slice0003 4 <split [100/20]> Slice0004 5 <split [100/20]> Slice0005 6 <split [100/20]> Slice0006 7 <split [100/20]> Slice0007 8 <split [100/20]> Slice0008 9 <split [100/20]> Slice0009 10 <split [100/20]> Slice0010 # … with 3,434 more rows |
右のように分割したい場合は,引数cumulativeをTRUEにします.
1 2 3 4 5 6 7 8 |
# 5.時系列分割 右 timeseries_cumulative_splits = rolling_origin( train, initial = 100, assess = 20, skip = 10, cumulative = TRUE) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Rolling origin forecast resampling # A tibble: 314 x 2 splits id <list> <chr> 1 <split [100/20]> Slice001 2 <split [111/20]> Slice002 3 <split [122/20]> Slice003 4 <split [133/20]> Slice004 5 <split [144/20]> Slice005 6 <split [155/20]> Slice006 7 <split [166/20]> Slice007 8 <split [177/20]> Slice008 9 <split [188/20]> Slice009 10 <split [199/20]> Slice010 # … with 304 more rows |
どちらの場合でも引数skip,引数lagの数値を調整すると微調整ができるので,いろいろな数値を入れてみてどのような挙動するのか試してみましょう.
まとめ
今回は{rsample}についてやってきました!
{rsample}を使用すると簡単にデータを様々な形へ分割できます.
今回学習した分割方法は以下のとおりです.
0.初期分割
1.K分割
2.hold-out分割
3.leave-one-out分割
4.stratified分割
5.時系列分割
もちろん,これ以外にも分割方法はありますが,まずはこれらの主要な分割をマスターしましょう.
大事なことは,目的に合わせて適切にデータを分割することです.
データ分割の基本原則である
①学習データと評価データの関係と各foldのanalysis,assessmentの関係を類似させる.
②各foldのanalysisを網羅的にする.
を意識し,適切なデータ分割を心がけましょう!
それでは,お疲れ様でした!!