4

When dealing with user input using packages shiny or plumber one often needs to convert character arguments to numeric or logical.

I would like to do it automatically, what's an efficient way to do it ?

expected (this or similar) :

convert_args <- ...

fun <- function(a, b, c, d){
  convert_args()
  dplyr::lst(a, b, c , d)
}

fun("na","true","1","foo")
#> $a
#> [1] NA
#> 
#> $b
#> [1] TRUE
#> 
#> $c
#> [1] 1
#> 
#> $d
#> [1] "foo"
moodymudskipper
  • 46,417
  • 11
  • 121
  • 167
  • See `?type.convert`. – nicola Jun 21 '19 at 14:41
  • ah, I had forgotten about this one. it's slightly different from @Ronak's proposal as by default it converts character to factor and doesn't convert lower case "true, "t" or "na" to their logical counterpart – moodymudskipper Jun 21 '19 at 14:48
  • 1
    If you use `lapply(x, type.convert, as.is = T, na.strings = c('na', 'NA'))` it will keep things as strings and catch the lower-case `"na"` but won't catch `"true"` or `"false"` – IceCreamToucan Jun 21 '19 at 14:48
  • 1
    Related to https://stackoverflow.com/questions/45940855/coerce-variables-in-data-frame-to-appropriate-format – akrun Jun 21 '19 at 15:24

2 Answers2

3

One option is to use readr::parse_guess which as the name suggests tries to guess the type of the character vector.

convert_args <- function(x) {
  lapply(x, readr::parse_guess)  
}

convert_args(c("NA","true","1","foo"))
#[[1]]
#[1] NA

#[[2]]
#[1] TRUE

#[[3]]
#[1] 1

#[[4]]
#[1] "foo"

This doesn't directly work when we have "na"

readr::parse_guess("na")
#[1] "na"

but as @Moody_Mudskipper mentions it can be resolved specifying na argument in parse_guess

readr::parse_guess("na", c("na", "NA"))
#[1] NA
Ronak Shah
  • 377,200
  • 20
  • 156
  • 213
  • Thanks @Ronak this is what I was looking for, to get the exact expected output see my wrapper in other answer. – moodymudskipper Jun 21 '19 at 15:25
  • `"na"` is not converted by default to `NA`, but this can be handled by the `na` argument of `readr::parse_guess`. In this case for example by using `lapply(x, readr::parse_guess, c("na","NA") ` above. – moodymudskipper Jun 21 '19 at 15:46
3

I built a wrapper around readr::parse_guess thanks to @Ronak's solution to get exactly the expected output.

I also added an option to evaluate the unconverted character input as it's a common task as well.


convert_args <- function(na = c("", "NA"), locale = readr::default_locale(),
                         trim_ws = TRUE, guess_integer = FALSE, eval = FALSE){
  if(!requireNamespace("readr")) 
    stop("convert_args() requires package readr to be installed")
  args <- as.list(eval.parent(quote(match.call())))[-1]
  args <- lapply(args, readr::parse_guess, na, locale, trim_ws, guess_integer)
  if (eval){
    args <- lapply(args, function(arg) {
      if(is.character(arg))
        eval(parse(text = arg, parent.frame(2)))
      else
        arg
    })
  }
  list2env(args, envir = parent.frame())
  invisible(NULL)
}
fun <- function(a, b, c, d){
  convert_args()
  dplyr::lst(a, b, c , d)
}


fun("NA","true","1","head(cars,2)")
#> Loading required namespace: readr
#> $a
#> [1] NA
#> 
#> $b
#> [1] TRUE
#> 
#> $c
#> [1] 1
#> 
#> $d
#> [1] "head(cars,2)"
fun2 <- function(a, b, c, d){
  convert_args(eval = TRUE, na = c("na","NA"))
  dplyr::lst(a, b, c , d)
}

fun2("na","true","1","head(cars,2)")
#> $a
#> [1] NA
#> 
#> $b
#> [1] TRUE
#> 
#> $c
#> [1] 1
#> 
#> $d
#>   speed dist
#> 1     4    2
#> 2     4   10

Created on 2019-06-21 by the reprex package (v0.3.0)

moodymudskipper
  • 46,417
  • 11
  • 121
  • 167