purrr icon indicating copy to clipboard operation
purrr copied to clipboard

An accumulate-style function but without recursivity?

Open MarceloRTonon opened this issue 4 years ago • 3 comments

This issue ocurr to me as I do Structural Decomposition Analysis (using Input-Output matrices and etecetera) , and found no function like this in the purrr package and no issue about this here. Nevertheless, I will try to make my point the simplier the possible. What I want is to apply functions using 2 consecutive elements of a vectors without being recursive as accumulate is.

Suppose you have a vector, inputVec, of length n. In this case, let's make:

set.seed(1)

inputVec <- runif(10)

inputVec
#>  [1] 0.26550866 0.37212390 0.57285336 0.90820779 0.20168193 0.89838968
#>  [7] 0.94467527 0.66079779 0.62911404 0.06178627

If you run accumulate(inputVec, sum) you are going to get a vector like:

library(purrr)
purrr::accumulate(inputVec, sum)
#>  [1] 0.2655087 0.6376326 1.2104859 2.1186937 2.3203756 3.2187653 4.1634406
#>  [8] 4.8242384 5.4533524 5.5151387

The third element, 1.2104859, is the result of sum( sum(inputVec[1], inputVec[2]), inputVec[3]) . This happens because accumulate is recursive (it uses its own outputs). But lets suppose you don't want this recursivity. Suppose you want to use only the original elements of inputVec as inputs. In our example, this would be c(sum(inputVec[1], inputVec[2]), sum(inputVec[2], inputVec[3]), ....,sum(inputVec[9], inputVec[10])) .

One can notice that this take a vector of length n and returns one of lenght n-1. Combining purrr:map2 and rlang::exec, I created a function that I believe that should solve this problem, I called it innermap:

library(rlang, warn,conflicts = F)
innermap <- function(input, .f0){
    require(rlang)
    require(purrr)
    .output <- map2(c(1:(length(input)-1)), c(2:length(input)),
                    function(x1,x2)  rlang::exec(.f0, input[x1], input[x2]))
    return(.output)
}

The result we got is:

innermap(inputVec, sum) %>% unlist()
#> [1] 0.6376326 0.9449773 1.4810612 1.1098897 1.1000716 1.8430650 1.6054731
#> [8] 1.2899118 0.6909003

It also works in other situations:

innermap(letters, paste0) %>% unlist()
#>  [1] "ab" "bc" "cd" "de" "ef" "fg" "gh" "hi" "ij" "jk" "kl" "lm" "mn" "no" "op"
#> [16] "pq" "qr" "rs" "st" "tu" "uv" "vw" "wx" "xy" "yz"

I believe a function like that, done by true professionals, could be a good addition to the purrr package.

MarceloRTonon avatar Oct 14 '20 17:10 MarceloRTonon

I created a package with functions that would be what I think nice additions (I didn't make them using C++, so maybe there are some possible perfomance gains here).

https://github.com/MarceloRTonon/innermap

MarceloRTonon avatar Oct 14 '20 22:10 MarceloRTonon

A more compact purrr solution to the described problem would be

map2_dbl(head(input, -1L), tail(input, -1L), sum)

AshesITR avatar Nov 05 '20 14:11 AshesITR

I think slider::slide_*() (with parameters .before = 1, .after = 0) can provide the desired output.

zenggyu avatar Mar 01 '22 00:03 zenggyu

This definitely sounds more like a job for slider than purrr.

hadley avatar Aug 24 '22 10:08 hadley