29  用recipe实现特征工程

本章介绍了如何使用recipe包来实现特征工程。特征工程是机器学习中非常重要的一步,它涉及到对原始数据进行处理和转换,以便更好地适应模型的需求。通过合理的特征工程,我们可以提高模型的性能和预测能力。关于recipe包的更多内容,请参考 章节 36 章节:嵌入法,以及 章节 34 , 章节 35,?sec-sec-missing-data。

library(tidyverse)
library(tidymodels)
tidymodels::tidymodels_prefer()

data(ames)
ames <- ames |>
  mutate(Sale_Price = log10(Sale_Price))

set.seed(502)
ames_split <- initial_split(ames, prop = 0.80, strata = Sale_Price)
ames_train <- training(ames_split)
ames_test <- testing(ames_split)

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

lm_wflow <- workflow() |>
  add_model(lm_model) |>
  add_variables(outcomes = Sale_Price, predictors = c(Longitude, Latitude))

lm_fit <- fit(lm_wflow, ames_train)

一些常用的、可能的预处理步骤包括:

29.1 建立简单recipe对象

recipe包通过step_**()系列函数定义预处理步骤,这个步骤不会立即执行,只是声明了步骤的内容。

时刻注意,要在recpie外对结果列进行转换,尽量不要在配方中对结果列进行预处理
library(tidymodels)
tidymodels::tidymodels_prefer()

simple_ames <- recipe(
  Sale_Price ~ Neighborhood + Gr_Liv_Area + Year_Built + Bldg_Type,
  data = ames_train
) |>
  step_log(Gr_Liv_Area, base = 10) |> # 对Gr_Liv_Area变量进行对数转换
  step_dummy(all_nominal_predictors()) # 将所有名义变量(因子或字符)转换为定量格式
simple_ames

使用recipe包进行预处理比直接使用公式或原始变量更有优势:

  • 相关的预处理计算可以在不同模型间重复使用,因为预处理步骤和建模函数是分开的。
  • 可以提供比公式丰富非常多的数据预处理方法。
  • 语法更加紧凑,例如以上代码中使用的all_nominal_predictors()。
  • 所有的预处理步骤都可以储存在同一个的recipe对象中。

29.2 使用recipe

将recipe对象加入workflow对象中进行使用。但要注意,同一时间只能使用同一个Preprocessor,当一个workflow中已经包含了Preprocessor,则需要先将其删除后再添加recipe。

lm_wflow <- lm_wflow |>
  remove_variables() |>
  add_recipe(simple_ames)
lm_wflow
══ Workflow ════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: linear_reg()

── Preprocessor ────────────────────────────────────────────────────────────────
2 Recipe Steps

• step_log()
• step_dummy()

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

Computational engine: lm 

接下来就可以使用一些列函数对workflow对象进行操作。

lm_fit <- fit(lm_wflow, ames_train)
predict(lm_fit, ames_test |> slice(1:3))
# A tibble: 3 × 1
  .pred
  <dbl>
1  5.08
2  5.32
3  5.28
# 获取拟合后的模型对象
lm_fit |>
  extract_fit_parsnip() |>
  tidy() |>
  slice(1:5)
# A tibble: 5 × 5
  term                       estimate std.error statistic   p.value
  <chr>                         <dbl>     <dbl>     <dbl>     <dbl>
1 (Intercept)                -0.669    0.231        -2.90 3.80e-  3
2 Gr_Liv_Area                 0.620    0.0143       43.2  2.63e-299
3 Year_Built                  0.00200  0.000117     17.1  6.16e- 62
4 Neighborhood_College_Creek  0.0178   0.00819       2.17 3.02e-  2
5 Neighborhood_Old_Town      -0.0330   0.00838      -3.93 8.66e-  5
# 获取拟合后的recipe对象
lm_fit |>
  extract_recipe(estimated = TRUE)

29.3 如何在workflow对象之外使用和调试recipe

  • recipes 包可以脱离 tidymodels 的其他组件独立使用,专门用于数据预处理。
  • 它的核心逻辑是:
  1. 定义步骤:先定义一份“配方”(recipe);
  2. 学习参数:再通过 prep() 在训练数据上学习(执行)预处理参数;
  3. 应用参数:最后用 bake() 将学到的参数应用到新数据上。
library(recipes)
library(dplyr)

# 示例数据
set.seed(123)
df <- tibble(
  y = rnorm(100),
  x1 = rnorm(100, mean = 10, sd = 2),
  x2 = rnorm(100, mean = 100, sd = 20),
  x3 = sample(c("A", "B", "C"), 100, replace = TRUE),
  x4 = rnorm(100)
) |>
  mutate(x4 = ifelse(runif(100) < 0.1, NA, x4)) # 人为制造缺失值

# 1. 定义recipe
rec <- recipe(y ~ ., data = df) |>
  step_impute_knn(all_predictors(), neighbors = 5) |> # KNN插补缺失值
  step_normalize(all_numeric_predictors()) |> # 标准化数值变量
  step_dummy(all_nominal_predictors()) # 编码分类变量
rec

# 2. 在训练数据上使用`prep()`
rec_prep <- prep(rec, training = df)
rec_prep

# 3. 查看学习到的参数
tidy(rec_prep)
# A tibble: 3 × 6
  number operation type       trained skip  id              
   <int> <chr>     <chr>      <lgl>   <lgl> <chr>           
1      1 step      impute_knn TRUE    FALSE impute_knn_rRRBu
2      2 step      normalize  TRUE    FALSE normalize_Es22X 
3      3 step      dummy      TRUE    FALSE dummy_APj0i     
# 4. 将prep后的配方应用到数据集(或新数据、测试数据)
df_processed <- bake(rec_prep, new_data = df)

# 5. 查看处理后的数据
glimpse(df_processed)
Rows: 100
Columns: 6
$ x1   <dbl> -0.62344169, 0.37687234, -0.14389556, -0.24818937, -0.87288879, 0…
$ x2   <dbl> 2.188010597, 1.254841839, -0.405957160, 0.445034541, -0.563024425…
$ x4   <dbl> 1.50201503, -0.40962976, 2.35363219, -2.96308860, -0.97263894, 0.…
$ y    <dbl> -0.56047565, -0.23017749, 1.55870831, 0.07050839, 0.12928774, 1.7…
$ x3_B <dbl> 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0,…
$ x3_C <dbl> 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,…

  1. 例如方差分析,或使用方差作为特征重要性的模型。↩︎