Monday, February 15, 2021

R Shiny App: Annuity & Amortization Schedule Calculator

Annuity & Amortization Schedule Calculator (Shiny App)

Introduction

In December, I decided I wanted to try and see if I could figure out how to build a Shiny App that would allow me to either calculate a Loan Amoritzation schedule (e.g. auto, mortgage) or would allow me to determine the future value of an investment. I looked around online to see if anyone else had tried this and the closest example I could find was done was a loan amortization calculator by Guangming Lang of MasterR.org (https://masterr.org/r/calculate-mortgage-payment-schedule/). I decided to use some of his code as a starting point for the loan portion of of the Shiny application we’ll build today. I then added the annuity portion and the pie chart. If you want to learn more about the calculations I used for the annuity portion of the code here is a a reference I used to come up with the calculations.

The Shiny Web Application

Before we get started, I just want to mention that I’m not going to explain how I’m going the build the app, rather I’m just going to share the code. I covered the basics of building the code on RStudio.cloud and hosting a Shiny Application on ShinyApps.io back in Lesson 9. If you want more information on how to setup and host a Shiny App, please go do that lesson first before you attempt this example.

Build the Application

I like to use a single RScript file format that combines the user interface and server information into a single file called app.R. Here’s what my finished code looks like.

# HEADER --------------------------------------------
#
# Author: RDataGuy
# Copyright (c) RDataGuy, 2019
# Email:  rdataguy@gmail.com
# 
# Date: 2020-12-20
#
# Shiny App Name: Annuity & Amortization Schedule Calculator
#
# Shiny App Description: Generate a table showing future value of an an
# investment (e.g. loan, annuity)
#
# Notes:
# I found original code on: https://masterr.org/r/calculate-mortgage-payment-schedule/
# and then decided to make modifications to it to include not just loan payments but also
# annuity growth calculations.
#
# This is a single file Shiny App.
#

# LOAD LIBRARIES ------------------------------------
library('shiny')
library('tidyverse')
library('lubridate')
library('plotly')

options(scipen = 999)

# USER INTERFACE ------------------------------------
# Define UI for application that calculates a table showing the future value of
# an investment.

ui <- fluidPage(
    
    # Application title
    titlePanel("Annuity & Amortization Schedule Calculator"),
    
    # Sidebar with inputs for the investment calculator
    sidebarLayout(
        sidebarPanel(
            
            radioButtons("type",
                         "Investment Type?",
                         choices = c('Loan','Annuity'),
                         selected = 'Loan'),
            
            numericInput("amount",
                         "Initial Amount ($):",
                         10000),
            
            numericInput("APR",
                         "Interest Rate (%):",
                         2.5),
            
            numericInput("term",
                         "Term (# of months)",
                         72),
            
            dateInput("start_date",
                      "Start Date",
                      today(),
                      format = "yyyy-mm-dd"),
            
            numericInput("down_pmt",
                         "Initial Down Payment (if Loan)",
                         2000),
            
            numericInput("dep",
                         "Monthly Deposit (if Annuity)",
                         0),
          
            br(), # adds a blank line
            actionButton("do", "Generate!"),
            width = 3
        ),
        
        # Show a table of investment using the Server's output called "schedule"
        mainPanel(
            splitLayout( tableOutput("schedule"),
                         plotlyOutput("pie_chart")
            ),
            width = 9
        )
    )
)

# SERVER INFORMATION --------------------------------
# Define server logic required to display table & pie chart
server <- function(input, output) {
    
    amortize = function(loan_amt, down_pmt, start_date, term, APR) {
        # calculate simple monthly rate 
        monthly_rate = APR / 12
        # start_date = as.Date(start_date, "%Y-%m-%d")
        
        # calculate (constant) contractual monthly payment amount
        # derived from the present value formula for annuities
        # https://en.wikipedia.org/wiki/Annuity
        r = (1 + monthly_rate) ^ term - 1
        pay = (loan_amt-down_pmt) * monthly_rate * (r + 1) / r
        
        # initialize output variables
        interest = principal = payment = balance = vector("numeric", term)
        
        # calc amortization schedule
        outstanding_principal = loan_amt - down_pmt
        for (i in 1:term) {
          
            intr = outstanding_principal * monthly_rate
            if(outstanding_principal < payment[i]){
                prnp = outstanding_principal
                outstanding_principal = 0
                final = TRUE
                
            }else{
              
                prnp = pay - intr
                outstanding_principal = outstanding_principal - prnp
                final = FALSE
            }
            
            interest[i]  = round(intr,2)
            principal[i] = round(prnp,2)
            payment[i] = round((prnp + intr),2)
            balance[i] = round(outstanding_principal,2)
        }
        
        data.frame('Month' = 1:term, 'Date' = ymd(start_date) %m+% months(1:term), 'Interest' = interest, 'Principal' = principal, 'Payment' = payment, 'Balance' = balance) 
    }
    
    annuity = function(initial_amt, start_date, dep, term, APR) {
        # calculate simple monthly rate 
        monthly_rate = APR / 12
        
        # calculate (constant) contractual monthly payment amount
        # derived from the present value formula for annuities
        # https://en.wikipedia.org/wiki/Annuity

        deposit = dep
        
        # initialize output variables
        interest = deposit = balance = vector("numeric", term)
        
        # calc amortization schedule
        outstanding_principal = initial_amt
        for (i in 1:term) {
            intr = outstanding_principal * monthly_rate
            prnp = dep + intr
            outstanding_principal = outstanding_principal + prnp
            
            interest[i]  = round(intr,2)
            deposit[i] = round(dep,2)
            balance[i] = round(outstanding_principal,2)
        }
        
        data.frame('Month' = 1:term, 'Date' = seq(as.Date(start_date), length.out = term, by="months"), 'Interest' = interest, 'Deposit' = deposit, 'Balance' = balance) 
    }

    # Use an action button as an event to generate the list of random numbers
    schedule_data <- eventReactive(input$do, {  
        
        # Determine if loan or annuity and then calcluate
        if (input$type == "Loan"){
          
            sched <- amortize(loan_amt = input$amount, down_pmt = input$down_pmt, start_date = input$start_date, term = input$term, APR = input$APR/100)
            sched$Date <- format(sched$Date,'%Y-%m-%d')
            return(sched)
            
        }else{
          
            sched <- annuity(initial_amt = input$amount, start_date = input$start_date, dep = input$dep, term = input$term, APR = input$APR/100)
            sched$Date <- format(sched$Date,'%Y-%m-%d')
            return(sched)
        }
    })
    
    pie_data <- eventReactive(input$do, {  
        
        # Determine if loan or annuity and then calcluate
        if (input$type == "Loan"){
          
            sched <- amortize(loan_amt = input$amount, down_pmt = input$down_pmt, start_date = input$start_date, term = input$term, APR = input$APR/100)
            sched$Date <- format(sched$Date,'%Y-%m-%d')
            name = c("Principal", "Down Payment","Interest")
            start_bal = input$amount
            final_bal = sched$Balance[input$term]
            principal = sum(sched$Principal)
            interest = sum(sched$Interest)
            values = c(principal , input$down_pmt, interest )
            dat = data.frame(cbind(name, values))
            dat$values <- as.numeric(dat$values)
            total <- sum(dat$values)
            dat$percents <- 100*(dat$values / total)
            dat = dat %>% dplyr::arrange(desc(percents)) 
            
            return(dat)

        }else{
          
            sched <- annuity(initial_amt = input$amount, start_date = input$start_date, dep = input$dep, term = input$term, APR = input$APR/100)
            sched$Date <- format(sched$Date,'%Y-%m-%d')
            name = c("Initial Amount", "Contributions", "Growth")
            start_bal = input$amount
            final_bal = sched$Balance[nrow(sched)]
            contrib = sum(sched$Deposit)
            growth = final_bal - start_bal - contrib
            values = c(input$amount, contrib , growth )
            dat = data.frame(cbind(name, values))
            dat$values <- as.numeric(dat$values)
            total <- sum(dat$values)
            dat$percents <- 100*(dat$values / total)
            dat = dat %>% dplyr::arrange((percents)) 
            return(dat)
        }
    })
    
    # Output the schedule only AFTER the "Generate!" button is pressed
    output$schedule <- renderTable({
        schedule_data()
    }, rownames = FALSE, colnames = TRUE)
    
    output$pie_chart <- renderPlotly({
        # store rendered pie_data() as a tibble
        data <- tibble(pie_data())
        fig <- plot_ly(data, labels = ~name, values = ~values, type = 'pie')
        fig <- fig %>% layout(title = paste0(input$type, ' Details', sep=""),
                              xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
                              yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE))
    })
}

