recursive purrr::mutate_if

If we look at how modify_depth is implemented, it is really not very fancy and basically performs modification at a fixed level. The .ragged parameter handles lists with heterogeneous levels as @rensa suggested.

If we really want to use modify_depth we have to iterate through each level:

library(purrr)

x <- list(list(a = as.character(1), b = as.double(2)), 
          c = as.character(3), 
          d = as.double(4))

as_integer_recursive <- function(x){
  if (is.character(x)) {
    as.integer(x)
  } else if (length(x) > 1) {
    map(x, as_integer_recursive)
  } else {
    x
  }
}
x %>% as_integer_recursive() %>% str()
#> List of 3
#>  $  :List of 2
#>   ..$ a: int 1
#>   ..$ b: num 2
#>  $ c: int 3
#>  $ d: num 4

as_integer_recursive_map <- function(x) {
  x %>%
    map_if(is.character, as.integer) %>%
    map_if(is.list, as_integer_recursive_map)
}
x %>% as_integer_recursive_map() %>% str()
#> List of 3
#>  $  :List of 2
#>   ..$ a: int 1
#>   ..$ b: num 2
#>  $ c: int 3
#>  $ d: num 4

as_integer_modify_depth <- function(x) {
  reduce(
    -seq(vec_depth(x) - 1),
    function(x, depth) {
      modify_depth(x, depth, ~if(is.character(.x)) as.integer(.x) else .x, .ragged = TRUE)
    }, 
    .init = x
  )
}
x %>% as_integer_modify_depth() %>% str()
#> List of 3
#>  $  :List of 2
#>   ..$ a: int 1
#>   ..$ b: num 2
#>  $ c: int 3
#>  $ d: num 4

microbenchmark::microbenchmark(
  as_integer_recursive(x),
  as_integer_recursive_map(x),
  as_integer_modify_depth(x),
  times = 100
)
#> Unit: microseconds
#>                         expr      min        lq       mean    median
#>      as_integer_recursive(x)   31.026   35.6635   59.97857   40.9725
#>  as_integer_recursive_map(x) 1898.626 2091.3115 3539.13028 2688.1005
#>   as_integer_modify_depth(x)  386.974  422.1990  694.83505  470.1635
#>        uq       max neval
#>    65.128   277.072   100
#>  3976.983 11519.629   100
#>   681.178  3770.032   100

Created on 2018-10-18 by the reprex package (v0.2.1)

2 Likes