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-
dv = rlang::as_string(y)
dv = rlang::as_name(y)
dv = rlang::enquo(y)
But none of these work.
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:
rlang
functions.rlang::eval_tidy
if quosures are involved, or with base::eval
otherwise.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.