如何将多值列独热编码逻辑转为tidymodels的recipe步骤?
在tidymodels中实现多值列的拆分与独热编码
问题场景
已有如下R数据框:
library(tidyverse) # 示例数据框 df <- data.frame( id = 1:3, fruits = c("apple | oranges", "apple | bananas", "bananas | oranges") )
对应表格:
| id | fruits |
|---|---|
| 1 | apple |
| 2 | apple |
| 3 | bananas |
已经通过tidyverse代码实现fruits列的拆分与独热编码:
# 步骤1:按|拆分多行 df_separated <- df %>% separate_rows(fruits, sep = " \\| ") # 步骤2:生成哑变量 df_dummy <- df_separated %>% mutate(value = TRUE) %>% spread(fruits, value, fill = FALSE)
得到结果:
| id | apple | bananas | oranges |
|---|---|---|---|
| 1 | TRUE | FALSE | TRUE |
| 2 | TRUE | TRUE | FALSE |
| 3 | FALSE | TRUE | TRUE |
需要将该逻辑整合进tidymodels工作流,作为recipe步骤。
解决方案
方案1:借助textrecipes包快速实现
如果安装了textrecipes包,可以用现成的步骤处理多值列:
library(tidymodels) library(textrecipes) # 创建并预处理recipe fruit_recipe <- recipe(~ id + fruits, data = df) %>% # 把字符串按" | "拆成列表列 step_mutate(fruits_list = str_split(fruits, " \\| ")) %>% # 对列表列做多值哑变量编码 step_dummy_multi(fruits_list) %>% # 移除原列和中间列表列(可选) step_rm(fruits, fruits_list) %>% prep() # 生成结果 bake(fruit_recipe, new_data = NULL)
方案2:自定义recipe步骤(无额外依赖)
如果不想依赖第三方包,可以自定义步骤完全复刻原tidyverse逻辑:
library(tidymodels) # 1. 定义自定义步骤的框架 step_multi_dummy <- function(recipe, ..., role = "predictor", trained = FALSE, columns = NULL, sep = " \\| ", skip = FALSE, id = rand_id("multi_dummy")) { add_step( recipe, step_multi_dummy_new( terms = enquos(...), role = role, trained = trained, columns = columns, sep = sep, skip = skip, id = id ) ) } step_multi_dummy_new <- function(terms, role, trained, columns, sep, skip, id) { step( subclass = "multi_dummy", terms = terms, role = role, trained = trained, columns = columns, sep = sep, skip = skip, id = id ) } # 2. 实现训练阶段逻辑(提取所有类别) prep.step_multi_dummy <- function(x, training, info = NULL, ...) { col_names <- recipes::terms_select(x$terms, info = info) # 从训练数据中提取所有可能的类别 all_categories <- training %>% separate_rows(all_of(col_names), sep = x$sep) %>% pull(all_of(col_names)) %>% unique() %>% sort() step_multi_dummy_new( terms = x$terms, role = x$role, trained = TRUE, columns = list(col = col_names, categories = all_categories, sep = x$sep), sep = x$sep, skip = x$skip, id = x$id ) } # 3. 实现应用阶段逻辑(生成哑变量) bake.step_multi_dummy <- function(object, new_data, ...) { col_name <- object$columns$col categories <- object$columns$categories sep <- object$columns$sep new_data %>% separate_rows(all_of(col_name), sep = sep) %>% mutate(value = TRUE) %>% spread(all_of(col_name), value, fill = FALSE) %>% group_by(id) %>% # 这里假设id是分组主键,根据实际数据调整 summarise(across(all_of(categories), ~any(.x)), .groups = "drop") %>% right_join(new_data %>% select(-all_of(col_name)), by = "id") %>% select(id, all_of(categories), everything()) } # 4. 使用自定义步骤创建recipe fruit_recipe <- recipe(~ id + fruits, data = df) %>% step_multi_dummy(fruits, sep = " \\| ") %>% prep() # 生成结果 bake(fruit_recipe, new_data = NULL)
验证结果
两种方案都能输出和原tidyverse代码一致的结果:
| id | apple | bananas | oranges |
|---|---|---|---|
| 1 | TRUE | FALSE | TRUE |
| 2 | TRUE | TRUE | FALSE |
| 3 | FALSE | TRUE | TRUE |
内容的提问来源于stack exchange,提问作者GM_3
相关产品推荐
相关产品推荐

