You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何tune_grid识别字符型变量而非因子型变量?

问题分析与解决

问题背景

我有包含name、id、gender字符列的test_data,已将它们全部转换为因子型变量,同时标记name和id为「informational」(不用于建模)。但运行tune_grid时,仍报错这三个变量是字符型而非因子型,尽管其中2个应被忽略且3个均为因子型。

需求:保留所有列用于调试,不直接删除;分离数据处理(用recipes)与模型训练,因为有多种预处理配方但要重复构建同类模型。

错误信息

→ A | error:   ✖ The following variables have the wrong class:
               • `name` must have class <factor>, not <character>.
               • `id` must have class <factor>, not <character>.
               • `gender` must have class <factor>, not <character>.

调试信息

> final_model <- train_lasso_model(recipe_obj, processed_data)
vfold [2 × 2] (S3: vfold_cv/rset/tbl_df/tbl/data.frame)
 $ splits:List of 2
  ..$ :List of 4
  .. ..$ data  : tibble [3 × 6] (S3: tbl_df/tbl/data.frame)
  .. .. ..$ name          : Factor w/ 2 levels "sam","unknown": 1 1 1
  .. .. ..$ id            : Factor w/ 4 levels "1","2","3","unknown": 1 2 3
  .. .. ..$ gender        : Factor w/ 3 levels "female","male",..: 2 1 1
  .. .. ..$ target        : num [1:3] 4 5 6
  .. .. ..$ gender_male   : num [1:3] 1 0 0
  .. .. ..$ gender_unknown: num [1:3] 0 0 0
  .. ..$ in_id : int 3
  .. ..$ out_id: logi NA
  .. ..$ id    : tibble [1 × 1] (S3: tbl_df/tbl/data.frame)
  .. .. ..$ id: chr "Fold1"
  .. ..- attr(*, "class")= chr [1:2] "vfold_split" "rsplit"
  ..$ :List of 4
  .. ..$ data  : tibble [3 × 6] (S3: tbl_df/tbl/data.frame)
  .. .. ..$ name          : Factor w/ 2 levels "sam","unknown": 1 1 1
  .. .. ..$ id            : Factor w/ 4 levels "1","2","3","unknown": 1 2 3
  .. .. ..$ gender        : Factor w/ 3 levels "female","male",..: 2 1 1
  .. .. ..$ target        : num [1:3] 4 5 6
  .. .. ..$ gender_male   : num [1:3] 1 0 0
  .. .. ..$ gender_unknown: num [1:3] 0 0 0
  .. ..$ in_id : int [1:2] 1 2
  .. ..$ out_id: logi NA
  .. ..$ id    : tibble [1 × 1] (S3: tbl_df/tbl/data.frame)
  .. .. ..$ id: chr "Fold2"
  .. ..- attr(*, "class")= chr [1:2] "vfold_split" "rsplit"
 $ id    : chr [1:2] "Fold1" "Fold2"
 - attr(*, "v")= num 2
 - attr(*, "repeats")= num 1
 - attr(*, "breaks")= num 4
 - attr(*, "pool")= num 0.1
 - attr(*, "fingerprint")= chr "2c80c86a0361fcf4a6d480eb1b0b8d79"
before tune_grid
→ A | error:   ✖ The following variables have the wrong class:
               • `name` must have class <factor>, not <character>.
               • `id` must have class <factor>, not <character>.
               • `gender` must have class <factor>, not <character>.
There were issues with some computations   A: x2
after tune_grid
Error in `estimate_tune_results()`:
! All models failed. Run `show_notes(.Last.tune.result)` for more information.
Run `rlang::last_trace()` to see where the error occurred.
Warning message:
All models failed. Run `show_notes(.Last.tune.result)` for more information. 
> rlang::last_trace()
<error/rlang_error>
Error in `estimate_tune_results()`:
! All models failed. Run `show_notes(.Last.tune.result)` for more information.
---
Backtrace:
    ▆
 1. ├─global train_lasso_model(recipe_obj, processed_data)
 2. │ └─tune_results %>% select_best(metric = "roc_auc")
 3. ├─tune::select_best(., metric = "roc_auc")
 4. └─tune:::select_best.tune_results(., metric = "roc_auc")
 5.   ├─tune::show_best(...)
 6.   └─tune:::show_best.tune_results(...)
 7.     └─tune::.filter_perf_metrics(x, metric, eval_time)
 8.       └─tune::estimate_tune_results(x)

> train <- prepped_recipe %>% juice
> sapply(train[, info_vars], class)
    name       id   gender 
"factor" "factor" "factor" 
> sapply(processed_data[, info_vars], class)
    name       id   gender 
"factor" "factor" "factor" 
> class(processed_data)
[1] "tbl_df"     "tbl"        "data.frame"
> packageVersion("tune")
[1] ‘1.2.1’

