Динамически создавать вызовы функций с различными аргументами, используя dplyr и NSE - PullRequest
0 голосов
/ 29 марта 2020

Я хочу иметь возможность динамически создавать вызовы функций с различными группирующими переменными / аргументами, используя dplyr. Количество вызовов функций может быть довольно большим, что означает, что примеры в программировании с виньеткой dplyr не практичны. В идеале я хочу иметь возможность заранее создать объект (например, список), в котором хранятся аргументы / переменные, которые будут переданы при каждом вызове функции. Ниже приведен пример набора данных, в котором мы хотим применить некоторые функции суммирования, основанные на изменении переменных группировки.

set.seed(1)
df <- data.frame(values = sample(x = 1:10, size = 10),
                 grouping_var1 = sample(x = letters[1:2], size = 10, replace = TRUE),
                 grouping_var2 = sample(x = letters[24:26], size = 10, replace = TRUE),
                 grouping_var3 = sample(x = LETTERS[1:2], size = 10, replace = TRUE))

> df
   values grouping_var1 grouping_var2 grouping_var3
1       9             a             x             B
2       4             a             z             B
3       7             a             x             A
4       1             a             x             B
5       2             a             x             A
6       5             b             x             A
7       3             b             y             B
8      10             b             x             A
9       6             b             x             B
10      8             a             y             B

Следуя программированию с виньеткой dplyr , мы могли бы найти решение, подобное этому:

f <- function(df, ...){
  group_var <- enquos(...)

  df %>%
    group_by(!!! group_var) %>%
    summarise_at(.vars = "values", .funs = sum) %>%
    print(n = 10)
}

> f(df, grouping_var1)
# A tibble: 2 x 2
  grouping_var1 values
  <fct>          <int>
1 a                 31
2 b                 24

> f(df, grouping_var1, grouping_var2)
# A tibble: 5 x 3
# Groups:   grouping_var1 [2]
  grouping_var1 grouping_var2 values
  <fct>         <fct>          <int>
1 a             x                 19
2 a             y                  8
3 a             z                  4
4 b             x                 21
5 b             y                  3

Приведенный выше пример непрактичен и негибок, если я хочу построить большое количество звонков. Другое ограничение заключается в том, что другую информацию, которую я могу включить sh, нельзя легко передать вместе или в дополнение к группирующим переменным.

Предположим, у меня есть список, содержащий переменные группировки, которые я хочу передать в каждом вызове функции. Предположим также, что для каждого из этих элементов списка есть отдельное поле с идентификатором, описывающим группировку, которая была выполнена. Ниже приведен пример:

list(group_vars = list(c("grouping_var1"),
                       c("grouping_var1", "grouping_var2"),
                       c("grouping_var1", "grouping_var3")),
     group_ids = list("var_1",
                      c("var_1_2"),
                      c("var_1_3")))

Как динамически передать эти списки аргументов / переменных и идентификаторов в вызовы функций и успешно ли они оцениваться с помощью dplyr? Допустим, я хочу создать столбец в результирующем фрейме данных, который помимо суммированных данных также содержит group_ids. Например, если мои group_vars были c("grouping_var1", "grouping_var2"), а group_ids был "var_1_2" для специфического c вызова функции, я бы ожидал вывод:

# A tibble: 5 x 4
# Groups:   grouping_var1 [2]
  grouping_var1 grouping_var2 values group_ids
  <fct>         <fct>          <int> <chr>    
1 a             x                 19 var_1_2  
2 a             y                  8 var_1_2  
3 a             z                  4 var_1_2  
4 b             x                 21 var_1_2  
5 b             y                  3 var_1_2 

Я надеюсь увидеть решение реализация этого без с использованием устаревших group_by_ функций, которые принимают строки.

В заключение я чувствую, что довольно обескураживает, что программирование с использованием dplyr в функциях с использованием NSE имеет такой барьер для входа. Каждый раз, когда я зацикливаюсь на чем-то простом, поиск решения занимает часы.

Ответы [ 2 ]

1 голос
/ 30 марта 2020

Я не уверен, что такое «стандартный» подход Tidyverse, так как у меня никогда не было ощущения, «правильно ли я делаю», когда я пытаюсь написать обобщенные функции Tidyverse для моих типичных рабочих процессов, но вот другой подход. *

Во-первых, мы можем генерировать список комбинаций группирующих столбцов, а не жестко их кодировать. В этом случае список включает все возможные комбинации из 1, 2 или 3 группирующих столбцов, но их можно при необходимости сократить.

library(tidyverse)

# Generate a list of combinations of grouping variables.
groups.list = map(1:3, ~combn(names(df)[map_lgl(df, ~!is.numeric(.))], .x, simplify=FALSE)) %>% 
  flatten

Ниже приведена функция сводки, которая использует group_by_at, которая может принимать строки, поэтому нет необходимости в нестандартной оценке. Кроме того, мы получаем значения group.ids от самого group_vars, поэтому нам не нужен отдельный параметр или аргумент (хотя это может потребоваться настроить в зависимости от того, что вы ожидаете от имен столбцов группировки).

# Summarise for each combination of groups
# Generate group.ids from group_vars itself
f2 <- function(data, group_vars) {

  data %>%
    group_by_at(group_vars) %>%
    summarise(values=sum(values)) %>% 
    mutate(group.ids=paste0("var_", paste(str_extract(group_vars, "[0-9]"), collapse="_")))

  }

Теперь мы можем запустить функцию запуска для каждого элемента group.list

map(groups.list, ~f2(df, .x))
[[1]]
# A tibble: 2 x 3
  grouping_var1 values group.ids
  <fct>          <int> <chr>    
