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