Использование пользовательской функции dplyr quosure с mutate_at

Я пытаюсь создать вспомогательную функцию, которая извлекает цифры из столбца, указанного в аргументе. Я могу использовать свою функцию внутри mutate (и повторять ее для всех интересующих столбцов), но, похоже, она не работает внутри mutate_at.

Вот пример того, как выглядят мои данные:

> set.seed(20190928)
> evalYr <- 2018
> n <- 5
> (df <- data.frame(
+     AY = sample(2016:2019, n, replace = T),
+     Pay00 = rgamma(n, 2, 1/1000),
+     Pay01 = rgamma(n, 2, 1/1000),
+     Pay02 = rgamma(n, 2, 1/1000),
+     Pay03 = rgamma(n, 2, 1/1000)
+ ))
    AY     Pay00     Pay01     Pay02     Pay03
1 2018 2520.3772 2338.9490  919.8245  629.1657
2 2016  259.7804 1543.4450  661.6488 2382.7916
3 2018 2446.3075  312.5143 2297.9717  942.5627
4 2017 1386.6288 4179.0352 2370.2669 1846.5838
5 2018  541.8261 2104.4589 2622.1758 2606.0694

Итак, я создал (используя синтаксис dplyr) этот помощник для изменения каждого PayXX столбца, который у меня есть:

# Helper function to get the number inside column `PayXX` name
f1 <- function(pmt) enquo(pmt) %>% quo_name() %>% str_extract('(\\d)+') %>% as.numeric()

Эта функция отлично работает с dplyr::mutate:

> df %>% mutate(Pay00_numcol = f1(Pay00),
+               Pay01_numcol = f1(Pay01),
+               Pay02_numcol = f1(Pay02),
+               Pay03_numcol = f1(Pay03))
    AY     Pay00     Pay01     Pay02     Pay03 Pay00_numcol Pay01_numcol Pay02_numcol Pay03_numcol
1 2018 2520.3772 2338.9490  919.8245  629.1657            0            1            2            3
2 2016  259.7804 1543.4450  661.6488 2382.7916            0            1            2            3
3 2018 2446.3075  312.5143 2297.9717  942.5627            0            1            2            3
4 2017 1386.6288 4179.0352 2370.2669 1846.5838            0            1            2            3
5 2018  541.8261 2104.4589 2622.1758 2606.0694            0            1            2            3

Но когда я пытаюсь использовать ту же функцию внутри mutate_at, она возвращает NA:

> df %>% mutate_at(vars(starts_with('Pay')), list(numcol = ~f1(.)))
    AY     Pay00     Pay01     Pay02     Pay03 Pay00_numcol Pay01_numcol Pay02_numcol Pay03_numcol
1 2018 2520.3772 2338.9490  919.8245  629.1657           NA           NA           NA           NA
2 2016  259.7804 1543.4450  661.6488 2382.7916           NA           NA           NA           NA
3 2018 2446.3075  312.5143 2297.9717  942.5627           NA           NA           NA           NA
4 2017 1386.6288 4179.0352 2370.2669 1846.5838           NA           NA           NA           NA
5 2018  541.8261 2104.4589 2622.1758 2606.0694           NA           NA           NA           NA

У кого-нибудь когда-нибудь была подобная проблема? Как мне поступить с функцией mutate_at в этом случае?

Спасибо,

Воспроизводимый пример

library(tidyverse)
library(stringr)
set.seed(20190928)
evalYr <- 2018
n <- 5
(df <- data.frame(
    AY = sample(2016:2019, n, replace = T),
    Pay00 = rgamma(n, 2, 1/1000),
    Pay01 = rgamma(n, 2, 1/1000),
    Pay02 = rgamma(n, 2, 1/1000),
    Pay03 = rgamma(n, 2, 1/1000)
))

# Helper function to get the number inside column `PayXX` name
f1 <- function(pmt) enquo(pmt) %>% quo_name() %>% str_extract('(\\d)+') %>% as.numeric()