1 a                 31 var_1    
2 b                 24 var_1    

[[2]]
# A tibble: 3 x 3
  grouping_var2 values group.ids
  <fct>          <int> <chr>    
1 x                 40 var_2    
2 y                 11 var_2    
3 z                  4 var_2    

[[3]]
# A tibble: 2 x 3
  grouping_var3 values group.ids
  <fct>          <int> <chr>    
1 A                 24 var_3    
2 B                 31 var_3    

[[4]]
# A tibble: 5 x 4
# Groups:   grouping_var1 [2]
  grouping_var1 grouping_var2 values group.ids
  <fct>         <fct>          <int> <chr>    
1 a             x                 19 var_1_2  
2 a             y                  8 var_1_2  
3 a             z                  4 var_1_2  
4 b             x                 21 var_1_2  
5 b             y                  3 var_1_2  

[[5]]
# A tibble: 4 x 4
# Groups:   grouping_var1 [2]
  grouping_var1 grouping_var3 values group.ids
  <fct>         <fct>          <int> <chr>    
1 a             A                  9 var_1_3  
2 a             B                 22 var_1_3  
3 b             A                 15 var_1_3  
4 b             B                  9 var_1_3  

[[6]]
# A tibble: 4 x 4
# Groups:   grouping_var2 [3]
  grouping_var2 grouping_var3 values group.ids
  <fct>         <fct>          <int> <chr>    
1 x             A                 24 var_2_3  
2 x             B                 16 var_2_3  
3 y             B                 11 var_2_3  
4 z             B                  4 var_2_3  

[[7]]
# A tibble: 7 x 5
# Groups:   grouping_var1, grouping_var2 [5]
  grouping_var1 grouping_var2 grouping_var3 values group.ids
  <fct>         <fct>         <fct>          <int> <chr>    
1 a             x             A                  9 var_1_2_3
2 a             x             B                 10 var_1_2_3
3 a             y             B                  8 var_1_2_3
4 a             z             B                  4 var_1_2_3
5 b             x             A                 15 var_1_2_3
6 b             x             B                  6 var_1_2_3
7 b             y             B                  3 var_1_2_3

Или, если вы хотите объединить все из результатов, вы могли бы сделать что-то вроде этого:

map(groups.list, ~f2(df, .x)) %>% 
  bind_rows() %>% 
  mutate_if(is.factor, fct_explicit_na, na_level="All") %>% 
  select(group.ids, matches("grouping"), values)
   group.ids grouping_var1 grouping_var2 grouping_var3 values
   <chr>     <fct>         <fct>         <fct>          <int>
 1 var_1     a             All           All               31
 2 var_1     b             All           All               24
 3 var_2     All           x             All               40
 4 var_2     All           y             All               11
 5 var_2     All           z             All                4
 6 var_3     All           All           A                 24
 7 var_3     All           All           B                 31
 8 var_1_2   a             x             All               19
 9 var_1_2   a             y             All                8
10 var_1_2   a             z             All                4
11 var_1_2   b             x             All               21
12 var_1_2   b             y             All                3
13 var_1_3   a             All           A                  9
14 var_1_3   a             All           B                 22
15 var_1_3   b             All           A                 15
16 var_1_3   b             All           B                  9
17 var_2_3   All           x             A                 24
18 var_2_3   All           x             B                 16
19 var_2_3   All           y             B                 11
20 var_2_3   All           z             B                  4
21 var_1_2_3 a             x             A                  9
22 var_1_2_3 a             x             B                 10
23 var_1_2_3 a             y             B                  8
24 var_1_2_3 a             z             B                  4
25 var_1_2_3 b             x             A                 15
26 var_1_2_3 b             x             B                  6
27 var_1_2_3 b             y             B                  3
  • Этот вопрос был добавлен в RStudio Community и я ' Мы также добавили этот ответ.
0 голосов
/ 29 марта 2020

Одним из решений при передаче списков строк, которые мы хотим использовать в качестве группирующих переменных, является использование rlang::syms().

f <- function(df, group_var){
  # Allows us to pass dplyr variables as strings in a list
  my_group_vars <- syms(group_var$group_var) 

  df %>%
    group_by(!!! my_group_vars) %>%
    summarise_at(.vars = "values", .funs = sum) %>%
    mutate(group_ids = group_var$group_ids)
}

params_list <- list(
  list(group_var = c("grouping_var1"), group_ids = "var_1"),
  list(group_var = c("grouping_var1", "grouping_var2"), group_ids = "var_1_2"),
  list(group_var = c("grouping_var1", "grouping_var3"), group_ids = "var_1_3")
  )

lapply(params_list, f, df = df)

[[1]]
# A tibble: 2 x 3
  grouping_var1 values group_ids
  <fct>          <int> <chr>    
1 a                 31 var_1    
2 b                 24 var_1    

[[2]]
# A tibble: 5 x 4
# Groups:   grouping_var1 [2]
  grouping_var1 grouping_var2 values group_ids
  <fct>         <fct>          <int> <chr>    
1 a             x                 19 var_1_2  
2 a             y                  8 var_1_2  
3 a             z                  4 var_1_2  
4 b             x                 21 var_1_2  
5 b             y                  3 var_1_2  

[[3]]
# A tibble: 4 x 4
# Groups:   grouping_var1 [2]
  grouping_var1 grouping_var3 values group_ids
  <fct>         <fct>          <int> <chr>    
1 a             A                  9 var_1_3  
2 a             B                 22 var_1_3  
3 b             A                 15 var_1_3  
4 b             B                  9 var_1_3 
...