6  用workflow管理建模流程

一个模型建立的过程,不仅仅是指定模型和估计参数的问题,还包括自变量选择、数据预处理、变量变换、判别阈值选择等问题,这一整套流程在tidymodels中被称为workflow,它可以帮助我们更好地管理建模流程。workflow将基于训练产生最优模型的过程完整的封装起来并管理,有利于管理研究项目。

6.1 workflow基础

workflow用来管理建模估计、建模前预处理、建模后的调整等流程,所以workflow至少需要指定一个模型

以简单线性回归建模为例:

library(tidymodels)
tidymodels_prefer()

lm_model <- linear_reg() %>%
  set_engine("lm")

# 创建初始workflow
lm_wflow <- workflow() %>%
  add_model(lm_model)
lm_wflow
══ Workflow ════════════════════════════════════════════════════════════════════
Preprocessor: None
Model: linear_reg()

── Model ───────────────────────────────────────────────────────────────────────
Linear Regression Model Specification (regression)

Computational engine: lm 

从输出结果可以看出,一个workflow对象包含了两个部分:

  1. Preprocessor:预处理步骤,包括变量选择、数据预处理、变量变换等。本例中没有指定预处理的步骤,所以Preprocessor显示”None”。
  2. Model:模型,这里是一个简单的线性回归模型。

最简单的Preprocessor是指定一个回归公式:

lm_wflow <- workflow() %>%
  add_model(lm_model) %>%
  add_formula(
    Sale_Price ~ Longitude + Latitude
  )
lm_wflow
══ Workflow ════════════════════════════════════════════════════════════════════
Preprocessor: Formula
Model: linear_reg()

── Preprocessor ────────────────────────────────────────────────────────────────
Sale_Price ~ Longitude + Latitude

── Model ───────────────────────────────────────────────────────────────────────
Linear Regression Model Specification (regression)

Computational engine: lm 

在建立一个workflow后,就可以用fit()函数来拟合模型:

值得注意的是,一个workflow拟合后的对象仍是一个workflow(不同的是新workflow包含了拟合的结果,同时会在输出结果中显示[trained]。),这也使得新的workflow可以当做拟合结果来使用。
lm_fit <- fit(lm_wflow, ames_train)
lm_fit
══ Workflow [trained] ══════════════════════════════════════════════════════════
Preprocessor: Formula
Model: linear_reg()

── Preprocessor ────────────────────────────────────────────────────────────────
Sale_Price ~ Longitude + Latitude

── Model ───────────────────────────────────────────────────────────────────────

Call:
stats::lm(formula = ..y ~ ., data = data)

Coefficients:
(Intercept)    Longitude     Latitude  
   -301.337       -1.986        2.869  
# 用broom::tidy对结果进行整洁化处理
broom::tidy(lm_fit)
# A tibble: 3 × 5
  term        estimate std.error statistic  p.value
  <chr>          <dbl>     <dbl>     <dbl>    <dbl>
1 (Intercept)  -301.      14.3       -21.1 1.05e-90
2 Longitude      -1.99     0.130     -15.3 1.51e-50
3 Latitude        2.87     0.177      16.2 2.57e-56
# 用predict()函数进行预测
predict(
  lm_fit,
  ames_test %>%
    slice(1:5)
)
# A tibble: 5 × 1
  .pred
  <dbl>
1  5.29
2  5.29
3  5.24
4  5.24
5  5.23

workflow对象(包括已经拟合过的),其中的模型和预处理内容都可以删除或更改:

注意修改后的对象不包含拟合结果, 需要重新拟合。
lm_fit %>%
  update_formula(
    Sale_Price ~ Longitude + Latitude + Neighborhood
  )
══ Workflow ════════════════════════════════════════════════════════════════════
Preprocessor: Formula
Model: linear_reg()

── Preprocessor ────────────────────────────────────────────────────────────────
Sale_Price ~ Longitude + Latitude + Neighborhood

── Model ───────────────────────────────────────────────────────────────────────
Linear Regression Model Specification (regression)

Computational engine: lm 

6.2 指定变量

除了使用add_formula()函数指定回归公式外,还可以用add_variables()函数指定需要使用的变量:

如果需要指定多个变量,还可以与select()函数连用
lm_wflow <- workflow() %>%
  add_model(lm_model) %>%
  add_variables(
    outcome = Sale_Price,
    predictors = c("Longitude", "Latitude", "Neighborhood")
  )
Note
  1. 如果指定的自变量中还包含了因变量,那因变量会自动忽略,在这种情况下我们可以使用predictors = everything()参数来指定自变量。
  2. add_variables()函数在与select()系列函数连用时,可以灵活的指定变量,但不能对变量进行处理,仅会将选中的变量保存数据框中传给engine.如果我们需要对变量进行处理,我们就需要使用add_formular()函数(详见 Section 6.3),或借助recipe包(详见 Chapter 7 也是tidymodels的系列包).

