rpurrraccumulate

Acummulate .init for first instance only


I am trying to use accumulate in the following way described below. I'm not sure this is the correct way of using the function, and I am more than open to any alternative suggestions to achieve the desired result - I am just not knowlegeable enough about this type of iteration or how to acheive it.

I am trying to make a calculation interatively, and then incorporate the previous results of the iteration into the preceeding calculation over a series of values. However, I also need to supply a single intial value for the first calculation. Is it possible to take advantage of purrr::accumulates .init as well as its default cumulative result of previous function behaviour?

I have the following data for the example:

change <- c(-65.007602, 
             46.957756,
            -49.028273, 
            -162.412249,  
            -28.002166,  
             116.749729,  
            -59.090951,   
             60.309175,   
             55.703313,   
            -6.434954)

As well as the below function:

t2 <- function(t1, changeval) {
  change.quant <- ((1/100)*t1*abs(changeval)+t1)-t1
    ifelse(str_detect(changeval, "-") == T, 
         t1 - change.quant, 
         t1 + change.quant)
} 

The function appears to work correctly for a single instance:


t2(150000, -65.007602) 
[1] 52488.6

I have a predetermined value for the inital t1 value, for the example will just use 150000.

To my understanding, purrr::accumulate will take either use the inital value or result of the previous function. So if I want to supply the inital start value, I can use .init :

change %>% 
  as.tibble() %>% 
  accumulate(t2, .init = 150000)

However, this then becomes the start value for all interations. What I am wondering is if it is possible to use the .init behaviour for the first instance (to 'get the ball rolling' so to speak), and then return to its cummulative result behaviour?

Or if there is an alterantive way to acheive this?

This process itself I intend to iterate, so the simpler the better.

Grateful for any advice, even if its just to point me in the right direction (i.e., correct terms for what I'm trying to achieve, etc.)

Thanks

UPDATE

Thanks for feedback for those who posted, am updating below with a better description of what I expect and what I am getting.

Note: also updated the original if/else function above to the working version using ifelse, as in my bleary eyed caffine deficent state during posting, I included an earlier iteration - apologies.

Take the improved version of the original t2 function (following suggestion below):

t2 <- function(t1, changeval) {
  change.quant <- ((1/100)*t1*abs(changeval)+t1)-t1
  t1 + sign(changeval) * change.quant
} 

This gives the inital result of:

t2(150000, -65.007602)
[1] 52488.6

Following on from this value, if accumulate works by taking the result of this function, and using it to calculate the next iteration, then the next t1 value should be '52488.6'. The next change value in the vector is '46.957756'. Using the function:

t2(52488.6, 46.957756)
[1] 77136.07

However, when I use accumulate, I get the following results:

change %>% 
  as.tibble() %>% 
  accumulate(t2, .init = 150000)

[1]   52488.60  220436.63   76457.59  -93618.37  107996.75  325124.59   61363.57  240463.76  233554.97  140347.57

So instead of '77136.07' I am getting '220436.63', which if I use the function to calculate:

t2(150000, 46.957756)
[1] 220436.6 

That is, it appears that the inital '150000' value is again being used here for the calculation, instead of the '52488.60' value from the first iteration.

Would appreciate any insight into whether this is how the accumulate function is supposed to behave, or if it is something I am doing wrong in how I have constructed my function. And especially, any advice on achieving the desired result!

Thanks again, wranglr


Solution

  • Short version: remove the as.tibble() %>% line from the code and you get the right answer.

    Long version: This is the type of situation where R has failed in a silent and hard to understand way. We can see a hint of why if we get the length of the input:

     change %>% 
      as.tibble() %>% 
      length()
    # [1] 1
    

    I'm guessing you were expecting it to be 10, but tibbles are nested lists, and change %>% as.tibble() is something similar to:

    list(
      change = c(-65.007602, 46.957756, -49.028273, -162.412249, -28.002166, 116.749729, -59.090951, 60.309175, 55.703313, -6.434954)
    )
    

    So it makes sense when we try to get the length of the parent list, we get 1, and why when we try to loop through the list, our function only does one iteration.

    As Ritchie was saying in the comments, arithmetic functions like addition, multiplication, getting the absolute value of something etc. work with lists as well as vectors, which is great in some circumstances - for example, doing something like 1 + c(2, 3, 4). But in this case, it means we get the wrong answer, and it's unclear to us as the user why it hasn't given us the right answer.

    So to summarise, accumulate in your example seemed to use the init value on every value because it was running the calculation on every value (you were right!), but only doing it once (i.e. it didn't iterate). It didn't iterate because in the parent list (i.e. the tibble), there is only one element inside.

    Working code:

    tibble(change_col = change) |> 
      mutate(t2_vals = accumulate(change_col, t2, .init = 150000)[-1])
    

    Output:

    # A tibble: 10 × 2
       change_col t2_vals
            <dbl>   <dbl>
     1     -65.0   52489.
     2      47.0   77136.
     3     -49.0   39318.
     4    -162.   -24539.
     5     -28.0  -17668.
     6     117.   -38294.
     7     -59.1  -15666.
     8      60.3  -25114.
     9      55.7  -39103.
    10      -6.43 -36587.