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.