r-lib / slider

Sliding Window Functions
https://slider.r-lib.org
Other
295 stars 12 forks source link

Possibility of using "safe" functions in slide #163

Closed MalditoBarbudo closed 2 years ago

MalditoBarbudo commented 2 years ago

purrr allows to create safe versions of the functions used in the map* family functions with purrr::safely and others. It would be nice to allow slide to also accept these safe versions, specially when iterating the rows of a dataframe to carry on a side effect (writing to a database, creating objects with the rows, transforming other object with the data in the rows...). These side effects can fail due to external reasons, and a failsafe is needed to avoid stopping the rows loop.

I tried to use a function created with purrr::safely but if any row fails, all results fail:

library(slider)
library(purrr)

test_list <- list(1,2,-1,'a')

# both, map and slide fails to perform log:
purrr::map(test_list, log)
#> Warning in .Primitive("log")(x, base): NaNs produced
#> Error in .Primitive("log")(x, base): non-numeric argument to mathematical function
slider::slide(test_list, log)
#> Error in .f(.x, ...): non-numeric argument to mathematical function

# we create a safe version of log
safe_log <- purrr::safely(log)

# now map works, but slide no
purrr::map(test_list, safe_log)
#> Warning in .Primitive("log")(x, base): NaNs produced
#> [[1]]
#> [[1]]$result
#> [1] 0
#> 
#> [[1]]$error
#> NULL
#> 
#> 
#> [[2]]
#> [[2]]$result
#> [1] 0.6931472
#> 
#> [[2]]$error
#> NULL
#> 
#> 
#> [[3]]
#> [[3]]$result
#> [1] NaN
#> 
#> [[3]]$error
#> NULL
#> 
#> 
#> [[4]]
#> [[4]]$result
#> NULL
#> 
#> [[4]]$error
#> <simpleError in .Primitive("log")(x, base): non-numeric argument to mathematical function>
slider::slide(test_list, safe_log)
#> [[1]]
#> [[1]]$result
#> NULL
#> 
#> [[1]]$error
#> <simpleError in .Primitive("log")(x, base): non-numeric argument to mathematical function>
#> 
#> 
#> [[2]]
#> [[2]]$result
#> NULL
#> 
#> [[2]]$error
#> <simpleError in .Primitive("log")(x, base): non-numeric argument to mathematical function>
#> 
#> 
#> [[3]]
#> [[3]]$result
#> NULL
#> 
#> [[3]]$error
#> <simpleError in .Primitive("log")(x, base): non-numeric argument to mathematical function>
#> 
#> 
#> [[4]]
#> [[4]]$result
#> NULL
#> 
#> [[4]]$error
#> <simpleError in .Primitive("log")(x, base): non-numeric argument to mathematical function>

Created on 2022-01-15 by the reprex package (v2.0.1)

DavisVaughan commented 2 years ago

slide() isn't exactly the same as map(). In particular, slide subsets with [ rather than the [[ that map uses. It does this because slide generally selects multiple elements at a time, while map always selects a single element. This means that you are calling log() on things like list(1) rather than 1 (the [ vs [[ difference really only affects lists). You can see this if you use a simpler function:

library(slider)
library(purrr)

test_list <- list(1, 2)

map(test_list, identity)
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] 2

slide(test_list, identity)
#> [[1]]
#> [[1]][[1]]
#> [1] 1
#> 
#> 
#> [[2]]
#> [[2]][[1]]
#> [1] 2

So for this particular example you'd have to access the element of the list with [[ to make slide equivalent to map.

library(slider)
library(purrr)

test_list <- list(1,2,-1,'a')
safe_log <- safely(log)

map(test_list, safe_log)
#> Warning in .Primitive("log")(x, base): NaNs produced
#> [[1]]
#> [[1]]$result
#> [1] 0
#> 
#> [[1]]$error
#> NULL
#> 
#> 
#> [[2]]
#> [[2]]$result
#> [1] 0.6931472
#> 
#> [[2]]$error
#> NULL
#> 
#> 
#> [[3]]
#> [[3]]$result
#> [1] NaN
#> 
#> [[3]]$error
#> NULL
#> 
#> 
#> [[4]]
#> [[4]]$result
#> NULL
#> 
#> [[4]]$error
#> <simpleError in .Primitive("log")(x, base): non-numeric argument to mathematical function>

slide(test_list, ~safe_log(.x[[1]]))
#> Warning in .Primitive("log")(x, base): NaNs produced
#> [[1]]
#> [[1]]$result
#> [1] 0
#> 
#> [[1]]$error
#> NULL
#> 
#> 
#> [[2]]
#> [[2]]$result
#> [1] 0.6931472
#> 
#> [[2]]$error
#> NULL
#> 
#> 
#> [[3]]
#> [[3]]$result
#> [1] NaN
#> 
#> [[3]]$error
#> NULL
#> 
#> 
#> [[4]]
#> [[4]]$result
#> NULL
#> 
#> [[4]]$error
#> <simpleError in .Primitive("log")(x, base): non-numeric argument to mathematical function>
MalditoBarbudo commented 2 years ago

Thanks! My real example is more complex and i will have to see how to adapt it to [[ notation but now I have the starter point.