dplyr мутирует новые динамические переменные с помощью case_when

Мне известны подобные вопросы здесь и здесь, но я не смог найти правильного решения для моей конкретной ситуации. Некоторые из того, что я нахожу, это решения, которые используют mutate_и т. д. но я понимаю, что они устарели. Я новичок в динамическом использовании dplyr.

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

df <- data.frame(alpha.num = c(1, 3, 5, 7),
             alpha.char = c("a", "c", "e", "g"),
             beta.num = c(2, 4, 6, 8),
             beta.char = c("b", "d", "f", "h"),
             which.to.use = c("alpha", "alpha", "beta", "beta"))

Я хочу создать новые переменные с префиксом "выбран". которые являются копиями столбцов "alpha" или "beta", в зависимости от того, какой из них назван для этой строки в столбце which.to.use. Желаемый результат будет:

desired.df <- data.frame(alpha.num = c(1, 3, 5, 7),
                     alpha.char = c("a", "c", "e", "g"),
                     beta.num = c(2, 4, 6, 8),
                     beta.char = c("b", "d", "f", "h"),
                     which.to.use = c("alpha", "alpha", "beta", "beta"),
                     chosen.num = c(1, 3, 6, 8),
                     chosen.char = c("a", "c", "f", "h"))

Моя неудачная попытка:

varnames <- c("num", "char")
df %<>%
  mutate(as.name(paste0("chosen.", varnames)) := case_when(
    which.to.use == "alpha" ~ paste0("alpha.", varnames),
    which.to.use == "beta" ~ pasteo("beta.", varnames)
  ))

Я бы предпочел чистое решение dplyr, и еще лучше было бы решение, которое могло бы быть включено в более длинную трубу, модифицирующую df (то есть не нужно останавливаться, чтобы создавать "varnames"). Спасибо за вашу помощь.

4 ответа

Решение

Это nest()/map() стратегия, которая должна быть довольно быстрой. Это остается в tidyverse, но не входит в rlang земельные участки.

library(tidyverse)

df %>% 
    nest(-which.to.use) %>%
    mutate(new_data = map2(data, which.to.use,
                       ~ select(..1, matches(..2)) %>%
                           rename_all(funs(gsub(".*\\.", "choosen.", .) )))) %>%
    unnest()

  which.to.use alpha.num alpha.char beta.num beta.char choosen.num choosen.char
1        alpha         1          a        2         b           1            a
2        alpha         3          c        4         d           3            c
3         beta         5          e        6         f           6            f
4         beta         7          g        8         h           8            h

Он захватывает все столбцы, а не только num а также char, которые не which.to.use, Но похоже, что вы (я) хотели бы IRL. Вы можете добавить select(matches('(var1|var2|etc')) линия, прежде чем позвонить nest() если вы хотите тянуть только конкретные переменные.

РЕДАКТИРОВАТЬ: мое оригинальное предложение об использовании select() отбрасывание ненужных столбцов приведет к выполнению join чтобы вернуть их позже. Если вместо этого вы настраиваете nest параметры, вы можете получить это только на определенных столбцах.

Я добавил новый bool столбцы здесь, но они будут игнорироваться для выбора "выбранный":

new_df <- data.frame(alpha.num = c(1, 3, 5, 7),
                 alpha.char = c("a", "c", "e", "g"),
                 alpha.bool = FALSE,
                 beta.num = c(2, 4, 6, 8),
                 beta.char = c("b", "d", "f", "h"),
                 beta.bool = TRUE,
                 which.to.use = c("alpha", "alpha", "beta", "beta"),
                 stringsAsFactors = FALSE)

new_df %>% 
    nest(matches("num|char")) %>% # only columns that match this pattern get nested, allows you to save others
    mutate(new_data = map2(data, which.to.use,
                           ~ select(..1, matches(..2)) %>%
                               rename_all(funs(gsub(".*\\.", "choosen.", .) )))) %>%
    unnest()

  alpha.bool beta.bool which.to.use alpha.num alpha.char beta.num beta.char choosen.num choosen.char
1      FALSE      TRUE        alpha         1          a        2         b           1            a
2      FALSE      TRUE        alpha         3          c        4         d           3            c
3      FALSE      TRUE         beta         5          e        6         f           6            f
4      FALSE      TRUE         beta         7          g        8         h           8            h