6.3 公式在workflow中的作用

workflow中使用add_formula()函数添加公式,可以用来指定因变量和自变量集合,并在engine需要时进行适当的变换(如将取属性值的自变量转换为哑变量 1 ,产生交叉项等).

workflow中,对公式的解释依赖于具体的模型和engine,所以workflow会努力模拟模型所需要的数据变化,但如果不能实现模型所需的数据变化,那workflow不会对公式做出改变。

6.3.1 公式内的特殊语法

有些特殊的模型会使用特殊的公式语法,这些语法中相同的符号可能代表不同的含义,在实际使用过程中会产生不必要的误解.workflow解决这个问题的办法是在add_model()函数中同时指定模型及其对应的公式,而用到的变量则使用add_variables()函数指定。

library(multilevelmod)
data(Orthodont, package = "nlme")

# 建立模型
multilevel_spec <- linear_reg() %>%
  set_engine("lmer")
# 建立workflow
multilevel_wflow <- workflow() %>%
  add_variables(
    outcome = distance,
    predictors = c(Sex, age, Subject)
  ) %>%
  add_model(
    multilevel_spec,
    formula = distance ~ Sex + (age | Subject)
  )
# 拟合模型
multilevel_fit <- fit(multilevel_wflow, Orthodont)
multilevel_fit
══ Workflow [trained] ══════════════════════════════════════════════════════════
Preprocessor: Variables
Model: linear_reg()

── Preprocessor ────────────────────────────────────────────────────────────────
Outcomes: distance
Predictors: c(Sex, age, Subject)

── Model ───────────────────────────────────────────────────────────────────────
Linear mixed model fit by REML ['lmerMod']
Formula: distance ~ Sex + (age | Subject)
   Data: data
REML criterion at convergence: 471.1635
Random effects:
 Groups   Name        Std.Dev. Corr 
 Subject  (Intercept) 7.3912        
          age         0.6943   -0.97
 Residual             1.3100        
Number of obs: 108, groups:  Subject, 27
Fixed Effects:
(Intercept)    SexFemale  
     24.517       -2.145  

6.3.2 批量建立workflow

统计建模的过程,一般都需要比较多个模型,有时需要比较不同的变量子集。workflowsets包可以指定预处理器列表和模型列表,然后生成一批工作流程,最后设法比较拟合结果。

ames数据为例,我们需要考虑待售房屋方位的不同方式对房价的影响:

# 建立房屋方位对房价影响的列表
location <- list(
  longitude = Sale_Price ~ Longitude,
  latitude = Sale_Price ~ Latitude,
  coords = Sale_Price ~ Longitude + Latitude,
  neighborhood = Sale_Price ~ Neighborhood
)
# 建立workflowset-使用之前建立的线性模型进行演示
location_models <- workflow_set(
  preproc = location,
  models = list(lm_model)
)
location_models # 数据框,每一行都是一个workflow
# A workflow set/tibble: 4 × 4
  wflow_id                info             option    result    
  <chr>                   <list>           <list>    <list>    
1 longitude_linear_reg    <tibble [1 × 4]> <opts[0]> <list [0]>
2 latitude_linear_reg     <tibble [1 × 4]> <opts[0]> <list [0]>
3 coords_linear_reg       <tibble [1 × 4]> <opts[0]> <list [0]>
4 neighborhood_linear_reg <tibble [1 × 4]> <opts[0]> <list [0]>
# 提取某一行的workflow
extract_workflow(
  location_models,
  id = "coords_linear_reg"
)
══ Workflow ════════════════════════════════════════════════════════════════════
Preprocessor: Formula
Model: linear_reg()

── Preprocessor ────────────────────────────────────────────────────────────────
Sale_Price ~ Longitude + Latitude

── Model ───────────────────────────────────────────────────────────────────────
Linear Regression Model Specification (regression)

Computational engine: lm 

为比较workflowset中的不同模型拟合结果,通常使用交叉验证等重抽样的方法,重抽样的设置于比较结果会保存在workflowsetresults属性中。

重抽样的方法在 Chapter 9?sec-compare-workflowsets 中有详细介绍。这里我们先不进行重抽样,重点关注workflowset的使用方法。

location_models <- location_models %>%
  mutate(fit = map(info, \(x) fit(x$workflow[[1]], ames_train)))
location_models
# A workflow set/tibble: 4 × 5
  wflow_id                info             option    result     fit       
  <chr>                   <list>           <list>    <list>     <list>    
