dplyr: mutate_at coalesce: динамические имена столбцов

#r #dplyr #rlang

#r #dplyr #rlang

Вопрос:

Некоторое время я пытался объединить mutate_at с coalesce в случае, когда имена столбцов генерируются динамически.

В моем примере всего пять столбцов, но в реальных данных их намного больше (и не все столбцы следует включать в coalesce шаг).

Пример DF:

 data_example <- data.frame(
  aa = c(1, NA, NA),
  bb = c(NA, NA, 2),
  cc = c(6, 7, 8),
  aa_extra = c(2, 2, NA),
  bb_extra = c(1, 2, 3)
)
  

Ожидаемый результат:

   aa bb cc aa_extra bb_extra
1  1  1  6        2        1
2  2  2  7        2        2
3 NA  2  8       NA        3

  

вывод в виде structure :

 structure(list(aa = c(1, 2, NA), bb = c(1, 2, 2), cc = c(6, 7, 
8), aa_extra = c(2, 2, NA), bb_extra = c(1, 2, 3)), class = "data.frame", row.names = c(NA, 
-3L))
  

Я пробовал что-то подобное, но безуспешно («Только строки могут быть преобразованы в символы»). Я хотел бы избежать создания дополнительных переменных, просто включить все в mutate_at выражение, поскольку это часть более длинного dplyr «потока».

 data_example %>%
  dplyr::mutate_at(
    gsub("_extra", "", grep("_extra$",
                            colnames(.),
                            perl = T,
                            value = T)),
    dplyr::funs(
      dplyr::coalesce(., !!! dplyr::sym(paste0(., "_extra")))
    )
  )

  

Я также пробовал это (ошибок нет, но значения для столбца bb неверны):

 data_example %>%
  dplyr::mutate_at(
    gsub("_extra", "", grep("_extra$",
                            colnames(.),
                            perl = T,
                            value = T)),
    dplyr::funs(
      dplyr::coalesce(., !!as.name(paste0(names(.), "_extra")))
    )
  )
  

Как получить имя обрабатываемого столбца и передать его coalesce ?

Ответ №1:

Мы можем split преобразовать набор данных в list набор данных.кадры после удаления подстроки имен столбцов ( "_extra" ), затем с помощью map цикла через list , coalesce столбец, а затем bind с помощью столбцов «_extra» в исходном наборе данных

 library(tidyverse)
data_example %>% 
   split.default(str_remove(names(.), "_extra")) %>%
   map_df(~ coalesce(!!! .x)) %>%
   #or use
   # map_df(reduce, coalesce) %>%
   bind_cols(., select(data_example, ends_with("extra")))
# A tibble: 3 x 5
#     aa    bb    cc aa_extra bb_extra
#  <dbl> <dbl> <dbl>    <dbl>    <dbl>
#1     1     1     6        2        1
#2     2     2     7        2        2
#3    NA     2     8       NA        3
  

Ответ №2:

Думаю, теперь можно достичь желаемого результата, используя mutate across

 data_example %>% 
  mutate(across(c(str_subset(names(.), "_extra") %>% str_remove("_extra")) ,
                ~ coalesce( ., get(str_c(cur_column(), "_extra"))  ))) 

  aa bb cc aa_extra bb_extra
1  1  1  6        2        1
2  2  2  7        2        2
3 NA  2  8       NA        3
  

Ответ №3:

Используя data.table для melt и dcast поскольку я никогда не могу вспомнить, как spread и gather работают

 library(data.table)
library(dplyr)

data_example %>% 
  mutate(row = row_number()) %>% 
  melt('row') %>% 
  group_by(g = sub('_*$', '', variable), row) %>% 
  mutate(value = reduce(value, coalesce)) %>% 
  dcast(row ~ variable) %>% 
  select(-row)

#   aa bb cc aa_extra bb_extra
# 1  1  1  6        1        1
# 2  2  2  7        2        2
# 3 NA  2  8       NA        2