Веселиться rlang прочее purrr:

library(rlang)
library(purrr)
library(dplyr)

df <- data.frame(alpha.num = c(1, 3, 5, 7),
                 alpha.char = c("a", "c", "e", "g"),
                 beta.num = c(2, 4, 6, 8),
                 beta.char = c("b", "d", "f", "h"),
                 which.to.use = c("alpha", "alpha", "beta", "beta"),
                 stringsAsFactors = F)

c("num", "char") %>% 
    map(~ mutate(df, !!sym(paste0("chosen.", .x)) := 
      case_when(
          which.to.use == "alpha" ~ !!sym(paste0("alpha.", .x)),
          which.to.use == "beta" ~ !!sym(paste0("beta.", .x))
                ))) %>% 
    reduce(full_join)

Результат:

  alpha.num alpha.char beta.num beta.char which.to.use chosen.num chosen.char
1         1          a        2         b        alpha          1           a
2         3          c        4         d        alpha          3           c
3         5          e        6         f         beta          6           f
4         7          g        8         h         beta          8           h

Без reduce(full_join):

c("num", "char") %>% 
  map_dfc(~ mutate(df, !!sym(paste0("chosen.", .x)) := 
                 case_when(
                   which.to.use == "alpha" ~ !!sym(paste0("alpha.", .x)),
                   which.to.use == "beta" ~ !!sym(paste0("beta.", .x))
                 ))) %>% 
  select(-ends_with("1"))



alpha.num alpha.char beta.num beta.char which.to.use chosen.num chosen.char
1         1          a        2         b        alpha          1           a
2         3          c        4         d        alpha          3           c
3         5          e        6         f         beta          6           f
4         7          g        8         h         beta          8           h

Объяснение:
(Примечание: я не полностью или даже не получаю rlang, Может быть, другие могут дать лучшее объяснение;).)

С помощью paste0 сам по себе создает строку, когда нам нужно голое имя для mutate знать, что это относится к имени переменной.

Если мы завернем paste0 в sym это оценивает голое имя:

> x <- varrnames[1]
> sym(paste0("alpha.", x))
  alpha.num

Но mutate не знает, чтобы оценить и вместо этого читать его как символ:

> typeof(sym(paste0("alpha.", x)))
[1] "symbol"

"Взрыв" !! Оператор оценивает sym функция. Для сравнения:

> expr(mutate(df, var = sym(paste0("alpha.", x))))
mutate(df, var = sym(paste0("alpha.", x)))

> expr(mutate(df, var = !!sym(paste0("alpha.", x))))
mutate(df, var = alpha.num)

Так с !!sym мы можем использовать paste для динамического вызова имен переменных с помощью dplyr.

Базовый R подход с использованием apply с margin = 1 где мы выбираем столбцы для каждой строки на основе значения в which.to.use столбец и получить значение из соответствующего столбца для строки.

df[c("chosen.num", "chosen.char")] <- 
          t(apply(df, 1, function(x) x[grepl(x["which.to.use"], names(df))]))

df
#  alpha.num alpha.char beta.num beta.char which.to.use chosen.num chosen.char
#1         1          a        2         b        alpha          1           a
#2         3          c        4         d        alpha          3           c
#3         5          e        6         f         beta          6           f
#4         7          g        8         h         beta          8           h

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

df %>% 
  rownames_to_column() %>% 
  gather(k,v,-which.to.use,-rowname) %>% 
  separate(k,into = c("k1", "k2"), sep="[.]") %>% 
  filter(which.to.use == k1) %>% 
  mutate(k1="chosen") %>% 
  unite(k, k1, k2,sep=".") %>% 
  spread(k,v) %>%
  select(.,chosen.num, chosen.char) %>% 
  bind_cols(df, .)
    alpha.num alpha.char beta.num beta.char which.to.use chosen.num chosen.char
 1         1          a        2         b        alpha          1           a
 2         3          c        4         d        alpha          3           c
 3         5          e        6         f         beta          6           f
 4         7          g        8         h         beta          8           h
Другие вопросы по тегам