在 R 中的函数中创建和使用新变量:tidyverse 中的 NSE 编程错误
Posted
技术标签:
【中文标题】在 R 中的函数中创建和使用新变量:tidyverse 中的 NSE 编程错误【英文标题】:Creating and using new variables in function in R: NSE programing error in the tidyverse 【发布时间】:2018-04-23 20:32:29 【问题描述】:在阅读和重新阅读许多“使用 dplyr 编程”指南之后,我仍然找不到解决我的特殊情况的方法。
我知道使用 group_by_
、mutate_
和这种“字符串友好”版本的 tidyverse 函数正在走向弃用,而 enquo
是要走的路。
但是,我的情况有些不同,我正在努力寻找一种整洁的方式来解决它。
确实,我的目标是在函数中创建和操作数据框。根据其他变量创建(mutating)新变量,使用它们等。
但是,无论我多么努力,我的代码要么出错,要么在检查包时返回一些警告,例如no visible binding for global variable ...
。
这是一个可重现的示例:
这是我想做的:
df <- data.frame(X=c("A", "B", "C", "D", "E"),
Y=c(1, 2, 3, 1, 1))
new_df <- df %>%
group_by(Y) %>%
summarise(N=n()) %>%
mutate(Y=factor(Y, levels=1:5)) %>%
complete(Y, fill=list(N = 0)) %>%
arrange(Y) %>%
rename(newY=Y) %>%
mutate(Y=as.integer(newY))
一些常见的 dplyr 操作,预期结果应该是:
# A tibble: 5 x 3
newY N Y
<fctr> <dbl> <int>
1 1 3 1
2 2 1 2
3 3 1 3
4 4 0 4
5 5 0 5
我希望这段代码能够在内部一个函数中安静地工作。以下是我处理非 NSE 问题的最佳尝试:
myfunction <- function()
df <- data.frame(X=c("A", "B", "C", "D", "E"),
Y=c(1, 2, 3, 1, 1))
new_df <- df %>%
group_by_("Y") %>%
summarise(!!"N":=n()) %>%
mutate(!!"Y":=factor(Y, levels=1:5)) %>%
complete_("Y", fill=list(N = 0)) %>%
arrange_("Y") %>%
rename(!!"newY":="Y") %>%
mutate(!!"Y":=as.integer(newY))
很遗憾,我仍然收到以下消息:
myfunction: no visible global function definition for ':='
myfunction: no visible binding for global variable 'Y'
myfunction: no visible binding for global variable 'newY'
Undefined global functions or variables:
:= Y n.Factors n_optimal newY
有办法解决吗?非常感谢!
编辑:我正在使用 R 3.4.1、dplyr_0.7.4、tidyr_0.7.2 和 tidyverse_1.1.1
回答
感谢我设法解决的 cmets,这是可行的解决方案:
myfunction <- function()
df <- data.frame(X=c("A", "B", "C", "D", "E"),
Y=c(1, 2, 3, 1, 1))
new_df <- df %>%
group_by_("Y") %>%
summarise_("N"=~n()) %>%
mutate_("Y"= ~factor(Y, levels=1:5)) %>%
complete_("Y", fill=list(N = 0)) %>%
arrange_("Y") %>%
rename_("newY"=~Y) %>%
mutate_("Y"=~as.integer(newY))
非常感谢:)
【问题讨论】:
您使用的是哪个版本的dplyr
?你加载的是library(tyidyverse)
还是library(rlang)
?运行代码时,我没有遇到与您相同的错误。分享来自sessionInfo()
的信息,说明您正在运行哪些软件包和版本。在你的新函数中究竟需要参数化什么?
我确实在开始时加载了 tidyverse(我已经编辑了我的问题以添加版本)。 “什么需要参数化”是什么意思?
现在你的函数似乎根本不需要任何花哨的 NSE 东西。所以您希望能够将“Y”和“N”以及变量传递给函数或其他东西?您使用字符串的原因是什么?到底要怎么调用函数?
好的,所以我正在开发一个从分析中提取一些值的函数,将其转换为基本数据框(我的示例中的 df
)。然后,我执行这些确切的步骤(如示例所示)以获得一个新的数据框。后来,我将这个数据框重新用于其他计算、绘图和其他东西。我的意思是函数有效,但困扰我的是我在构建包时收到的警告,因为它阻止了 CRAN 发布:(
哦,这实际上是关于在 R 包中正确使用 rlang。问题中似乎缺少这种背景。见相关:here1here2here3。在编写自己的包时避免这些东西要容易得多。
【参考方案1】:
答案不在“使用 dplyr 编程”指南中,因为您的问题更为普遍。尽管您的代码处理非标准评估,但您的案例不需要它。如果您删除处理非标准评估的代码,您将减少需要修复的问题数量。
不过,一些重要的问题仍然存在——NAMESPACE 问题。每当您在自己的包的函数中使用来自其他包的函数时,您都会处理 NAMESPACE。 NAMESPACE 不是一个简单的话题,但如果你正在编写包,学习一点是值得的。我建议您阅读:从 r-pkgs.had.co.nz/namespace.html,找到“Imports”部分并阅读它的介绍以及副标题“R 函数”。这将帮助您理解我在下面发布的步骤、代码和 cmets。
请按照以下步骤解决您的问题:
- 将 dplyr、magrittr 和 tidyr 添加到DESCRIPTION。
- 引用函数为PACKAGE::FUNCTION()
.
- 删除所有 !!
和 :=
,因为在这种情况下您不需要它们。
- 从 magrittr 导入和导出管道。
- 从 rlang 导入 .data。
- 将全局变量传递给 utils::globalVariables()。
- 重建,重新加载,重新检查。
# I make your function shorter to focus on the important details.
myfunction <- function()
df <- data.frame(
X = c("A", "B", "C", "D", "E"),
Y = c(1, 2, 3, 1, 1)
)
df %>%
dplyr::group_by(.data$Y) %>%
dplyr::summarise(N = n())
# Fix check() notes
#' @importFrom magrittr %>%
#' @export
magrittr::`%>%`
#' @importFrom rlang .data
NULL
utils::globalVariables(c(".data", "n"))
【讨论】:
虽然代码 cmets 是提供代码解释的好方法,但除了代码之外,请提供一些关于您的答案的解释。 请在答案中添加解释。 感谢 Mehraban 帮助我改进答案!【参考方案2】:您可以使用rlang::sym()
(或base::as.name()
)将字符转换为符号,所以让我添加一个替代答案。
请注意,我并不是要强迫您丢弃这些已弃用的功能。你可以使用对你来说容易理解的。 (不过我相信sym()
更有用)
案例一:rlang::sym()
的基本用法
此代码
group_by_("Y") %>%
可以写成
group_by(!! rlang::sym("Y"))
或者您甚至可以预先将符号分配给变量。
col_Y <- rlang::sym("Y")
df %>%
group_by(!! col_Y)
案例 2:左侧符号
这段代码完全没问题。
summarise(!!"N":=n())
LHS 允许使用字符和符号。所以这也很好:
col_N <- rlang::sym("N")
# ...
summarise(!! col_N := n())
案例 3) 选择语义
select()
和 rename()
与 mutate()
等其他函数具有不同的语义;它允许除符号之外的字符。这可能是一个有点高级的话题。你可以在a vignette找到更详细的解释。
More precisely, the code bellow are both permitted:
rename(new = old)
rename(new = "old")
So, this code is fine.
rename(!! "newY" := "Y")
(示例)
reprex::reprex_info()
#> Created by the reprex package v0.1.1.9000 on 2017-11-12
library(dplyr, warn.conflicts = FALSE)
library(tidyr)
df <- data.frame(X=c("A", "B", "C", "D", "E"),
Y=c(1, 2, 3, 1, 1))
col_Y <- rlang::sym("Y")
col_N <- rlang::sym("N")
col_newY <- rlang::sym("newY")
df %>%
group_by(!! col_Y) %>%
summarise(!! col_N := n()) %>%
mutate(!! col_Y := factor(!! col_Y, levels=1:5)) %>%
complete(!! col_Y, fill = list(N = 0)) %>%
arrange(!! col_Y) %>%
rename(!! col_newY := !! col_Y) %>%
mutate(!! col_Y := as.integer(!! col_newY))
#> # A tibble: 5 x 3
#> newY N Y
#> <fctr> <dbl> <int>
#> 1 1 3 1
#> 2 2 1 2
#> 3 3 1 3
#> 4 4 0 4
#> 5 5 0 5
【讨论】:
以上是关于在 R 中的函数中创建和使用新变量:tidyverse 中的 NSE 编程错误的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Protractor 中创建和操作 Promise?