如何使用purrr::reduce()将表达式列表应用于任意数据框?
表达式列表适配任意数据框的通用实现方案
问题背景
我用rlang::enexprs()生成了一组处理数据框的表达式列表,想把这组表达式依次应用到任意数据框上,最终得到处理后的单一数据框。但自己写的apply.expr()函数有问题:
- 当函数参数命名为
df时,执行结果不符合预期 - 改成
data后,只能适配命名为data的数据框,没法通用到其他命名的数据集
现有尝试代码
初始版本(参数为df,结果不符合预期)
select.expr <- function(...) rlang::enexprs(...) apply.expr <- function(df, filtre.list) { eval_df <- function(df, expr) rlang::eval_tidy(expr, data = df) purrr::reduce(filtre.list, eval_df, .init = df) }
测试示例
data <- data.frame(a = c(1, 1, 1, 1), b = c(2, 2, 2, 2), c = c(3, 3, 3, 3)) expr <- select.expr(replace(data, data == 2, "two"), replace(data, data == 3, "three")) apply.expr(data, expr) ## a b c ## 1 1 2 three ## 2 1 2 three ## 3 1 2 three ## 4 1 2 three
修改参数名后的版本(仅适配data数据框)
apply.expr <- function(df, filtre.list) { eval_df <- function(data, expr) rlang::eval_tidy(expr, data = data) purrr::reduce(filtre.list, eval_df, .init = df) } apply.expr(data, expr) ## a b c ## 1 1 two three ## 2 1 two three ## 3 1 two three ## 4 1 two three
问题根源
核心问题是表达式里硬编码了数据框的名称(比如data),执行eval_tidy时,会优先去创建表达式的环境中查找这个硬编码的变量,而不是使用apply.expr传入的动态数据框参数。修改参数名为data只是刚好匹配了硬编码的名称,换个数据框名就失效了。
通用解决方案
方案1:创建表达式时使用占位符(推荐)
修改select.expr,让表达式用占位符(比如.)代替具体的数据框名,然后在apply.expr中动态绑定占位符到当前处理的数据框:
# 生成表达式列表,用`.`作为数据框占位符 select.expr <- function(...) rlang::enexprs(...) # 通用的表达式应用函数 apply.expr <- function(df, filtre.list) { purrr::reduce(filtre.list, function(current_df, expr) { # 把表达式中的`.`绑定到当前迭代的数据集 rlang::eval_tidy(expr, data = list(. = current_df)) }, .init = df) }
测试验证(适配任意命名的数据框)
# 测试命名为test_df的数据框 test_df <- data.frame(a = c(1, 1), b = c(2, 2), c = c(3, 3)) # 创建表达式时用`.`代替具体数据框名 expr <- select.expr(replace(., . == 2, "two"), replace(., . == 3, "three")) apply.expr(test_df, expr) ## a b c ## 1 1 two three ## 2 1 two three
方案2:动态替换表达式中的硬编码数据框名
如果已经有了硬编码数据框名的表达式,可以在apply.expr中动态替换这个名称为传入的数据框:
apply.expr <- function(df, filtre.list, original_name = rlang::sym("data")) { eval_df <- function(current_df, expr) { # 替换表达式中的硬编码名称为当前数据集 modified_expr <- rlang::expr_replace(expr, original_name, rlang::enexpr(current_df)) rlang::eval_tidy(modified_expr) } purrr::reduce(filtre.list, eval_df, .init = df) } # 使用示例:表达式里硬编码的是data apply.expr(test_df, expr) # 如果表达式里硬编码的是other_df,指定original_name参数 # apply.expr(test_df, expr, original_name = rlang::sym("other_df"))
说明
方案1更灵活,不需要关心表达式对应的原始数据框名称,只要用统一的占位符就能适配任意数据框;方案2适合处理已经存在的、硬编码了数据框名的表达式列表。
内容的提问来源于stack exchange,提问作者ian
相关产品推荐
相关产品推荐