# Working
df %>% mutate(Pay00_numcol = f1(Pay00),
              Pay01_numcol = f1(Pay01),
              Pay02_numcol = f1(Pay02),
              Pay03_numcol = f1(Pay03))

# Not working
df %>% mutate_at(vars(starts_with('Pay')), list(numcol = ~f1(.)))

person Gabriel Crépeault    schedule 27.09.2019    source источник
comment
Не знаю, сыворотка, но работает следующий код: df %>% mutate_at(vars(starts_with('Pay')), list(numcol = f1)). Почему-то этот синтаксис ~f1(.) не работает.   -  person yusuzech    schedule 27.09.2019
comment
FYI stringr - это один из пакетов, загружаемых tidyverse, поэтому вам не нужно загружать его отдельно   -  person camille    schedule 27.09.2019


Ответы (1)


Первый способ, о котором я подумал, это то, что это может быть проще с изменением формы данных. Однако для получения 1) столбца «Pay00», «Pay01» и т.д .; 2) извлеките числа; 3) манипулировать так, чтобы можно было использовать tidyr::spread, чтобы вернуться к широкой форме; и 4) распространить и удалить бит "_value", который я добавил.

Я считаю, что есть более удобный способ сделать это с помощью последней версии tidyr, поскольку новая функция pivot_wider должна иметь возможность принимать более одного столбца как value. Я вообще с этим не связывался, но, может быть, кто-нибудь еще сможет это написать.

library(tidyverse)

df %>%
  rowid_to_column() %>%
  gather(key, value, -AY, -rowid) %>%
  mutate(numcol = as.numeric(str_extract(key, "\\d+$"))) %>%
  gather(key = coltype, value, value, numcol) %>%
  unite(key, key, coltype) %>%
  spread(key, value) %>%
  select(AY, ends_with("value"), ends_with("numcol")) %>%
  rename_all(str_remove, "_value")
#>     AY     Pay00     Pay01     Pay02     Pay03 Pay00_numcol Pay01_numcol
#> 1 2018 2520.3772 2338.9490  919.8245  629.1657            0            1
#> 2 2016  259.7804 1543.4450  661.6488 2382.7916            0            1
#> 3 2018 2446.3075  312.5143 2297.9717  942.5627            0            1
#> 4 2017 1386.6288 4179.0352 2370.2669 1846.5838            0            1
#> 5 2018  541.8261 2104.4589 2622.1758 2606.0694            0            1
#>   Pay02_numcol Pay03_numcol
#> 1            2            3
#> 2            2            3
#> 3            2            3
#> 4            2            3
#> 5            2            3

Или, если вы хотите придерживаться подхода tidyeval: получите имена столбцов в виде запросов, для которых вы вызываете свою функцию. Только будьте осторожны, если вы используете обозначение list(numcol = ~f1(.)), все эти вопросы будут отображаться как .

f1 <- function(pmt) {
  str_extract(rlang::as_name(enquo(pmt)), "\\d+$") %>%
    as.numeric()
}

df %>%
  mutate_at(vars(starts_with("Pay")), list(numcol = f1))
# same output as prev
person camille    schedule 27.09.2019
comment
Спасибо большое за вашу помощь! Моя проблема немного сложнее (мне нужно выполнить конкретный расчет, который зависит от имени столбца, например Pay01, Pay02 и т. Д. Так что споры по сбору / распространению - это определенно то, что я искал. Однако, если бы я хотел чтобы придерживаться моего первоначального подхода, и я хотел передать несколько аргументов для функции внутри .funs (в моем реальном случае f1 имеет 4 аргумента), как бы я справился с этим без использования оператора ~? Спасибо за вашу помощь, один раз опять таки. - person Gabriel Crépeault; 28.09.2019
comment
Слишком много сложностей могло быть выше моих навыков. Но вы можете передавать дополнительные аргументы внутри вызова mutate_at, но вне вызова list - person camille; 28.09.2019