在R中使用tidyverse构建自定义函数——enquo()无法正常工作
解决自定义分组计算RMSE函数的报错问题
我有一个包含预测值(pred)、观测值(obs)和数据集分组(datasets)的数据框,直接用tidyverse分组计算RMSE可以正常运行:
datasets = rep(1:4, each=5) obs = runif(n=20, min=1, max=20) pred = runif(n=20, min=1, max=20) df = data.frame(datasets,obs,pred) library(tidyverse) library(hydroGOF) # 正常运行的代码 df %>% group_by(datasets) %>% reframe(result = rmse(obs,pred))
但自定义函数调用时却报错,函数代码和调用方式如下:
# 报错的自定义函数 make_calc = function(df,x,y,group){ library(tidyverse) library(hydroGOF) obs = enquo(x) pred = enquo(y) group= enquo(group) some_calc = df %>% group_by(!!group) %>% reframe(result = rmse(!!obs,!!pred)) return(some_calc) } make_calc(df,'obs','pred','datasets')
报错信息:
Error in `reframe()`: ℹ In argument: `result = rmse("obs", "pred")`. ℹ In group 1: `"datasets" = "datasets"`. Caused by error in `rmse.default()`: ! Invalid argument type: 'sim' & 'obs' have to be of class: c('integer', 'numeric', 'ts', 'zoo') Run `rlang::last_trace()` to see where the error occurred.
错误原因
问题出在参数传递和引用的不匹配:
enquo()的作用是捕获传入的表达式/裸变量名,但调用函数时传的是字符串(比如'obs'),enquo('obs')会把字符串本身当成值,而不是引用数据框中的obs列。- 这导致
rmse()拿到的是字符串而非数值向量,触发类型错误;同时group_by(!!group)会把字符串'datasets'当成一个常量值,从而新增一列而非按原有的datasets列分组。
解决方案
有两种常用的修正方式,根据参数传递习惯选择:
方法1:使用裸变量名传参(tidyverse推荐)
修改函数,用{{ }}(embrace语法)替代enquo()和!!,同时调用函数时直接传列名(不用引号):
make_calc = function(df, x, y, group){ library(tidyverse) library(hydroGOF) some_calc = df %>% group_by({{ group }}) %>% reframe(result = rmse({{ x }}, {{ y }})) return(some_calc) } # 调用时直接传裸列名 make_calc(df, obs, pred, datasets)
方法2:支持字符串传参
如果需要保留字符串传参的方式,用ensym()替代enquo(),它可以将字符串转换为对应的列名符号:
make_calc = function(df, x, y, group){ library(tidyverse) library(hydroGOF) obs = ensym(x) pred = ensym(y) group_col = ensym(group) some_calc = df %>% group_by(!!group_col) %>% reframe(result = rmse(!!obs, !!pred)) return(some_calc) } # 保持字符串传参 make_calc(df, 'obs', 'pred', 'datasets')
两种方法都能正确按datasets分组,并计算每组的RMSE,得到和直接运行代码一致的结果。
内容的提问来源于stack exchange,提问作者Jonathan
相关产品推荐
相关产品推荐

