Использование цитат внутри мутирования: альтернатива мутированию _ (. Dots = ...) - PullRequest
6 голосов
/ 08 июля 2019

Я хочу применить разные функции к одному и тому же столбцу в таблице.Эти функции хранятся в символьной строке.Раньше я делал это с mutate_ и аргументом .dots следующим образом:

library(dplyr)

myfuns <- c(f1 = "a^2", f2 = "exp(a)", f3 = "sqrt(a)")
tibble(a = 1:3) %>% 
  mutate_(.dots = myfuns)

Этот подход все еще работает нормально, но mutate_ не рекомендуется.Я пытался добиться того же результата с mutate и пакетом rlang, но не очень далеко.

В моем реальном примере myfuns содержит около 200 функций, поэтому вводить их по очереди нельзя.

Заранее спасибо.

Ответы [ 6 ]

4 голосов
/ 08 июля 2019

Конвертируйте ваши строки в выражения

myexprs <- purrr::map( myfuns, rlang::parse_expr )

затем передайте эти выражения обычному mutate, используя квазиквотация :

tibble(a = 1:3) %>% mutate( !!!myexprs )
# # A tibble: 3 x 4
#       a    f1    f2    f3
#   <int> <dbl> <dbl> <dbl>
# 1     1     1  2.72  1   
# 2     2     4  7.39  1.41
# 3     3     9 20.1   1.73

Обратите внимание, что это также будет работать со строками / выражениями, включающими несколько столбцов.

4 голосов
/ 08 июля 2019

Для простых уравнений, которые принимают один вход, достаточно указать саму функцию, например,

iris %>% mutate_at(vars(-Species), sqrt)

Или, при использовании уравнения, а не простой функции, по формуле:

iris %>% mutate_at(vars(-Species), ~ . ^ 2)

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

area = quo(Sepal.Length * Sepal.Width)
iris %>% mutate(Sepal.Area = !! area)

Здесь quo создает «quosure» -т. е. представление вашего уравнения в кавычках, такое же, как вы используете строки, за исключением того, что, в отличие от строк, оно правильно определено, непосредственно используется dplyr и концептуально чище: оно подобно любому другому выражению R, за исключением того, что еще не вычислено.Разница заключается в следующем:

  • 1 + 2 - это выражение со значением 3.
  • quo(1 + 2) - это неоцененное выражение со значением 1 + 2, которое оцениваетдо 3, но это должно быть явно оценено.Так как же мы оценили неоцененное выражение?Ну…:

Затем !! (произносится «bang bang») снимает кавычки ранее цитируемое выражение, т.е. оценивает его - внутриконтекст mutate.Это важно, потому что Sepal.Length и Sepal.Width известны только внутри вызова mutate, а не за его пределами.


Во всех вышеупомянутых случаях выражения могут быть внутри списка,тоже.Единственное отличие состоит в том, что для списков нужно использовать !!! вместо !!:

funs = list(
    Sepal.Area = quo(Sepal.Length * Sepal.Width),
    Sepal.Ratio = quo(Sepal.Length / Sepal.Width)
)

iris %>% mutate(!!! funs)

Операция !!! известна как «unquote-splice».Идея состоит в том, что он «склеивает» элементы списка своих аргументов в родительский вызов.То есть кажется, что он изменяет вызов так, как если бы он содержал дословно элементы списка в качестве аргументов (хотя это работает только в функциях, таких как mutate, которые его поддерживают).

4 голосов
/ 08 июля 2019

У вас есть только один столбец, поэтому оба подхода ниже приведут вас к одинаковому результату.

Вам нужно только изменить список своих функций.

library(dplyr)

myfuns <- c(f1 = ~.^2, f2 = ~exp(.), f3 = ~sqrt(.))

tibble(a = 1:3) %>% mutate_at(vars(a), myfuns)

tibble(a = 1:3) %>% mutate_all(myfuns)


# # A tibble: 3 x 4
#       a    f1    f2    f3
#   <int> <dbl> <dbl> <dbl>
# 1     1     1  2.72  1   
# 2     2     4  7.39  1.41
# 3     3     9 20.1   1.73
3 голосов
/ 08 июля 2019

Базовая альтернатива:

myfuns <- c(f1 = "a^2", f2 = "exp(a)", f3 = "sqrt(a)")
df <- data.frame(a = 1:3)
df[names(myfuns)] <- lapply(myfuns , function(x) eval(parse(text= x), envir = df))
df
#>   a f1        f2       f3
#> 1 1  1  2.718282 1.000000
#> 2 2  4  7.389056 1.414214
#> 3 3  9 20.085537 1.732051

Создано в 2019-07-08 пакетом представ. (v0.3.0)

1 голос
/ 08 июля 2019

Вы также можете попробовать purrr подход

# define the functions
f1 <- function(a) a^2
f2 <- function(a, b) a + b
f3 <- function(b) sqrt(b)

# put all functions in one list
tibble(funs=list(f1, f2, f3)) %>%
  # give each function a name 
  mutate(fun_id=paste0("f", row_number())) %>% 
  # add to each row/function the matching column profile
  # first extract the column names you specified in each function 
  #mutate(columns=funs %>% 
  #         toString() %>% 
  #         str_extract_all(., "function \\(.*?\\)", simplify = T) %>% 
  #         str_extract_all(., "(?<=\\().+?(?=\\))", simplify = T) %>%
  #         gsub(" ", "", .) %>% 
  #         str_split(., ",")) %>%
  # with the help of Konrad we can use fn_fmls_names
  mutate(columns=map(funs, ~ rlang::fn_fmls_names(.)))  %>% 
  # select the columns and add to our tibble/data.frame  
  mutate(params=map(columns, ~select(df, .))) %>% 
  # invoke the functions
  mutate(results = invoke_map(.f = funs, .x = params)) %>% 
  # transform  to desired output
  unnest(results) %>% 
  group_by(fun_id) %>% 
  mutate(n=row_number()) %>% 
  spread(fun_id, results) %>% 
  left_join(mutate(df, n=row_number()), .) %>% 
  select(-n)
Joining, by = "n"
# A tibble: 5 x 5
      a     b    f1    f2    f3
  <dbl> <dbl> <dbl> <dbl> <dbl>
1     2     1     4     3  1   
2     4     1    16     5  1   
3     5     2    25     7  1.41
4     7     2    49     9  1.41
5     8     2    64    10  1.41

некоторые данные

df <- data_frame(
  a = c(2, 4, 5, 7, 8),
  b = c(1, 1, 2, 2, 2))
1 голос
/ 08 июля 2019

В одну сторону, используя parse_expr из rlang

library(tidyverse)
library(rlang)

tibble(a = 1:3) %>% 
   mutate(ans =  map(myfuns, ~eval(parse_expr(.)))) %>%
   #OR mutate(ans =  map(myfuns, ~eval(parse(text  = .)))) %>%
   unnest() %>%
   group_by(a) %>%
   mutate(temp = row_number()) %>%
   spread(a, ans) %>%
   select(-temp) %>%
   rename_all(~names(myfuns))

# A tibble: 3 x 3
#    f1    f2    f3
#  <dbl> <dbl> <dbl>
#1     1  2.72  1   
#2     4  7.39  1.41
#3     9  20.1  1.73
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...