gpt4 book ai didi

r - purrr + dplyr NSE 问题在用户编写的函数中

转载 作者:行者123 更新时间:2023-12-03 21:54:11 24 4
gpt4 key购买 nike

经过大量的反复试验和咨询以前的答案,例如 How to detect if bare variable or string我想我自己已经完成了大部分我需要做的事情。但是我很想知道在我将“解决方案”投入生产之前,我是否做出了一些错误的假设或愚蠢地解决了问题。

考虑以下数据:

library(dplyr)
library(purrr)
library(tidyselect)

set.seed(1111)
dat1 <- data.frame(Region = rep(c("r1","r2"), each = 100),
State = rep(c("NY","MA","FL","GA"), each = 10),
Loc = rep(c("a","b","c","d","e","f","g","h"),each = 5),
ID = rep(c(1:10), each = 2),
var1 = rnorm(200),
var2 = rnorm(200),
var3 = rnorm(200),
var4 = rnorm(200),
var5 = rnorm(200))

我想编写一个可以做很多事情的函数,但我将从一个最小的可重现示例开始。我要获取 tidied aov结果返回单个案例 var1 ~ State或使用 map2 匹配一对列表一个列表包含“结果”,另一个“预测器”。它们从使用到使用永远不会相同,并且与我的示例不同,变量很少适用于像 starts_with 这样的简单解决方案。 .

两个具体问题和一个通用问题。

