Mayou
Mayou

Reputation: 8818

Simplify loop computation

Consider the following vector x:

 > 1:9
 [1] 1 2 3 4 5 6 7 8 9

and consider the following inputs:

 start = 10
 pmt = 2

This is the result (let's call the resulting vector res) I am looking to achieve (what's displayed are the actual formulas). Note that the result is a vector not a dataframe. I just displayed it here 2 dimensions.

enter image description here

In other words, to obtain res, you multiple start by the cumulative product for each cell of df up to the corresponding cell.

When the vector index is a multiple is 4 or 7, the start value gets updated.

This is what I have attempted:

     for(i in 1:9) {
         res[i] = start * cumprod(df[k:i])[i]
         if(i %% 3 == 0) {
             start = res[i] - pmt
             k = k + 3
         } else {
             start = res[i]
         }
     }
 }

To put the problem into context, imagine you have a start value of money of 10 dollars, and you want to invest it over 9 months. However, you want to make a withdrawal at the end of each 3 months (i.e. at the beginning of month 4, month 7, ...). The vector x represent random values of returns. Therefore, at the beginning of month 4, your start value is start*1*2*3 minus the withdrawal pmt.

The purpose here is computing the wealth value at the end of month 9.

The problem is that in reality, i = 200 (200 months), and I need to redo this computation for 10,000 different vectors x. So looping 10,000 times over the above code takes forever to execute!

Would you have any suggestion as to how to compute this more efficiently? I hope the explanation is not too confusing!

Thank you!

Upvotes: 2

Views: 86

Answers (2)

TheComeOnMan
TheComeOnMan

Reputation: 12875

I know you mentioned it being 1d, but I think this works well and you can convert it to 1d very easily -

start = 10
pmt = 2

library(data.table)

dt <- data.table(
month = 1:13
)

dt[,principalgrown := start*cumprod(month)]

#explained below#######
dt[,interestlost := 0]
for(i in seq(from = 4, to = (dim(dt)[1]), by = 3))
{
dt[month >= i,interestlost := interestlost + (prod(i:month)), by = month]
}
#######################

dt[,finalamount := principalgrown - (pmt*interestlost)]

The part within the #s is the trick. Where you calculate month 7 value as ((1*2*3*start - pmt)*4*5*6 - pmt) * 7, i calculate it as 1*2*3*4*5*6*7*start - 4*5*6*7*pmt - 7*pmt. 1*2*3*4*5*6*7*start is principalgrown and - 4*5*6*7*pmt - 7*pmt is -(pmt*interestlost)

Upvotes: 0

Brian Diggs
Brian Diggs

Reputation: 58825

If you work out your formula for res as an iterative formula, then it is easier to write a function that you can give to Reduce. Here it is as a simple loop

x <- 1:9
start <- 10
pmt <- 2

res <- numeric(length(x))
res[1] <- x[1] * start
for (i in seq_along(x)[-1]) {
  res[i] <- (res[i-1] - (pmt * (!(i%%4) || !(i%%7)))) * x[i]
}

If you want to write it as a Reduce function, it would look like this

Reduce(function(r, i) {
  (r - (pmt * (!(i%%4) || !(i%%7)))) * x[i]
}, 
       seq_along(x),
       init = start, 
       accumulate = TRUE)[-1]

There is some weirdness with the start values and dropping the first element of the result because of the way that initial values are handled (and that it iteration is over indexes, not values, since comparisons must be done on the index). The loop here is probably more understandable.

Upvotes: 3

Related Questions