将数据变量传递给 R 公式
Passing data-variables to R formulas
假设我想写 anscombe %>% lm_tidy("x1", "y1")
(实际上,我想写 anscombe %>% lm_tidy(x1, y1)
,其中 x1
和 y1
是数据框的一部分)。因此,由于以下功能似乎有效:
plot_gg <- function(df, x, y) {
x <- enquo(x)
y <- enquo(y)
ggplot(df, aes(x = !!x, y = !!y)) + geom_point() +
geom_smooth(formula = y ~ x, method="lm", se = FALSE)
}
我开始编写以下函数:
lm_tidy_1 <- function(df, x, y) {
x <- enquo(x)
y <- enquo(y)
fm <- y ~ x ##### I tried many stuff here!
lm(fm, data=df)
}
## Error in model.frame.default(formula = fm, data = df, drop.unused.levels = TRUE) :
## object is not a matrix
中的一条评论指出 embrace {{...}}
是引号-反引号模式的 shorthand 符号。不幸的是,两种情况下的错误消息都不同:
lm_tidy_2 <- function(df, x, y) {
fm <- !!enquo(y) ~ !!enquo(x) # alternative: {{y}} ~ {{x}} with different errors!!
lm(fm, data=df)
}
## Error:
## ! Quosures can only be unquoted within a quasiquotation context.
这似乎可行(基于 @jubas's answer,但我们受困于字符串处理和 paste
):
lm_tidy_str <- function(df, x, y) {
fm <- formula(paste({{y}}, "~", {{x}}))
lm(fm, data=df)
}
再一次,{{y}} != !!enquo(y)
。但更糟糕的是:以下函数出现故障,出现与之前相同的 Quosure
错误:
lm_tidy_str_1 <- function(df, x, y) {
x <- enquo(x)
y <- enquo(y)
fm <- formula(paste(!!y, "~", !!x))
lm(fm, data=df)
}
- 是
{{y}} != !!enquo(y)
吗?
- 如何将数据变量传递给
lm
?
编辑: 抱歉,我的许多试验都有遗留问题。我想直接将数据变量(比如 x1
和 y1
)传递给将它们用作公式组件(例如 lm
)的函数,而不是它们的字符串版本( "x1"
and "y1"
): 我尽量避免使用字符串,从用户的角度来看更精简。
考虑:
lm_tidy_1 <- function(df, x, y) {
fm <- reformulate(as.character(substitute(x)), substitute(y))
lm(fm, data=df)
}
lm_tidy_1(iris, Species, Sepal.Length)
lm_tidy_1(iris, 'Species', Sepal.Length)
lm_tidy_1(iris, Species, 'Sepal.Length')
lm_tidy_1(iris, 'Species', 'Sepal.Length')
编辑:
如果需要公式出现,更改调用对象:
lm_tidy_1 <- function(df, x, y) {
fm <- reformulate(as.character(substitute(x)), substitute(y))
res<-lm(fm, data=df)
res$call[[2]]<- fm
res
}
lm_tidy_1(iris, Species, Sepal.Length)
Call:
lm(formula = Sepal.Length ~ Species, data = df)
Coefficients:
(Intercept) Speciesversicolor Speciesvirginica
5.006 0.930 1.582
将公式包裹在“expr”中,然后计算它。
library(dplyr)
lm_tidy <- function(df, x, y) {
x <- sym(x)
y <- sym(y)
fm <- expr(!!y ~ !!x)
lm(fm, data = df)
}
这个函数等价于:
lm_tidy <- function(df, x, y) {
fm <- expr(!!sym(y) ~ !!sym(x))
lm(fm, data = df)
}
然后
lm_tidy(mtcars, "cyl", "mpg")
给予
Call:
lm(formula = fm, data = .)
Coefficients:
(Intercept) cyl
37.885 -2.876
根据以下评论进行编辑:
library(rlang)
lm_tidy_quo <- function(df, x, y){
y <- enquo(y)
x <- enquo(x)
fm <- paste(quo_text(y), "~", quo_text(x))
lm(fm, data = df)
}
然后您可以将符号作为参数传递
lm_tidy_quo(mtcars, cyl, mpg)
@BiranSzydek 的回答很不错。
但是它有 3 个缺点:
Call:
lm(formula = fm, data = .)
- 看不到实际使用的公式和数据。
- 必须将符号作为字符串输入。
- 来自
rlang
的依赖项 - 虽然它是一个很棒的包。
你确实可以用纯 base R 解决这个问题!
纯碱基 R 中的溶液
R 实际上是 under-the-hood 一个 Lisp。它适合这样的 meta-programming 任务。 R 的唯一缺点是它可怕的语法。
尤其是面对meta-programming的时候,没有Lisp语言那么漂亮优雅。语法确实会造成很多混淆 - 正如您在尝试解决此问题时亲身经历的那样。
解决方案是使用 substitute()
,您可以通过它以引用的方式替换代码片段:
lm_tidy <- function(df, x, y) {
# take the arguments as code pieces instead to evaluate them:
.x <- substitute(x)
.y <- substitute(y)
.df <- substitute(df)
# take the code piece `y ~ x` and substitute using list lookup table
.fm <- substitute(y ~ x, list(y=.y, x=.x))
# take the code `lm(fm, data=df)` and substitute with the code pieceses defined by the lookup table
# by replacing them by the code pieces stored in `.fm` and `.df`
# and finally: evaluate the substituted code in the parent environment (the environment where the function was called!)
eval.parent(substitute(lm(fm, data=df), list(fm=.fm, df=.df)))
}
诀窍是使用 eval.parent(substitute( <your expression>, <a list which determines the evaluation lookup-table for the variables in your expression>))
.
注意范围界定!只要 <your expression>
仅使用函数内部定义的变量或 substitute()
的 lookup-list 内部定义的变量构建,就不会有任何作用域问题!但避免引用 <your expression>
内的任何其他变量! - 因此,这是您在这种情况下安全使用 eval()
/eval.parent()
必须遵守的唯一规则!
但即使 eval.parent()
小心,替换的代码
在调用此函数的环境中执行。
现在,您可以:
lm_tidy(mtcars, cyl, mpg)
输出现在符合要求:
Call:
lm(formula = mpg ~ cyl, data = mtcars)
Coefficients:
(Intercept) cyl
37.885 -2.876
我们用纯碱基 R 做到了!
安全使用 eval()
的诀窍实际上是 substitute()
表达式中的每个变量都是 defined/given 在 substitute()
或函数参数的查找表中。换句话说:被替换变量的 None 指的是函数定义之外的任何悬空变量。
plot_gg
函数
因此,按照这些规则,您的 plot_gg
函数将定义为:
plot_gg <- function(df, x, y) {
.x <- substitute(x)
.y <- substitute(y)
.df <- substitute(df)
.fm <- substitute( y ~ x, list(x=.x, y=.y))
eval.parent(substitute(
ggplot(df, aes(x=x, y=y)) + geom_point() +
geom_smooth(formula = fm, method="lm", se=FALSE),
list(fm=.fm, x=.x, y=.y, df=.df)
))
}
当您想输入 x
和 y
作为字符串时
lm_tidy_str <- function(df, x, y) {
.x <- as.name(x)
.y <- as.name(y)
.df <- substitute(df)
.fm <- substitute(y ~ x, list(y=.y, x=.x))
eval.parent(substitute(lm(fm, data=df), list(fm=.fm, df=.df)))
}
plot_gg_str <- function(df, x, y) {
.x <- as.name(x)
.y <- as.name(y)
.df <- substitute(df)
.fm <- substitute( y ~ x, list(x=.x, y=.y))
eval.parent(substitute(
ggplot(df, aes(x=x, y=y)) + geom_point() +
geom_smooth(formula = fm, method="lm", se=FALSE),
list(fm=.fm, x=.x, y=.y, df=.df)
))
}
lm_tidy_str(mtcars, "cyl", "mpg")
# Call:
# lm(formula = mpg ~ cyl, data = mtcars)
#
# Coefficients:
# (Intercept) cyl
# 37.885 -2.876
#
require(ggplot2)
plot_gg_str(mtcars, "cyl", "mpg")
假设我想写 anscombe %>% lm_tidy("x1", "y1")
(实际上,我想写 anscombe %>% lm_tidy(x1, y1)
,其中 x1
和 y1
是数据框的一部分)。因此,由于以下功能似乎有效:
plot_gg <- function(df, x, y) {
x <- enquo(x)
y <- enquo(y)
ggplot(df, aes(x = !!x, y = !!y)) + geom_point() +
geom_smooth(formula = y ~ x, method="lm", se = FALSE)
}
我开始编写以下函数:
lm_tidy_1 <- function(df, x, y) {
x <- enquo(x)
y <- enquo(y)
fm <- y ~ x ##### I tried many stuff here!
lm(fm, data=df)
}
## Error in model.frame.default(formula = fm, data = df, drop.unused.levels = TRUE) :
## object is not a matrix
embrace {{...}}
是引号-反引号模式的 shorthand 符号。不幸的是,两种情况下的错误消息都不同:
lm_tidy_2 <- function(df, x, y) {
fm <- !!enquo(y) ~ !!enquo(x) # alternative: {{y}} ~ {{x}} with different errors!!
lm(fm, data=df)
}
## Error:
## ! Quosures can only be unquoted within a quasiquotation context.
这似乎可行(基于 @jubas's answer,但我们受困于字符串处理和 paste
):
lm_tidy_str <- function(df, x, y) {
fm <- formula(paste({{y}}, "~", {{x}}))
lm(fm, data=df)
}
再一次,{{y}} != !!enquo(y)
。但更糟糕的是:以下函数出现故障,出现与之前相同的 Quosure
错误:
lm_tidy_str_1 <- function(df, x, y) {
x <- enquo(x)
y <- enquo(y)
fm <- formula(paste(!!y, "~", !!x))
lm(fm, data=df)
}
- 是
{{y}} != !!enquo(y)
吗? - 如何将数据变量传递给
lm
?
编辑: 抱歉,我的许多试验都有遗留问题。我想直接将数据变量(比如 x1
和 y1
)传递给将它们用作公式组件(例如 lm
)的函数,而不是它们的字符串版本( "x1"
and "y1"
): 我尽量避免使用字符串,从用户的角度来看更精简。
考虑:
lm_tidy_1 <- function(df, x, y) {
fm <- reformulate(as.character(substitute(x)), substitute(y))
lm(fm, data=df)
}
lm_tidy_1(iris, Species, Sepal.Length)
lm_tidy_1(iris, 'Species', Sepal.Length)
lm_tidy_1(iris, Species, 'Sepal.Length')
lm_tidy_1(iris, 'Species', 'Sepal.Length')
编辑:
如果需要公式出现,更改调用对象:
lm_tidy_1 <- function(df, x, y) {
fm <- reformulate(as.character(substitute(x)), substitute(y))
res<-lm(fm, data=df)
res$call[[2]]<- fm
res
}
lm_tidy_1(iris, Species, Sepal.Length)
Call:
lm(formula = Sepal.Length ~ Species, data = df)
Coefficients:
(Intercept) Speciesversicolor Speciesvirginica
5.006 0.930 1.582
将公式包裹在“expr”中,然后计算它。
library(dplyr)
lm_tidy <- function(df, x, y) {
x <- sym(x)
y <- sym(y)
fm <- expr(!!y ~ !!x)
lm(fm, data = df)
}
这个函数等价于:
lm_tidy <- function(df, x, y) {
fm <- expr(!!sym(y) ~ !!sym(x))
lm(fm, data = df)
}
然后
lm_tidy(mtcars, "cyl", "mpg")
给予
Call:
lm(formula = fm, data = .)
Coefficients:
(Intercept) cyl
37.885 -2.876
根据以下评论进行编辑:
library(rlang)
lm_tidy_quo <- function(df, x, y){
y <- enquo(y)
x <- enquo(x)
fm <- paste(quo_text(y), "~", quo_text(x))
lm(fm, data = df)
}
然后您可以将符号作为参数传递
lm_tidy_quo(mtcars, cyl, mpg)
@BiranSzydek 的回答很不错。 但是它有 3 个缺点:
Call:
lm(formula = fm, data = .)
- 看不到实际使用的公式和数据。
- 必须将符号作为字符串输入。
- 来自
rlang
的依赖项 - 虽然它是一个很棒的包。
你确实可以用纯 base R 解决这个问题!
纯碱基 R 中的溶液
R 实际上是 under-the-hood 一个 Lisp。它适合这样的 meta-programming 任务。 R 的唯一缺点是它可怕的语法。 尤其是面对meta-programming的时候,没有Lisp语言那么漂亮优雅。语法确实会造成很多混淆 - 正如您在尝试解决此问题时亲身经历的那样。
解决方案是使用 substitute()
,您可以通过它以引用的方式替换代码片段:
lm_tidy <- function(df, x, y) {
# take the arguments as code pieces instead to evaluate them:
.x <- substitute(x)
.y <- substitute(y)
.df <- substitute(df)
# take the code piece `y ~ x` and substitute using list lookup table
.fm <- substitute(y ~ x, list(y=.y, x=.x))
# take the code `lm(fm, data=df)` and substitute with the code pieceses defined by the lookup table
# by replacing them by the code pieces stored in `.fm` and `.df`
# and finally: evaluate the substituted code in the parent environment (the environment where the function was called!)
eval.parent(substitute(lm(fm, data=df), list(fm=.fm, df=.df)))
}
诀窍是使用 eval.parent(substitute( <your expression>, <a list which determines the evaluation lookup-table for the variables in your expression>))
.
注意范围界定!只要 <your expression>
仅使用函数内部定义的变量或 substitute()
的 lookup-list 内部定义的变量构建,就不会有任何作用域问题!但避免引用 <your expression>
内的任何其他变量! - 因此,这是您在这种情况下安全使用 eval()
/eval.parent()
必须遵守的唯一规则!
但即使 eval.parent()
小心,替换的代码
在调用此函数的环境中执行。
现在,您可以:
lm_tidy(mtcars, cyl, mpg)
输出现在符合要求:
Call:
lm(formula = mpg ~ cyl, data = mtcars)
Coefficients:
(Intercept) cyl
37.885 -2.876
我们用纯碱基 R 做到了!
安全使用 eval()
的诀窍实际上是 substitute()
表达式中的每个变量都是 defined/given 在 substitute()
或函数参数的查找表中。换句话说:被替换变量的 None 指的是函数定义之外的任何悬空变量。
plot_gg
函数
因此,按照这些规则,您的 plot_gg
函数将定义为:
plot_gg <- function(df, x, y) {
.x <- substitute(x)
.y <- substitute(y)
.df <- substitute(df)
.fm <- substitute( y ~ x, list(x=.x, y=.y))
eval.parent(substitute(
ggplot(df, aes(x=x, y=y)) + geom_point() +
geom_smooth(formula = fm, method="lm", se=FALSE),
list(fm=.fm, x=.x, y=.y, df=.df)
))
}
当您想输入 x
和 y
作为字符串时
lm_tidy_str <- function(df, x, y) {
.x <- as.name(x)
.y <- as.name(y)
.df <- substitute(df)
.fm <- substitute(y ~ x, list(y=.y, x=.x))
eval.parent(substitute(lm(fm, data=df), list(fm=.fm, df=.df)))
}
plot_gg_str <- function(df, x, y) {
.x <- as.name(x)
.y <- as.name(y)
.df <- substitute(df)
.fm <- substitute( y ~ x, list(x=.x, y=.y))
eval.parent(substitute(
ggplot(df, aes(x=x, y=y)) + geom_point() +
geom_smooth(formula = fm, method="lm", se=FALSE),
list(fm=.fm, x=.x, y=.y, df=.df)
))
}
lm_tidy_str(mtcars, "cyl", "mpg")
# Call:
# lm(formula = mpg ~ cyl, data = mtcars)
#
# Coefficients:
# (Intercept) cyl
# 37.885 -2.876
#
require(ggplot2)
plot_gg_str(mtcars, "cyl", "mpg")