ranovarlangquotations

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


I am trying to write a custom function to run a one-way within-subjects ANOVA using rlang + ez.

An example of the output I am expecting:

# 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

Now here is a custom function I have written to do the same but using non-standard evaluation implemented in 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
  )
}

But this doesn't work. Note that the dataframe is getting cleaned properly, so that's not where the error lies.

# 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.

Instead of dv = {{ y }}, I have also tried-

But none of these work.


Solution

  • Whenever I want to bridge rlang's NSE with functions that don't explicitly support it, I find that dividing the procedure in these 2 steps (at least conceptually) is always helpful:

    In your case, you can probably finish your function with something like:

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

    expr creates the expression and obviously supports rlang's NSE, and eval_tidy simply evaluates the expression.

    Oh and BTW, if ezANOVA (or any other function you want to use NSE with) supported strings instead of expressions as input, you'd need something like rlang::as_string(rlang::enexpr(param)), first capturing the expression of what the user wrote as param, and then using as_string to transform that expression.