# RUN APPLICATION ----------------------------------
shinyApp(ui = ui, server = server)

Test our Web Application

Okay! Let’s test it! To test your application, click on the “Run App” button. Run App Button

You should see a web page pop up. Try clicking on the “Generate!” button.

If you Generated a Loan Schedule, you should see output similar to this: Loan Example

If you generated an Annuity Schedule, you should see output similar to this: Annuity Example

Deploy our Web Application

Alright! Time to deploy this baby! Rather than re-hash the steps to deploy this application, check out my previous example on how to setup a Shinyapps.io account and deploy your application. Skip down to “Step #5: Deploy our Web Application”

After you deploy your application the browser window should pop up with your newly deployed web application. Here’s a link to my final Annuity & Amortization Schedule Calculator App for you check out.

Conclusion

Overall, I was pretty happy with this Shiny App, it does exactly what I planned it would do. One idea I had for a possible improvement was to create a way to export the schedule & chart to a PDF document. I had developed the code but ran into issues with creating the PDF while hosting the app on Shinyapps.io and I can’t afford to run RStudio Connect where you get that capability. Apparently, you cannot render PDF documents because you cannot write temporary files to Shinyapps.io. If you figure out a way to do this, I would be interested in seeing your solution!

I really enjoyed building this application, I hope you have some fun building it as well.

Have fun coding!

RDataGuy

Get the complete code for this app on GitHub (Click link)!

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.