使用magrittr管道在tibble多列运行lm时为何报错?
问题背景
我发现以下两种R代码可以正常运行:
第一种:结合with()与magrittr管道的代码
with(mtcars, lm(mpg ~ qsec) %>% broom::tidy() %>% .[2,"p.value",T])
第二种:使用summarise()与across()的代码
mtcars %>% summarise(across(one_of("mpg", "cyl", "disp", "hp"), ~ broom::tidy(lm(. ~ qsec))[2,"p.value",T] ))
但当我把lm()的结果通过magrittr管道传递给broom::tidy()再提取p值时,代码报错:
出错代码
mtcars %>% summarise(across(one_of("mpg", "cyl", "disp", "hp"), ~ lm(. ~ qsec) %>% broom::tidy() %>% .[2,"p.value",T] ))
报错信息
16: .handleSimpleError(function (cnd) { msg <- glue("Can't compute column `{quo_data$name_auto}`.") abort(msg, call = call("across"), parent = cnd) }, "invalid 'x' type in 'x && y'", base::quote(drop && length(xo) == 1L)) 15: `[.tbl_df`(., mpg, 2, "p.value", T) 14: .[mpg, 2, "p.value", T] 13: lm(mpg ~ qsec) %>% broom::tidy() %>% mpg[2, "p.value", T] 12: .Call(dplyr_mask_eval_all_summarise, quo, private) 11: eval() 10: mask$eval_all_summarise(quo) 9: withCallingHandlers(mask$eval_all_summarise(quo), error = function(cnd) { msg <- glue("Can't compute column `{quo_data$name_auto}`.") abort(msg, call = call("across"), parent = cnd) }) 8: FUN(X[[i]], ...) 7: lapply(.x, .f, ...) 6: map(quosures, summarise_eval_one, mask = mask) 5: withCallingHandlers({ for (i in seq_along(dots)) { poke_error_context(dots, i, mask = mask) context_poke("column", old_current_column) dot <- dots[[i]] dot <- expand_pick(dot, mask) quosures <- expand_across(dot) quosures_results <- map(quosures, summarise_eval_one, mask = mask) for (k in seq_along(quosures)) { quo <- quosures[[k]] quo_data <- attr(quo, "dplyr:::data") quo_result <- quosures_results[[k]] if (is.null(quo_result)) { next } types_k <- quo_result$types chunks_k <- quo_result$chunks results_k <- quo_result$results if (!quo_data$is_named && is.data.frame(types_k)) { ... 4: summarise_cols(.data, dplyr_quosures(...), by, "summarise") 3: summarise.data.frame(., across(one_of("mpg", "cyl", "disp", "hp"), ~lm(. ~ qsec) %>% broom::tidy() %>% .[2, "p.value", T])) 2: summarise(., across(one_of("mpg", "cyl", "disp", "hp"), ~lm(. ~ qsec) %>% broom::tidy() %>% .[2, "p.value", T])) 1: mtcars %>% summarise(across(one_of("mpg", "cyl", "disp", "hp"), ~lm(. ~ qsec) %>% broom::tidy() %>% .[2, "p.value", T]))
原因解析
核心问题是magrittr管道中的.在dplyr的mask环境里被重新绑定了。
在across()的lambda函数(~开头的表达式)中,dplyr会创建特殊的mask环境,把当前遍历的列名(比如mpg)绑定为环境变量。而magrittr的.在这个环境里,会优先指向当前列的向量值,而非管道前一步的输出结果。
从报错信息第13行能看出:lm(mpg ~ qsec) %>% broom::tidy() %>% mpg[2, "p.value", T],这里的.被解析成了mpg列的数值向量,而非broom::tidy()返回的结果数据框,执行mpg[2,"p.value",T]自然报错——数值向量无法用数据框的索引方式取值。
而第二种正常运行的代码是直接嵌套调用,没有用管道,.在这里是lambda函数的参数(代表当前列),不会和管道的.混淆;第一种代码用with(),环境是mtcars数据框,管道的.正常指向前一步输出,因此都没问题。
修复方案
可以用两种方式解决:
- 用括号包裹管道里的
.表达式,明确指定.是管道前的输出:
mtcars %>% summarise(across(one_of("mpg", "cyl", "disp", "hp"), ~ lm(. ~ qsec) %>% broom::tidy() %>% (.)[2,"p.value",T] ))
- 改用
magrittr::extract()函数代替[索引,避免.的歧义:
mtcars %>% summarise(across(one_of("mpg", "cyl", "disp", "hp"), ~ lm(. ~ qsec) %>% broom::tidy() %>% magrittr::extract(2, "p.value") ))
内容的提问来源于stack exchange,提问作者Dima Lituiev
相关产品推荐
相关产品推荐