问题 1 - 我已经放弃了允许最终用户(包括我)传入裸变量名的做法,但以后总是会给我带来麻烦。按照上面的引用是我的代码之类的最快最可靠的方法来捕捉它们并告诉用户? (我在代码中添加了注释以表明我在说什么。

问题 #2 - 通过基本的跟踪和错误,我想我解决了我的另一个问题,即生成一些文本以供以后用作标签。当我不使用 map2 的功能时,我找到了很多解决方案但只有这个似乎适用于 map2。看起来如此复杂,我无法相信这是一个不错的选择......(再次在代码中注释以显示位置)

一般问题:我添加了推荐的 tidyselect::all_of因为这些可能是含糊不清的列表,为什么我还要提防 .x.y被视为调用而不仅仅是迭代标记?

MyFunction <- function(data,
groupvar,
var) {
# Issue #1 is this best way to warn/stop user?
lst <- as.list(match.call())

if (is.symbol(lst$groupvar) || is.symbol(lst$var)) {
stop("Please quote all variables")
}

# Issue #2 I want the group label but if I don't include
# this if logic it errors with " Error: Can't convert a call to a string"
# when I run it with purrr::map2
if (!is.call(groupvar)) {
grouplabel <- rlang::as_name(rlang::enquo(groupvar))
}

data <-
dplyr::select(
.data = data,
var = {{ var }},
groupvar = {{ groupvar }}
)

aov_object <- aov(var ~ groupvar, data = data)
aov_results <- broom::tidy(aov_object) %>%
mutate(term = if_else(term != "Residuals", grouplabel, term))
return(aov_results)
}

# Expected output

MyFunction(data = dat1, groupvar = "State", var = "var1") # works
#> # A tibble: 2 x 6
#> term df sumsq meansq statistic p.value
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 State 3 1.75 0.582 0.485 0.693
#> 2 Residuals 196 235. 1.20 NA NA

MyFunction(data = dat1, groupvar = State, var = var1) # warns appropriately
#> Error in MyFunction(data = dat1, groupvar = State, var = var1): Please quote all variables

# Quick test of `map2`
grouping_vars <- names(dat1[,1:3])
names(grouping_vars) <- names(dat1[,1:3])

outcome_vars <- names(dat1[,5:7])
names(outcome_vars) <- names(dat1[,5:7])

names(outcome_vars) <- paste(outcome_vars, "~", grouping_vars)

# get multiple results this is where issue #2 comes in but this is what I want it to look like.

map2(.x = outcome_vars,
.y = grouping_vars,
.f = ~ MyFunction(dat = dat1,
var = tidyselect::all_of(.x),
groupvar = tidyselect::all_of(.y)))
#> $`var1 ~ Region`
#> # A tibble: 2 x 6
#> term df sumsq meansq statistic p.value
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Region 1 0.0512 0.0512 0.0427 0.836
#> 2 Residuals 198 237. 1.20 NA NA
#>
#> $`var2 ~ State`
#> # A tibble: 2 x 6
#> term df sumsq meansq statistic p.value
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 State 3 5.05 1.68 2.07 0.106
#> 2 Residuals 196 159. 0.814 NA NA
#>
#> $`var3 ~ Loc`
#> # A tibble: 2 x 6
#> term df sumsq meansq statistic p.value
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Loc 7 5.09 0.727 0.772 0.612
#> 2 Residuals 192 181. 0.943 NA NA

最佳答案

在我看来,由于您坚持将字符串作为变量名传递,因此使用 as.formula 更改公式以匹配变量会更简单、更有效。而不是改变数据。这也可以防止您必须在函数内单独命名分组变量。

以下函数在基准测试中比原始函数更短,速度大约是原始函数的两倍,但行为保持不变:

MyFunctionNew <- function(data, groupvar, var) 
{
lst <- as.list(match.call())
if (is.symbol(lst$groupvar) || is.symbol(lst$var))
stop("Please quote all variables")

broom::tidy(aov(as.formula(paste(var, "~", groupvar)), data = data)) %>%
mutate(term = if_else(term != "Residuals", groupvar, term))
}

可以看到在 map2里面还是有效的:
map2(.x = outcome_vars,
.y = grouping_vars,
.f = ~ MyFunctionNew(dat = dat1,
var = tidyselect::all_of(.x),
groupvar = tidyselect::all_of(.y)))
#> $`var1 ~ Region`
#> # A tibble: 2 x 6
#> term df sumsq meansq statistic p.value
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Region 1 0.0512 0.0512 0.0427 0.836
#> 2 Residuals 198 237. 1.20 NA NA
#>
#> $`var2 ~ State`
#> # A tibble: 2 x 6
#> term df sumsq meansq statistic p.value
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 State 3 5.05 1.68 2.07 0.106
#> 2 Residuals 196 159. 0.814 NA NA
#>
#> $`var3 ~ Loc`
#> # A tibble: 2 x 6
#> term df sumsq meansq statistic p.value
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Loc 7 5.09 0.727 0.772 0.612
#> 2 Residuals 192 181. 0.943 NA NA

在筛选变量以确保它们是字符串方面,我认为这不是惯用的 R 用法,并且可能会给您的函数的临时用户造成一些混淆。换句话说,它违反了 principle of least astonishment .

例如,作为一个天真的用户,我希望能够像这样以编程方式指定分组变量:
MyVar <- "State"
MyFunction(data = dat1, groupvar = MyVar, var = "var1")

但是,我收到一个错误,告诉我所有变量都应该被引用。

这也意味着您的函数将无法在基本 R 循环和 *apply 中工作。职能:
lapply(c("State", "Region", "ID"), function(x) MyFunction(dat1, x, "var1"))
#> Error in MyFunction(dat1, x, "var1") : Please quote all variables

我认为这比在使用不带引号的列名时允许抛出错误更令人困惑和限制。因此,我认为你的生产函数应该是这样的:
MyFunction <- function(data, groupvar, var) 
{
broom::tidy(aov(as.formula(paste(var, "~", groupvar)), data = data)) %>%
mutate(term = if_else(term != "Residuals", groupvar, term))
}

其表现如下:
MyFunction(data = dat1, groupvar = "State", var = "var1") 
#> # A tibble: 2 x 6
#> term df sumsq meansq statistic p.value
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 State 3 1.75 0.582 0.485 0.693
#> 2 Residuals 196 235. 1.20 NA NA

MyFunction(data = dat1, groupvar = MyVar, var = "var1")
#> # A tibble: 2 x 6
#> term df sumsq meansq statistic p.value
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 State 3 1.75 0.582 0.485 0.693
#> 2 Residuals 196 235. 1.20 NA NA

MyFunction(data = dat1, groupvar = State, var = var1)
#> Error in paste(var, "~", groupvar) : object 'State' not found

我认为大多数 R 用户会意识到为什么他们会收到最后一个错误,因为这很清楚。这也是普通 R 用户会多次看到的错误。如果您对用户不那么信任,也许您可​​以尝试将函数体包装在 tryCatch 中。将“符号未找到错误”转换为“请使用引号”错误。

最终,最好将函数编写为使用裸符号,但我的印象是您很想避免这种情况,因此我不会在这里强调这一点。

关于r - purrr + dplyr NSE 问题在用户编写的函数中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62091090/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com