复现代码

library(recipes)
library(workflows)

train_lasso_model <- function(recipe_obj, processed_data,
                              grid_size = 10, folds=2) {
    # Create a logistic regression model specification with Lasso regularization
  log_reg_spec <- logistic_reg(penalty = tune(), mixture = 1) %>%
    set_engine("glmnet")

  # Create a workflow
  workflow_obj <- workflow() %>%
    add_recipe(recipe_obj) %>%
    add_model(log_reg_spec)

  # Set up cross-validation
  cv_folds <- vfold_cv(processed_data, v = folds)
  str(cv_folds)

  # Tune the model to find the best regularization strength (penalty)
  message("before tune_grid")
  tune_results <- workflow_obj %>%
    tune_grid(resamples = cv_folds, grid = grid_size)
  message("after tune_grid")

  # Check the best tuning parameters (lambda)
  best_lambda <- tune_results %>%
    select_best(metric = "roc_auc")

  # Finalize the workflow with the best penalty
  message("before finalize_workflow")
  final_workflow <- workflow_obj %>%
    finalize_workflow(best_lambda)
  message("after finalize_workflow")

  # Fit the final model
  final_model <- fit(final_workflow, data = processed_data)

  # Return the trained model
  return(final_model)
}

test_data <- data.frame(
  name = c("sam", "sam", "sam"),
  id = c("1", "2", "3"),
  gender = c("male", "female", "female"),
  target = c(4, 5, 6)
)

info_vars <- c("name", "id",
        # mark gender as informational, but still make it a dummy var
        "gender")

recipe_obj <- recipe(target ~ ., data = test_data) %>%
  # mark vars as not used in the model
  update_role(
    all_of(info_vars),
    new_role = "informational") %>%
  # Create an "unknown" category for all unknown factor levels
  step_unknown(all_nominal(), skip = TRUE) %>%
  # Convert factors/character columns to dummies
  step_dummy(all_nominal(), -all_outcomes(), -all_of(info_vars),
             gender,
             keep_original_cols = TRUE)

prepped_recipe <- recipe_obj %>% prep(training = test_data)
processed_data <- prepped_recipe %>% bake(new_data=NULL)

final_model <- train_lasso_model(recipe_obj, processed_data)

train <- prepped_recipe %>% juice
sapply(train[, info_vars], class)
sapply(processed_data[, info_vars], class)
class(processed_data)
packageVersion("tune")

问题原因

核心问题出在工作流(workflow)的输入数据与配方(recipe)的原始元数据不匹配:

  1. 传递给train_lasso_model的processed_data是经过prep()和bake()处理后的因子型数据,但recipe_obj是基于原始字符型的test_data创建的,其内部记录的变量类型元数据为字符型。
  2. 在tune_grid运行时,workflow会对交叉验证的每个fold重新执行recipe的预处理步骤,此时会基于recipe的原始元数据校验变量类型,导致实际因子型数据与预期字符型数据冲突。
  3. step_dummy存在语法错误:单独传入的gender参数与前面-all_of(info_vars)的排除逻辑冲突,导致步骤执行混乱。

解决方案

方案1:基于处理后数据集创建recipe(推荐)

让recipe基于已完成类型转换的processed_data构建,确保元数据与输入数据一致:

# 修改recipe创建逻辑
recipe_obj <- recipe(target ~ ., data = processed_data) %>%
  update_role(all_of(info_vars), new_role = "informational") %>%
  step_unknown(all_nominal(), skip = TRUE) %>%
  # 修正step_dummy的选择器,删除多余的gender参数
  step_dummy(all_nominal(), -all_outcomes(), -all_of(info_vars),
             keep_original_cols = TRUE)

方案2:在recipe中显式转换类型

若必须基于原始test_data创建recipe,需在recipe内添加类型转换步骤,将字符列转为因子,确保预处理流程包含类型转换逻辑:

recipe_obj <- recipe(target ~ ., data = test_data) %>%
  update_role(all_of(info_vars), new_role = "informational") %>%
  # 显式将字符列转为因子
  step_string2factor(all_of(info_vars)) %>%
  step_unknown(all_nominal(), skip = TRUE) %>%
  step_dummy(all_nominal(), -all_outcomes(), -all_of(info_vars),
             keep_original_cols = TRUE)

额外修正:step_dummy语法错误

删除step_dummy中单独传入的gender参数,避免与-all_of(info_vars)的排除逻辑冲突。

验证效果

修改后,tune_grid会基于recipe内正确的类型元数据执行预处理,交叉验证的每个fold都会生成符合要求的因子型变量,同时保留所有列用于调试,实现数据处理与模型训练的分离。

内容的提问来源于stack exchange,提问作者dfrankow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:17:02