Possible to avoid a FOR loop in this very simple R code?

The answers below are very helpful. But I oversimplified my original question. I figured I learn more if I oversimplify and then adapt to my actual need, but now I am stuck. There are other factors that drive the amortization. See more complete code here. I like the response using "amort$end_bal <- begin_bal * (1 - mpr)^amort$period" and "amort$pmt <- c(0, diff(amort$end_bal))* -1", but in addition npr increases the ending balances and ch_off decreases ending balances. Here´s the more complete code:

n_periods <- 8
begin_bal <- 10000
yld <- .20
npr <- .09
mpr <- .10           
co <- .10            

period = seq(0,n_periods,1)
fin = 0
pur = 0
pmt = 0
ch_off = 0
end_bal = begin_bal
  
for(i in 1:n_periods){
  {fin[i+1] = end_bal[i]*yld/12}
  {pur[i+1] = end_bal[i]*npr}
  {pmt[i+1] = end_bal[i]*mpr}
  {ch_off[i+1] = end_bal[i]*co/12}
  end_bal[i+1] = end_bal[i]+pur[i+1]-pmt[i+1]-ch_off[i+1]}
  
amort <- data.frame(period,fin,pur,pmt,ch_off,end_bal)

Which gives the below correct output:

 print(amort,row.names=FALSE)
 period      fin      pur       pmt   ch_off   end_bal
      0   0.0000   0.0000    0.0000  0.00000 10000.000
      1 166.6667 900.0000 1000.0000 83.33333  9816.667
      2 163.6111 883.5000  981.6667 81.80556  9636.694
      3 160.6116 867.3025  963.6694 80.30579  9460.022
      4 157.6670 851.4020  946.0022 78.83351  9286.588
      5 154.7765 835.7929  928.6588 77.38823  9116.334
      6 151.9389 820.4700  911.6334 75.96945  8949.201
      7 149.1534 805.4281  894.9201 74.57668  8785.132
      8 146.4189 790.6619  878.5132 73.20944  8624.072

I´m new to R, and I understand one of its features is matrix/vector manipulation. In the below example I amortize an asset over 8 months, where each payment ("pmt") is 10% ("mpr") of the prior period balance ("end_bal"). The below works fine. I used a FOR loop. I understand FOR loops can be slow in large models and a better solution is use of R´s abundant vector/matrix functions. But I didn´t know how to do this in my example since each monthly payment is calculated by referencing the prior period ending balance.

So my questions are:

  1. Is there a more efficient way to do the below?
  2. How do I replace the 0 for pmt in period 0, with an empty space?

R code:

n_periods <- 8                
begin_bal <- 100              
mpr <- .10                  

# Example loan amortization
pmt =     0
end_bal = begin_bal
for(i in 1:n_periods){
  {pmt[i+1] = end_bal[i]*mpr}
  end_bal[i+1] = end_bal[i]-pmt[i+1]}
amort <- data.frame(period = 0:n_periods,pmt,end_bal)
amort

Results, which are correct:

> amort
  period       pmt   end_bal
1      0  0.000000 100.00000
2      1 10.000000  90.00000
3      2  9.000000  81.00000
4      3  8.100000  72.90000
5      4  7.290000  65.61000
6      5  6.561000  59.04900
7      6  5.904900  53.14410
8      7  5.314410  47.82969
9      8  4.782969  43.04672

Upvotes: 0

Views: 77

Answers (2)

AnilGoyal
AnilGoyal

Reputation: 26218

Use R's vectorised calculations

n_periods <- 8                
begin_bal <- 100              
mpr <- .10

amort <- data.frame(period = seq(0, n_periods, 1))

amort$end_bal <- begin_bal * (1 - mpr)^amort$period
amort$pmt <- c(0, diff(amort$end_bal))* -1
amort
#>   period   end_bal       pmt
#> 1      0 100.00000  0.000000
#> 2      1  90.00000 10.000000
#> 3      2  81.00000  9.000000
#> 4      3  72.90000  8.100000
#> 5      4  65.61000  7.290000
#> 6      5  59.04900  6.561000
#> 7      6  53.14410  5.904900
#> 8      7  47.82969  5.314410
#> 9      8  43.04672  4.782969

