Use of switch() in R to replace vector values
Asked Answered
L

13

31

This should be pretty simple but even after checking all documentation and on-line examples I don't get it.

I'd like to use switch() to replace the values of a character vector.

A fake, extremely simple, reproducible example:

test<-c("He is", "She has", "He has", "She is")

Let's say I want to assign "1" to sentences including the verb "to be" and "2" to sentences including the verb "to have". The following DOES NOT work:

test<-switch(test,
                "He is"=1,
                "She is"=1,
                "He has"=2,
                "She has"=2)

Error message:

+ + + + Error in switch(test, `He is` = 1, `She is` = 1, `He has` = 2, `She has` = 2) : 
  EXPR must be a length 1 vector

I think EXPR is indeed a length 1 vector, so what's wrong?

I thought maybe R expected characters as replacements, but neither wrapping switch() into an "as.integer" nor the following work:

test<-switch(test,
                "He is"="1",
                "She is"="1",
                "He has"="2",
                "She has"="2")

Maybe it doesn't vectorize, and I should make a loop? Is that it? Would be disappointing, considering the strength of R is vectorization. Thanks in advance!

Lycian answered 1/7, 2015 at 9:1 Comment(3)
Vector "test" is of length 4. It cannot work. See with test <- "He is".Phenology
Are you trying to achieve thiscode <- c("He is"=1, "She is"=1, "He has"=2, "She has"=2); code[test]?Retrusion
Wow @ExperimenteR, that is elegant... I didn't know that if I select objects of a numeric vector with a names attribute using a character, R would use the names attribute to match the numbers of the numeric vector to my character vector. This is my favourite solution, but I cannot choose it as the answer because the question was how to use switch() correctly. Thumbs up thoLycian
M
14

The vectorised form of if is ifelse:

test <- ifelse(test == "He is", 1,
        ifelse(test == "She is", 1,
        ifelse(test == "He has", 2,
        2)))

or

test <- ifelse(test %in% c("He is", "She is"), 1, 2)

switch is basically a way of writing nested if-else tests. You should think of if and switch as control flow statements, not as data transformation operators. You use them to control the execution of an algorithm, eg to test for convergence or to choose which execution path to take. You wouldn't use them to directly manipulate data in most circumstances.

Milker answered 1/7, 2015 at 9:17 Comment(0)
P
32

Here is the correct way to vectorize a function, e.g. switch:

# Data vector:
test <- c("He is",
          "She has",
          "He has",
          "She is")

# Vectorized SWITCH:
foo <- Vectorize(vectorize.args = "a",
                 FUN = function(a) {
                   switch(as.character(a),
                          "He is" = 1,
                          "She is" = 1,
                          "He has" = 2,
                          2)})

# Result:
foo(a = test)

  He is She has  He has  She is 
      1       2       2       1

I hope this helps.

Poco answered 4/1, 2016 at 20:21 Comment(0)
R
24

You coud try

test_out <- sapply(seq_along(test), function(x) switch(test[x],
  "He is"=1,
  "She is"=1,
  "He has"=2,
  "She has"=2))

Or equivalently

test_out <- sapply(test, switch,
  "He is"=1,
  "She is"=1,
  "He has"=2,
  "She has"=2)
Rech answered 1/7, 2015 at 9:12 Comment(2)
This appears to be the solution, thanks, actually in an even simpler form in which "test" is the object of sapplyLycian
Sorry, another answer included a broad explanation of the notions behind the problem. But I promise I'll upvote you as soon as my Reputation allows it in a few weeks.Lycian
K
15

I found this approach the most readable:

# input
test <-c("He is", "She has", "He has", "She is", "Unknown", "She is")

# mapping
map <- c(
  "He is" = 1, 
  "She has" = 2, 
  "He has" = 2, 
  "She is" = 1)

answer <- map[test]

# output
answer
He is She has  He has  She is    <NA>  She is 
    1       2       2       1      NA       1 

If test is numeric, must convert value to character to use this.

Kreutzer answered 1/2, 2018 at 9:45 Comment(0)
M
14

The vectorised form of if is ifelse:

test <- ifelse(test == "He is", 1,
        ifelse(test == "She is", 1,
        ifelse(test == "He has", 2,
        2)))

or

test <- ifelse(test %in% c("He is", "She is"), 1, 2)

switch is basically a way of writing nested if-else tests. You should think of if and switch as control flow statements, not as data transformation operators. You use them to control the execution of an algorithm, eg to test for convergence or to choose which execution path to take. You wouldn't use them to directly manipulate data in most circumstances.

Milker answered 1/7, 2015 at 9:17 Comment(0)
X
4

While I usually prefer base R approaches, there is a package with a vectorized switch function.

