使用 rlang 围绕 ez::ezANOVA 编写自定义函数

using `rlang` to write a custom function around `ez::ezANOVA`

我正在尝试使用 rlang + ez 为 运行 单向受试者内方差分析编写自定义函数。

我期望的输出示例:

# setup
set.seed(123)
library(WRS2)
library(ez)
library(tidyverse)

# getting data in format that `ez` expects
df <- WRS2::WineTasting %>%
  dplyr::mutate_if(
    .tbl = .,
    .predicate = purrr::is_bare_character,
    .funs = as.factor
  ) %>%
  dplyr::mutate(.data = ., Taster = as.factor(Taster))

# this works
ez::ezANOVA(
  data = df,
  dv = Taste,
  wid = Taster,
  within = Wine,
  detailed = TRUE,
  return_aov = TRUE
)
#> $ANOVA
#>        Effect DFn DFd          SSn       SSd           F            p
#> 1 (Intercept)   1  21 2.005310e+03 4.2186364 9982.254929 1.311890e-29
#> 2        Wine   2  42 9.371212e-02 0.3129545    6.288308 4.084101e-03
#>   p<.05        ges
#> 1     * 0.99774530
#> 2     * 0.02026075
#> 
#> $`Mauchly's Test for Sphericity`
#>   Effect         W          p p<.05
#> 2   Wine 0.7071776 0.03128132     *
#> 
#> $`Sphericity Corrections`
#>   Effect       GGe       p[GG] p[GG]<.05       HFe       p[HF] p[HF]<.05
#> 2   Wine 0.7735015 0.008439799         * 0.8233709 0.007188822         *
#> 
#> $aov
#> 
#> Call:
#> aov(formula = formula(aov_formula), data = data)
#> 
#> Grand Mean: 5.512121
#> 
#> Stratum 1: Taster
#> 
#> Terms:
#>                 Residuals
#> Sum of Squares   4.218636
#> Deg. of Freedom        21
#> 
#> Residual standard error: 0.4482047
#> 
#> Stratum 2: Taster:Wine
#> 
#> Terms:
#>                       Wine  Residuals
#> Sum of Squares  0.09371212 0.31295455
#> Deg. of Freedom          2         42
#> 
#> Residual standard error: 0.08632091
#> Estimated effects may be unbalanced

现在这是我编写的一个自定义函数来执行相同的操作,但使用 rlang 中实现的非标准评估:

# custom function
aov_fun <- function(data, x, y, id) {
  # getting data in format that `ez` expects
  df <- data %>%
    dplyr::mutate_if(
      .tbl = .,
      .predicate = purrr::is_bare_character,
      .funs = as.factor
    ) %>%
    dplyr::mutate(.data = ., {{ id }} := as.factor({{ id }})) %>%
    tibble::as_tibble(.)

  # print the dataframe to see if it was cleaned as expected
  print(df)

  # running anova
  ez::ezANOVA(
    data = df,
    dv = {{ y }},
    wid = {{ id }},
    within = {{ x }},
    detailed = TRUE,
    return_aov = TRUE
  )
}

但这行不通。请注意,数据框已正确清理,因此这不是错误所在。

# using the function
aov_fun(WRS2::WineTasting, Wine, Taste, Taster)
#> # A tibble: 66 x 3
#>    Taste Wine   Taster
#>    <dbl> <fct>  <fct> 
#>  1  5.4  Wine A 1     
#>  2  5.5  Wine B 1     
#>  3  5.55 Wine C 1     
#>  4  5.85 Wine A 2     
#>  5  5.7  Wine B 2     
#>  6  5.75 Wine C 2     
#>  7  5.2  Wine A 3     
#>  8  5.6  Wine B 3     
#>  9  5.5  Wine C 3     
#> 10  5.55 Wine A 4     
#> # ... with 56 more rows

#> Error in ezANOVA_main(data = data, dv = dv, wid = wid, within = within, : "{
#>     y
#> }" is not a variable in the data frame provided.

代替dv = {{ y }},我也试过-

但是 none 这些工作。

这可以用

更正
aov_fun <- function(data, x, y, id) {

  lst1 <- as.list(match.call()[-1])
  names(lst1)<- c("data", "dv", "wid", "within")[match(names(lst1), 
                      c("data", "y", "id", "x"))]

  df <- data %>%
    dplyr::mutate_if(
      .tbl = .,
      .predicate = purrr::is_bare_character,
      .funs = as.factor
    ) %>%
    dplyr::mutate(.data = ., {{ id }} := as.factor({{ id }})) %>%
    tibble::as_tibble(.)




   do.call(getFromNamespace("ezANOVA", "ez"), 
                c(lst1, detailed = TRUE, return_aov = TRUE))

}

-测试

aov_fun(WRS2::WineTasting, x = Wine,y = Taste, id = Taster)
#$ANOVA
#           Effect DFn DFd          SSn       SSd           F            p p<.05        ges
#    1 (Intercept)   1  21 2.005310e+03 4.2186364 9982.254929 1.311890e-29     * 0.99774530
#    2        Wine   2  42 9.371212e-02 0.3129545    6.288308 4.084101e-03     * 0.02026075

#   $`Mauchly's Test for Sphericity`
#      Effect         W          p p<.05
#    2   Wine 0.7071776 0.03128132     *

#    $`Sphericity Corrections`
#      Effect       GGe       p[GG] p[GG]<.05       HFe       p[HF] p[HF]<.05
#    2   Wine 0.7735015 0.008439799         * 0.8233709 0.007188822         *

#    $aov

#    Call:
#    aov(formula = formula(aov_formula), data = data)

#    Grand Mean: 5.512121

#    Stratum 1: Taster

#    Terms:
#                    Residuals
#    Sum of Squares   4.218636
#    Deg. of Freedom        21

#    Residual standard error: 0.4482047

#    Stratum 2: Taster:Wine

#    Terms:
#                          Wine  Residuals
#    Sum of Squares  0.09371212 0.31295455
#    Deg. of Freedom          2         42

#    Residual standard error: 0.08632091
#    Estimated effects may be unbalanced

每当我想将 rlang 的 NSE 与未明确支持它的功能桥接时, 我发现将程序分为这两个步骤(至少在概念上)总是有帮助的:

  • 使用 rlang 函数创建我想要的最终表达式。
  • 评估它,如果涉及到问题则使用 rlang::eval_tidy,否则使用 base::eval

在你的情况下,你可能可以用类似的东西来完成你的功能:

# running anova
rlang::eval_tidy(rlang::expr(ez::ezANOVA(
    data = df,
    dv = {{ y }},
    wid = {{ id }},
    within = {{ x }},
    detailed = TRUE,
    return_aov = TRUE
)))

expr 创建表达式并且显然支持 rlang 的 NSE, eval_tidy 只是计算表达式。

哦,顺便说一句,if ezANOVA(或任何其他您想与 NSE 一起使用的函数)支持字符串而不是表达式作为输入, 你需要像 rlang::as_string(rlang::enexpr(param)) 这样的东西, 首先捕获用户写的内容的表达式 param然后 使用 as_string 转换该表达式。

这是来自 @moody_mudskipper 的 tags package

using_bang 的一个很棒的应用程序
aov_fun <- function(data, x, y, id) {

  # ...
  # code as before

  # running anova
  tags::using_bang$ezANOVA(
    data = df,
    dv = {{y}},
    wid = {{id}},
    within = {{x}},
    detailed = TRUE,
    return_aov = TRUE
  )
}