использование `rlang` для написания пользовательской функции вокруг `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 }} еще пробовал-

  • dv = rlang::as_string(y)
  • dv = rlang::as_name(y)
  • dv = rlang::enquo(y)

Но ни один из них не работает.


person Indrajeet Patil    schedule 13.08.2019    source источник
comment
Я также буду в порядке с решением rlang, которое не использует {{...}}; альтернативные решения, которые я пробовал, действительно не используют его, но все равно не работают.   -  person Indrajeet Patil    schedule 13.08.2019


Ответы (3)


Всякий раз, когда я хочу соединить NSE rlang с функциями, которые явно не поддерживают его, я считаю, что разделение процедуры на эти 2 шага (по крайней мере, концептуально) всегда полезно:

  • Создайте окончательное выражение, которое я хотел бы использовать, используя функции 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 создает выражение и, очевидно, поддерживает NSE rlang, а eval_tidy просто вычисляет выражение.

Да, и кстати, если ezANOVA (или любая другая функция, с которой вы хотите использовать NSE) поддерживает строки вместо выражений в качестве входных данных, вам понадобится что-то вроде rlang::as_string(rlang::enexpr(param)), сначала захватывающее выражение того, что написал пользователь. как param, и затем с помощью as_string преобразовать это выражение.

person Alexis    schedule 13.08.2019
comment
Спасибо! Знаете ли вы, почему это дает мне следующее предупреждение: Использование as.character() в quosure устарело, начиная с rlang 0.3.0. Вместо этого используйте as_label() или as_name().? Я бы предпочел, чтобы такого предупреждения не было, так как я планирую использовать этот код в пакете, и предупреждения будут видны пользователю. - person Indrajeet Patil; 13.08.2019
comment
@IndrajeetPatil О, я тоже видел это предупреждение, я думал, что избежал его, но оно появляется только один раз за сеанс. Это потому, что {{ создает квазуры, а ezANOVA, по-видимому, преобразует некоторые входные данные в символы. Я думаю, что если вы используете что-то вроде !!rlang::ensym(y) вместо {{, будут только выражения, избегая предупреждения. - person Alexis; 13.08.2019

Это можно исправить с помощью

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
person akrun    schedule 13.08.2019

Это отличное приложение для using_bang из пакета тегов от @moody_mudskipper.

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
  )
}
person Artem Sokolov    schedule 13.08.2019