terra_rob
terra_rob

Reputation: 13

If statements in for loop not qualifying vector in R

I wrote a function in R to attach zeros such that any number between 1 and 100 comes out as 001 (1), 010 (10), and 100 (100) but I can't figure out why the if statements aren't qualifying like I would like them to.

id <- 1:11
Attach_zero <- function(id){

    i<-1
    for(i in id){
        if(id[i] < 10){
            id[i] <- paste("00",id[i], sep = "")
        }

        if((id[i] < 100)&&(id[i]>=10)){
            id[i] <- paste("0",id[i], sep = "")
        } 
        print(id[i])
    }
}

The output is "001", "2", "3",... "010", "11"

I have no idea why the for loop is skipping middle integers.

Upvotes: 0

Views: 276

Answers (3)

llrs
llrs

Reputation: 3397

If you try your function with id = c(1:3, 6:11):

Attach_zero(id)
##[1] "001"
##[1] "2"
##[1] "3"
##[1] "8"
##[1] "9"
##[1] "010"
##[1] "11"
##Error in if (id[i] < 10) { : missing value where TRUE/FALSE needed

What here happens is that the missing values are omitted because your i values says so. The i<-1 does nothing as it is after that written with for (i in id) which in turns gives i for each loop the ith value of id instead of an index. So if your id is id <- c(1:3, 6:11) you will have unexpected results as showed. Just correcting your function to include all the elements of the id:

Attach_zero <- function(id){
    for(i in 1:length(id)){
        if(id[i] < 10){
            id[i] <- paste("00",id[i], sep = "")
        }

        if((id[i] < 100)&&(id[i]>=10)){
            id[i] <- paste("0",id[i], sep = "")
        } 
        print(id[i])
    }
}
Attach_zero(id)
##[1] "001"
##[1] "2"
##[1] "3"
##[1] "6"
##[1] "7"
##[1] "8"
##[1] "9"
##[1] "010"
##[1] "11"

Note the number 7 in this output. And using sprintf as jbaums says, including it in a function:

Attach_zero <- function(id){
 return(sprintf('%03d', id)) #You can change return for print if you want
}
Attach_zero(id)
## [1] "001" "002" "003" "006" "007" "008" "009" "010" "011"

Upvotes: 1

BiXiC
BiXiC

Reputation: 973

Try this:

id <- 1:11
Attach_zero <- function(id){
  id1 <- id
  i <- 1
  for (i in seq_along(id)) {
    if(id[i] < 10){
      id1[i] <- paste("00", id[i], sep = "")
    }
    if(id[i] < 100 & id[i] >= 10){
      id1[i] <- paste("0", id[i], sep = "")
    } 
  }
  print(id1)
}

Upvotes: 1

jbaums
jbaums

Reputation: 27388

The problem here is that you're assigning a character string (e.g. "001") to a numeric vector. When you do this, the entire id vector is converted to character (elements of a vector must be of one type).

So, after comparing 1 to 10 and assigning "001" to id[1], the next element of id is "2" (i.e. character 2). When an inequality includes a character element (e.g. "2" < 10), the numeric part is coerced to character, and alphabetic sorting rules apply. These rules mean that both "100" and "10" comes before "2", and so neither of your if conditions are met. This is the case for all numbers except 10, which according to alphabetic sorting is less than 100, and so your second if condition is met. When you get to 11, neither condition is met once again, since the "word" "11" comes after the word "100".

While there are a couple of ways to fix your function, this functionality exists in R (as mentioned in the comments), both with sprintf and formatC.

sprintf('%03d', 1:11)
formatC(1:11, flag=0, width=3)
# [1] "001" "002" "003" "004" "005" "006" "007" "008" "009" "010" "011"

For another vectorised approach, you could use nested ifelse statements:

ifelse(id < 10, paste0('00', id), ifelse(id < 100, paste0('0', id), id))

Upvotes: 1

Related Questions