为何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)的原始元数据不匹配:
- 传递给
train_lasso_model的processed_data是经过prep()和bake()处理后的因子型数据,但recipe_obj是基于原始字符型的test_data创建的,其内部记录的变量类型元数据为字符型。 - 在
tune_grid运行时,workflow会对交叉验证的每个fold重新执行recipe的预处理步骤,此时会基于recipe的原始元数据校验变量类型,导致实际因子型数据与预期字符型数据冲突。 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
相关产品推荐
相关产品推荐

