simoncolumbus
simoncolumbus

Reputation: 566

Conditionally mutate multiple columns in R

I have a dataframe with a factor column with j levels, as well as j vectors of length k. I would like to populate k columns in the former dataframe with values from the latter vectors, conditional on the factor.

Simplified example (three levels, three vectors, two values):

df1 <- data.frame("Factor" = rep(c("A", "B", "C"), times = 5))
vecA <- c(1, 2)
vecB <- c(2, 1)
vecC <- c(3, 3)

Here is a solution using nested ifelse statements:

library(tidyverse)
df1 %>%
  mutate(V1 = ifelse(Factor == "A", vecA[1], 
                     ifelse(Factor == "B", vecB[1], vecC[1])),
         V2 = ifelse(Factor == "A", vecA[2], 
                     ifelse(Factor == "B", vecB[2], vecC[2])))

I would like to avoid the nested ifelse statements. Ideally, I would also like to avoid mutating each column separately.

Upvotes: 3

Views: 1010

Answers (3)

akrun
akrun

Reputation: 886938

Here is a base R option

df1[c('V1', 'V2')] <- do.call(Map, c(f = c, mget(ls(pattern="^vec[A-C]$"))))
df1
#    Factor V1 V2
#1       A  1  2
#2       B  2  1
#3       C  3  3
#4       A  1  2
#5       B  2  1
#6       C  3  3
#7       A  1  2
#8       B  2  1
#9       C  3  3
#10      A  1  2
#11      B  2  1
#12      C  3  3
#13      A  1  2
#14      B  2  1
#15      C  3  3

Or with transpose from purrr

library(dplyr)
library(purrr)
mget(ls(pattern="^vec[A-C]$")) %>% 
     transpose %>% 
     setNames(c('V1', 'V2')) %>% 
     cbind(df1, .)

Upvotes: 1

jazzurro
jazzurro

Reputation: 23574

Here is one idea. In the global environment, get all objects that begin with "vec", which is done by mget(). This creates a list. For each element in the list, paste the numbers with "_" in between. Then, arrange names in the vector for the following join process. After join, split the column, values with cSplit(). I hope this approach will be applicable to your real situation.

library(tidyverse)
library(splitstackshape)

# Create a character vector.
mychr <- map_chr(.x = mget(ls(pattern = "vec")),
                 .f = function(x) {paste0(x, collapse = "_")})

# Remove "vec" in names.
names(mychr) <- sub(x = names(mychr), pattern = "vec", replacement = "")

#   A     B     C 
#"1_2" "2_1" "3_3"

# stack() creates a data frame. Use it in left_join().
# Then, split the column, values into two columns. You probably have more than
# two. So I decided to use cSplit() here.

left_join(df1, stack(mychr), by = c("Factor" = "ind")) %>%
cSplit(splitCols = "values", sep = "_", direction = "wide", type.convert = FALSE)

#    Factor values_1 values_2
# 1:      A        1        2
# 2:      B        2        1
# 3:      C        3        3
# 4:      A        1        2
# 5:      B        2        1
# 6:      C        3        3
# 7:      A        1        2
# 8:      B        2        1
# 9:      C        3        3
#10:      A        1        2
#11:      B        2        1
#12:      C        3        3
#13:      A        1        2
#14:      B        2        1
#15:      C        3        3

Upvotes: 1

YOLO
YOLO

Reputation: 21709

Here's a way to do:

# modify the vectors
l <- list('A' = vecA, 'B' = vecB, 'C' = vecC)

# create df with mapping
df2 = data.frame(t(sapply(df1$Factor, function(x) l[[x]])))
colnames(df2) <- c('V1', 'V2')

new_df = cbind(df1, df2)

   Factor V1 V2
1       A  1  2
2       B  2  1
3       C  3  3
4       A  1  2
5       B  2  1
6       C  3  3
7       A  1  2
8       B  2  1
9       C  3  3
10      A  1  2
11      B  2  1
12      C  3  3
13      A  1  2
14      B  2  1
15      C  3  3

Upvotes: 0

Related Questions