1 longitude_linear_reg    <tibble [1 × 4]> <opts[0]> <list [0]> <workflow>
2 latitude_linear_reg     <tibble [1 × 4]> <opts[0]> <list [0]> <workflow>
3 coords_linear_reg       <tibble [1 × 4]> <opts[0]> <list [0]> <workflow>
4 neighborhood_linear_reg <tibble [1 × 4]> <opts[0]> <list [0]> <workflow>
location_models$fit[[1]] # 第一个模型的拟合结果
══ Workflow [trained] ══════════════════════════════════════════════════════════
Preprocessor: Formula
Model: linear_reg()

── Preprocessor ────────────────────────────────────────────────────────────────
Sale_Price ~ Longitude

── Model ───────────────────────────────────────────────────────────────────────

Call:
stats::lm(formula = ..y ~ ., data = data)

Coefficients:
(Intercept)    Longitude  
   -179.412       -1.972  
  • 上述代码中的map()函数的实际效果可理解为location_models$info[[1]]$workflow[[1]]:从location_models中提取info(即 location_models$info[[1]]...location_models$info[[4]],再取出workflow。
  • 虽然使用mutate()map()批量拟合模型很方便,但是后面会给出更有用的批量拟合、比较模型的办法(?sec-compare-workflowsets).

6.3.3 使用测试集对模型进行评估

在一般的统计学习任务中,测试集不应该用来比较不同模型,而是在选择了最优模型后,用来给出其预测性能的评判指标。比较不同模型应该仅使用训练集。

现在假设已经有了最优模型,比如是工作流程lm_wflow的结果,则可以使用last_fit()在测试集上进行性能测试.last_fit()函数可以将模型拟合到训练集上,并使用测试集对模型进行评估.有关last_fit的详细信息,请参见 ?sec-appendix-A-modeling.

lm_model <- linear_reg() %>%
  set_engine("lm")
lm_wflow <- workflow() %>%
  add_model(lm_model) %>%
  add_formula(
    Sale_Price ~ Longitude + Latitude
  )
final_lm_res <- last_fit(lm_wflow, ames_split)
final_lm_res
# Resampling results
# Manual resampling 
# A tibble: 1 × 6
  splits             id               .metrics .notes   .predictions .workflow 
  <list>             <chr>            <list>   <list>   <list>       <list>    
1 <split [2344/586]> train/test split <tibble> <tibble> <tibble>     <workflow>
# 提取所使用的workflow
extract_workflow(final_lm_res)
══ Workflow [trained] ══════════════════════════════════════════════════════════
Preprocessor: Formula
Model: linear_reg()

── Preprocessor ────────────────────────────────────────────────────────────────
Sale_Price ~ Longitude + Latitude

── Model ───────────────────────────────────────────────────────────────────────

Call:
stats::lm(formula = ..y ~ ., data = data)

Coefficients:
(Intercept)    Longitude     Latitude  
   -301.337       -1.986        2.869  
# 提取评估模型性能的指标
collect_metrics(final_lm_res)
# A tibble: 2 × 4
  .metric .estimator .estimate .config        
  <chr>   <chr>          <dbl> <chr>          
1 rmse    standard       0.167 pre0_mod0_post0
2 rsq     standard       0.165 pre0_mod0_post0
# 提取再测试集上的预测结果
collect_predictions(final_lm_res)
# A tibble: 586 × 5
   .pred id               Sale_Price  .row .config        
   <dbl> <chr>                 <dbl> <int> <chr>          
 1  5.29 train/test split       5.28     5 pre0_mod0_post0
 2  5.29 train/test split       5.29     6 pre0_mod0_post0
 3  5.24 train/test split       5.26    29 pre0_mod0_post0
 4  5.24 train/test split       4.94    32 pre0_mod0_post0
 5  5.23 train/test split       5.11    33 pre0_mod0_post0
 6  5.32 train/test split       5.44    42 pre0_mod0_post0
 7  5.30 train/test split       5.27    55 pre0_mod0_post0
 8  5.29 train/test split       5.42    62 pre0_mod0_post0
 9  5.29 train/test split       5.51    63 pre0_mod0_post0
10  5.28 train/test split       5.42    70 pre0_mod0_post0
# ℹ 576 more rows

6.4 总结

workflowtidymodels中用于管理建模流程的重要工具。它可以帮助我们更好地管理建模流程,并使得模型的拟合、预测、评估等过程更加方便。

  1. workflow对象包含预处理器和模型,可以用add_model()函数添加模型,用add_formula()函数添加公式,用add_variables()函数添加变量。
  2. workflow对象可以用fit()函数拟合模型,用last_fit()函数在测试集上进行性能测试。
  3. workflowset可以用来批量建立workflow,用map()函数批量拟合模型,用collect_metrics()函数收集模型性能指标,用collect_predictions()函数收集模型预测结果。

  1. 将取属性值的自变量转换为可量化的哑变量,使得模型中不包含具体的数值,而只包含变量的属性,如将性别变量转换为男性=1,女性=0。这样做的好处是可以简化模型,使得模型更易于理解和解释。具体可参见哑变量详解↩︎