Home > Net >  Checking if an R function argument is an NA causes a problem when applied to vectors
Checking if an R function argument is an NA causes a problem when applied to vectors

Time:08-14

I created a function to turn pounds, shillings and pence into a decimal price that starts by checking if the argument is NA or an empty character string. It works perfectly with a single character string, but gives me an error if I apply it to a character vector (I am using R 4.2.1):

psp_to_decimal <- function(x){
  # Converts pounds shillings and pence to a decimal price
  psp <- strsplit(x, " ")[[1]]

  if(is.na(x) | trimws(x) == ""){
    decimal_price <- NA

  } else {
    decimal_price <- 0
    if(grepl("£", psp[1], fixed = TRUE)) {
      pounds <- gsub("£", "", psp[1])
      decimal_price <- decimal_price   as.numeric(pounds)
      psp <- psp[-1]
    }
    
    if(length(psp)>0 && grepl("s", psp[1], fixed = TRUE)) {
      shillings <- gsub("s", "", psp[1])
      decimal_price <- decimal_price   as.numeric(shillings) / 20
      psp <- psp[-1]
    }
  
    if(length(psp)>0 && grepl("d", psp[1], fixed = TRUE)) {
      pence <- gsub("d", "", psp[1])
      decimal_price <- decimal_price   as.numeric(pence) / 240
    }
  }
  writeLines(c("decimal price", decimal_price))
  return(decimal_price)
}

I create a vector to test it:

x <- c("£7 2s 10d", "£12 5s 1d", "2s 4d")

This works fine if I type

> psp_to_decimal(x[1])
[1] 7.114166

but chokes if I type

> psp_to_decimal(x)
Error in if (is.na(x) | trimws(x) == "") { : the condition has length > 1

Clearly is.na() and trimws() are apply themselves to the entire vector, and not to each item individually. How do I get psp_to_decimal_test to convert each item in x to a decimal?

Many thanks in advance

Thomas Philips

CodePudding user response:

Firstly, you can use the function exactly the way it is in the question, by using sapply as follows:

> x <- c("£7 2s 10d", "£12 5s 1d", "2s 4d")
> sapply(x,psp_to_decimal)
decimal price
7.14166666666667
decimal price
12.2541666666667
decimal price
0.116666666666667
 £7 2s 10d  £12 5s 1d      2s 4d 
 7.1416667 12.2541667  0.1166667 

But I assume it's preferred to avoid this. So, here's an adaptation of your function to work for vectors as well.

psp_to_decimal <- function(x){
  # Converts pounds shillings and pence to a decimal price
  psp <- strsplit(x, " ")
  
  decimal_price <- sapply(seq(psp), function(i) {
    psp_temp <- psp[[i]]
    if(is.na(x[i]) | trimws(x[i]) == ""){
      decimal_price <- NA
    } else {
      decimal_price <- 0
      if(grepl("£", psp_temp[1], fixed = TRUE)) {
        pounds <- gsub("£", "", psp_temp[1])
        decimal_price <- decimal_price   as.numeric(pounds)
        psp_temp <- psp_temp[-1]
      }
      
      if(length(psp_temp)>0 && grepl("s", psp_temp[1], fixed = TRUE)) {
        shillings <- gsub("s", "", psp_temp[1])
        decimal_price <- decimal_price   as.numeric(shillings) / 20
        psp_temp <- psp_temp[-1]
      }
      
      if(length(psp_temp)>0 && grepl("d", psp_temp[1], fixed = TRUE)) {
        pence <- gsub("d", "", psp_temp[1])
        decimal_price <- decimal_price   as.numeric(pence) / 240
      }
    }
    writeLines(c("decimal price", decimal_price))
    return(decimal_price)
  })
  
  return(decimal_price)
}

The function is very slightly changed to loop through x, so it can produce results for all values of the vector.

CodePudding user response:

Your function is written to process a single string only. If you want to apply it to a vector of strings, the simplest way is wrap it with a for-loop, an sapply function (see the other answer) or:

## you'd better remove that `writeLines`
Vectorize(psp_to_decimal)(x)

Alternatively, here is a function which can directly handle a vector of strings:

psp2num <- function (x) {
  logi <- rbind(grepl("£", x), grepl("s", x), grepl("d", x))
  num <- regmatches(x, gregexpr("[0-9] ", x))
  mat <- matrix(0, 3, length(x))
  mat[logi] <- as.numeric(unlist(num))
  ans <- c(crossprod(mat, c(1, 1/20, 1/240)))
  ans[lengths(num) == 0] <- NA
  ans
}

psp2num(c(NA, x[1], "", x[2], " ", x[3]))
#[1]         NA  7.1416667         NA 12.2541667         NA  0.1166667

Have fun.

  • Related