library(broman)

switchv(c("horse", "fish", "cat", "bug"),
horse="fast",
cat="cute",
"what?")

Added based on comment to use OP data.

library(broman)

test<-c("He is", "She has", "He has", "She is")


test<-switchv(test,
                "He is"="1",
                "She is"="1",
                "He has"="2",
                "She has"="2")

test
Ximenes answered 11/4, 2018 at 13:48 Comment(0)
S
2

"Vectorize" is based on the "mapply" function, whereas "ifelse" is a base function which should be already vectorized. So in terms of performance "Vectorize" might be slower. It is easy to vectorize an R function with the 'apply' family, but performance is usually an issue on large volumes. Better to use base functions optimized to work with vectors.

Suint answered 15/12, 2017 at 4:55 Comment(0)
S
1

Here is a solution with recode() from car:

# Data vector:
x <- c("He is", "She has", "He has", "She is")

library("car")
recode(x, "'He is'=1; 'She is'=1; 'He has'=2; 'She has'=2") # or
recode(x, "c('He is', 'She is')=1; c('He has', 'She has')=2")
Schnauzer answered 25/10, 2018 at 13:46 Comment(1)
Just to mention here: recode is in the dplyr package, too.Ballonet
C
1

You could use a named vector and simple base subsetting methods. E.g.

test <- c("He is", "She has", "He has", "She is")

named_vec <- c(
  "He is" = 1,
  "She is" = 1,
  "He has" = 2,
  "She has" = 2
)

named_vec[test]
#>   He is She has  He has  She is 
#>       1       2       2       1

Created on 2020-04-11 by the reprex package (v0.3.0)

Couchant answered 11/4, 2020 at 5:1 Comment(1)
Simplest answer.Randell
S
1

Package kit on CRAN has a vectorised switch function written in C called vswitch. You might also be interested to know that it has a nested if function called nif and a fast ifelse function called iif. Please look at the documentation, these functions are really fast compare to base R.

Steal answered 25/5, 2020 at 7:29 Comment(0)
T
0

Just for fun:

vSwitch <- function(vExpr,...) {
  l <- list(...)
  if(names(l)[[length(l)]] != '') stop('Last item in match list must be unnamed')
  i <- 0
  recurse <- function(v) {
    i <<- i + 1
    if(names(l[i+1]) != "") {
      ifelse(v == names(l)[[i]],l[[i]], recurse(v))
    } else {
      ifelse(v == names(l)[[i]],l[[i]], l[[i+1]])
    }
  } 
  recurse(vExpr)
}
Tercel answered 5/11, 2019 at 21:4 Comment(0)
C
0

The fancy and tidy way to do it with purrr package is like that:

purrr::map_int(c("He is", "She has", "He has", "She had", "She is", NA),
    ~ purrr::when(.,
         .x %in% c("He is", "She is") ~ 1L,
         .x %in% c("He has", "She has") ~ 2L,
         ~ NA))

Here, purrr::map() iterates over the first argument and returns whatever values the second parameter returns. That second parameter is a function, where purrr allows to write it in a much less elaborate manner: rather than writing function(x) x, one can write simply ~ ., ~ .x or ~ .1 (the last working for unlimited number of variables).

Then, we've got purrr::when(), which takes a single value and functions as a series of ifelse statements. These statements take form of LHS ~ RHS. LHS should be a logical expression, it can also use the same way of referencing variables as above; RHS is the value associated with this condition. Value returned is the first that fits. When LHS is empty (as in the last line), then it's treated as else clause.

purrr::map_int() differs from the purrr::map() only in that it guarantees returning a vector of integers (there are similar functions for numerics, logicals and strings).

Chloropicrin answered 1/6, 2021 at 22:33 Comment(0)
S
0

dplyr 1.1.0 introduced case_match, which is a vectorized version of switch:

library(dplyr)

test<-c("He is", "She has", "He has", "She is", "not matched")

case_match(
  test,
  "He is" ~ 1,
  "She is" ~ 1,
  "He has" ~ 2,
  "She has" ~ 2,
  .default = 3 # return value if test not matched
)

[1] 1 2 2 1 3
Shotten answered 17/2, 2023 at 20:31 Comment(0)
B
-1

Use revalue() function from the plyr package.

library(plyr)
test<-c("He is", "She has", "He has", "She is")
test<-revalue(test,
              c("He is"=1,
                "She is"=1,
                "He has"=2,
                "She has"=2))
test

Here is the output.

[1] "1" "2" "2" "1"
Billmyre answered 30/4, 2022 at 23:10 Comment(1)
Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes.Curitiba

© 2022 - 2024 — McMap. All rights reserved.