melbez
melbez

Reputation: 1000

Replace several values and keep others same efficiently in R

I have a dataframe like the following:

combo_2  combo_4  combo_7  combo_9
12       23       14       17
21       32       41       71
2        3        1        7
1        2        4        1
21       23       14       71
2        32       1        7

Each column has two single-digit values and two double-digit values composed of the single-digit values in each possible order.

I am trying to determine how to replace certain values in the dataframe so that there is only one version of the double-digit value. For example, all values of 21 in the first column should be 12. All values of 32 in the second column should become 23.

I know I can do something like this using the following code:

df <- df %>%
    mutate_at(vars(combo_2, combo_4, combo_7, combo_9), function(x)
    case_when(x == 21 ~ 12, x == 32 ~ 23, x == 41 ~ 14, x == 71 ~ 17))

The problem with this is that it gives me a dataframe that contains the correct values when specified but leaves all the other values as NA. The resulting dataframe only contains values where 21, 32, 41, and 71 were. I know I could address this by specifying each value, like x == 1 ~ 1. However, I have many values and would prefer to only specify the ones that I am trying to change.

How can I replace several values in a dataframe without all the other values becoming NA? Is there a way for me to replace the values I want to replace while holding the other values the same without directly specifying those values?

Upvotes: 2

Views: 1081

Answers (3)

zx8754
zx8754

Reputation: 56269

Using mapply:

df1[] <- mapply(function(d, x1, x2){ ifelse(d == x1, x2, d) },
                d = df1, 
                x1 = c(21, 32, 41, 71),
                x2 = c(12, 23, 14, 17))

df1
#   combo_2 combo_4 combo_7 combo_9
# 1      12      23      14      17
# 2      12      23      14      17
# 3       2       3       1       7
# 4       1       2       4       1
# 5      12      23      14      17
# 6       2      23       1       7

Upvotes: 0

Andrew
Andrew

Reputation: 5138

You can use TRUE ~ x at the end of your case_when() sequence:

df %>%
  mutate_at(vars(combo_2, combo_4, combo_7, combo_9), function(x)
    case_when(x == 21 ~ 12, x == 32 ~ 23, x == 41 ~ 14, x == 71 ~ 17, TRUE ~ x))

  combo_2 combo_4 combo_7 combo_9
1      12      23      14      17
2      12      23      14      17
3       2       3       1       7
4       1       2       4       1
5      12      23      14      17
6       2      23       1       7

Another option that may be more efficient would be data.table's fcase() function.

Data:

df = read.table(header = TRUE, text = "combo_2  combo_4  combo_7  combo_9
12       23       14       17
21       32       41       71
2        3        1        7
1        2        4        1
21       23       14       71
2        32       1        7")

df[] = lapply(df, as.double) # side-note: tidyverse has become very stict about types

Upvotes: 1

tmfmnk
tmfmnk

Reputation: 40181

One dplyr and stringi option may be:

df %>%
 mutate(across(everything(), 
               ~ if_else(. %in% c(21, 32, 41, 71), as.integer(stri_reverse(.)), .)))

  combo_2 combo_4 combo_7 combo_9
1      12      23      14      17
2      12      23      14      17
3       2       3       1       7
4       1       2       4       1
5      12      23      14      17
6       2      23       1       7

Upvotes: 1

Related Questions