如何按序将函数列表全部应用到R语言DataFrame?
问题描述
首先创建如下test数据框:
test <- data.frame( a = c(1, "two", "na", 4), b = c(1, 2, 3, 4), c = c(1, "nA", 3, 4), d = c("na", NA, TRUE, "5*"), e = c(3, 4, 0.02, 1.5) )
通过add.filters函数生成包含两个replace操作的函数列表f:
add.filters <- function(...){ rlang::enquos(...) } f <- add.filters(replace(test, test ==3 , 300), replace(test, test ==4 , 400))
查看f的结构:
<list_of<quosure>> [[1]] <quosure> expr: ^replace(test, test == 3, 300) env: global [[2]] <quosure> expr: ^replace(test, test == 4, 400) env: global
使用apply.filters函数按序应用操作:
apply.filters <- function(df, list.filtre) { purrr::reduce(list.filtre, function(df, filt) { rlang::eval_tidy(filt, data = df) }, .init = df) } apply.filters(test,f) # a b c d e # 1 1 1 1 na 3.00 # 2 two 2 nA <NA> 400.00 # 3 na 3 3 TRUE 0.02 # 4 400 400 400 5* 1.50
结果仅最后一个replace操作生效(4被替换为400,但3未被替换为300),需要让列表中所有函数按序执行,得到预期的DataFrame。
问题原因与解决方案
原因分析
你定义的f列表里,每个quosure都硬编码了原始的test数据框,而非引用迭代过程中动态更新的df参数。第一个replace操作实际是在原始test上执行,结果没有传递到下一个步骤;第二个操作同样基于原始test执行,最终只有最后一个操作的结果被保留。
修改方案
方法1:用占位符指代当前数据框
调整add.filters的调用,用.作为当前数据框的占位符,替代固定的test:
# 重新定义过滤列表 f <- add.filters(replace(., . ==3 , 300), replace(., . ==4 , 400))
此时f的结构变为:
<list_of<quosure>> [[1]] <quosure> expr: ^replace(., . == 3, 300) env: global [[2]] <quosure> expr: ^replace(., . == 4, 400) env: global
保持apply.filters不变,执行后即可得到预期结果:
apply.filters(test,f) # a b c d e # 1 1 1 1 na 300.00 # 2 two 2 nA <NA> 400.00 # 3 na 300 300 TRUE 0.02 # 4 400 400 400 5* 1.50
方法2:直接传递接收数据框的函数
如果希望逻辑更直观,可以将每个过滤操作定义为接收df参数的函数:
# 修改add.filters为接收函数列表 add.filters <- function(...){ list(...) } # 定义每个过滤函数,明确接收当前数据框 f <- add.filters( function(df) replace(df, df ==3 , 300), function(df) replace(df, df ==4 , 400) ) # 修改apply.filters直接调用函数 apply.filters <- function(df, list.filtre) { purrr::reduce(list.filtre, function(df, filt) { filt(df) }, .init = df) } apply.filters(test,f) # 同样得到预期结果
补充说明
- 第一种方法利用
rlang中.作为数据上下文占位符的特性,eval_tidy执行时会将.绑定到当前的df。 - 第二种方法直接传递函数,每个函数明确处理输入数据并返回修改结果,避免了quosure的环境绑定问题。
内容的提问来源于stack exchange,提问作者anria
相关产品推荐
相关产品推荐