Created on 2021-05-12 by the reprex package (v2.0.0)

dplyr way for a different case (say)

n_periods <- 15                
begin_bal <- 1000              
mpr <- .07

library(dplyr)


seq(0, n_periods, 1) %>% as.data.frame() %>%
  setNames('period') %>%
  mutate(end_bal = begin_bal * (1 - mpr)^period,
         pmt = -1 * c(0, diff(end_bal)))
#>    period   end_bal      pmt
#> 1       0 1000.0000  0.00000
#> 2       1  930.0000 70.00000
#> 3       2  864.9000 65.10000
#> 4       3  804.3570 60.54300
#> 5       4  748.0520 56.30499
#> 6       5  695.6884 52.36364
#> 7       6  646.9902 48.69819
#> 8       7  601.7009 45.28931
#> 9       8  559.5818 42.11906
#> 10      9  520.4111 39.17073
#> 11     10  483.9823 36.42878
#> 12     11  450.1035 33.87876
#> 13     12  418.5963 31.50725
#> 14     13  389.2946 29.30174
#> 15     14  362.0439 27.25062
#> 16     15  336.7009 25.34308

Created on 2021-05-12 by the reprex package (v2.0.0)


Though OP has put another question in edited scenario, here's the approach suggested (for future reference)

n_periods <- 8
begin_bal <- 10000
yld <- .20
npr <- .09
mpr <- .10           
co <- .10

library(dplyr)

seq(0, n_periods, 1) %>% as.data.frame() %>%
  setNames('period') %>%
  mutate(end_bal = begin_bal * (1 - (mpr + co/12 - npr))^period,
         fin = c(0, (end_bal * yld/12)[-nrow(.)]),
         pur = c(0, (end_bal * npr)[-nrow(.)]),
         pmt = c(0, (end_bal * mpr)[-nrow(.)]),
         ch_off = c(0, (end_bal * co/12)[-nrow(.)]))
#>   period   end_bal      fin      pur       pmt   ch_off
#> 1      0 10000.000   0.0000   0.0000    0.0000  0.00000
#> 2      1  9816.667 166.6667 900.0000 1000.0000 83.33333
#> 3      2  9636.694 163.6111 883.5000  981.6667 81.80556
#> 4      3  9460.022 160.6116 867.3025  963.6694 80.30579
#> 5      4  9286.588 157.6670 851.4020  946.0022 78.83351
#> 6      5  9116.334 154.7765 835.7929  928.6588 77.38823
#> 7      6  8949.201 151.9389 820.4700  911.6334 75.96945
#> 8      7  8785.132 149.1534 805.4281  894.9201 74.57668
#> 9      8  8624.072 146.4189 790.6619  878.5132 73.20944

Created on 2021-05-13 by the reprex package (v2.0.0)

Upvotes: 2

ThomasIsCoding
ThomasIsCoding

Reputation: 101034

If you are "lazy" (don't want to formulate the general expression of pmt and end_bal), you can define a recursive function f like blow

f <- function(k) {
  if (k == 1) {
    return(data.frame(pmt = 100 * mpr, end_bal = 100))
  }
  u <- f(k - 1)
  end_bal <- with(tail(u, 1), end_bal - pmt)
  pmt <- mpr * end_bal
  rbind(u, data.frame(pmt, end_bal))
}

n_periods <- 8
res <- transform(
  cbind(period = 0:n_periods, f(n_periods + 1)),
  pmt = c(0, head(pmt, -1))
)

and you will see

> res
  period       pmt   end_bal
1      0  0.000000 100.00000
2      1 10.000000  90.00000
3      2  9.000000  81.00000
4      3  8.100000  72.90000
5      4  7.290000  65.61000
6      5  6.561000  59.04900
7      6  5.904900  53.14410
8      7  5.314410  47.82969
9      8  4.782969  43.04672

Upvotes: 1

Related Questions