patternpythonMinor
Calculation of mortgage repayments
Viewed 0 times
calculationrepaymentsmortgage
Problem
I've just started learning R, but I feel that I'm using it only as a normal procedural language without taking advantage of some built-in functions and data structures.
I wrote a function that calculates repayments for mortgage with an option of specifying list of overpayments to reduce monthly payments. Please suggest any improvements on making it more native to R.
I wrote a function that calculates repayments for mortgage with an option of specifying list of overpayments to reduce monthly payments. Please suggest any improvements on making it more native to R.
source("http://pastebin.com/raw.php?i=q7tyiEmM") ## for pmt()
mortgage 0 && nrow(overPayments) > 0) {
overPayments <- sum(overPayments$payment)
currentCapital <- currentCapital - overPayments
payment <- -pmt(monthlyRate, nper = months + 1 - currentMonth, pv = currentCapital)
}
capital <- append(capital, currentCapital)
totalPaid <- totalPaid + payment
payments <- append(payments, payment)
}
cat("Would have paid: ", round(totalShouldPay, 2), "\n")
cat("Will pay: ", round(totalPaid, 2), "\n")
cat("Saved: ", round(totalShouldPay - totalPaid, 2), "\n")
return(data.frame(capital=capital, payments=payments))
}
overPayments <- data.frame(
month = c(20, 25, 30, 35, 40),
payment = c(20000, 20000, 20000, 20000, 20000))
cap <- mortgage(
over = overPayments
)
#print(cap)
par(mfcol=c(1, 2))
plot(cap$payments, type="s", main = "Payments", xlab = "Month", ylab = "Payment")
plot(cap$capital, type="l", main = "Capital", xlab = "Month", ylab = "Remaining loan")Solution
I spent clearly too much time on this... Main ideas:
- your understanding of how the payment on a fixed mortgage is computed was wrong. The payment is computed once at the beginning and remains the same regardless of prepayments, which is why people who choose to prepay end up repaying their whole mortgage before the scheduled term, as the example below should show.
- I added
pmtas an argument to the function as it is best practice for a function to not use any variable defined outside its scope (the dreaded global variables)
- A big no-no in your code was the use of constructs like
capital
- You'll see I use things like 12L
instead of12. That's the difference between integers and numerics. It is recommended to use integers when possible as it can prevent (rare) issues regarding floating point errors. It uses less memory and can sometimes make your code faster. Again, you won't notice it here, but it is a good practice.
- Regarding @janos' comment about trying to avoid for
loops. I agree, R works best and faster when vectorization can replace loops. However, your particular algorithm is a case of an iterative (statei+1depends on statei) process so it cannot be vectorized. Whereas vectorization can replace loops only in processes where each iteration can be computed independently. So you will find that my answer still has a loop, in the form of awhile()`.
- I modified the variables' names to be closer to the official language. (I work in finance.)
mortgage 0) {
balance.due <- start.balance * (1 + monthly.rate)
payment.due <- min(monthly.payment, balance.due)
prepayment <- if (is.null(prepayments)) 0 else
min(sum(subset(prepayments, month == month.idx)$payment),
balance.due - payment.due)
total.payment <- payment.due + prepayment
interest.payment <- start.balance * monthly.rate
end.balance <- balance.due - total.payment
principal.payment <- start.balance - end.balance
db[[month.idx]] <- c(month = month.idx,
start.balance = start.balance,
balance.due = balance.due,
payment.due = payment.due,
total.payment = total.payment,
prepayment = prepayment,
interest.payment = interest.payment,
principal.payment = principal.payment,
end.balance = end.balance)
month.idx <- month.idx + 1L
start.balance <- end.balance
}
as.data.frame(do.call(rbind, db))
}
source("http://pastebin.com/raw.php?i=q7tyiEmM") # for pmt()
overPayments <- data.frame(
month = c(20, 25, 30, 35, 40),
payment = c(20000, 20000, 20000, 20000, 20000))
cap1 <- mortgage()
cap2 <- mortgage(prepayments = overPayments)
par(mfcol=c(1, 2))
plot(c(cap1$start.balance, 0), type="l",
main = "Without Prepayments", xlab = "Month", ylab = "Balance")
plot(c(cap2$start.balance, 0), type="l",
main = "With Prepayments", xlab = "Month", ylab = "Balance")Code Snippets
mortgage <- function(property.value = 1e6,
downpayment = 0.10,
interest.rate = 0.04,
term = 25L,
prepayments = NULL,
pmt.fun = pmt) {
original.balance <- property.value * (1 - downpayment)
monthly.rate <- interest.rate / 12L
term.in.months <- term * 12L
monthly.payment <- -pmt.fun(monthly.rate, nper = term.in.months,
pv = original.balance)
db <- list()
month.idx <- 1L
start.balance <- original.balance
while(start.balance > 0) {
balance.due <- start.balance * (1 + monthly.rate)
payment.due <- min(monthly.payment, balance.due)
prepayment <- if (is.null(prepayments)) 0 else
min(sum(subset(prepayments, month == month.idx)$payment),
balance.due - payment.due)
total.payment <- payment.due + prepayment
interest.payment <- start.balance * monthly.rate
end.balance <- balance.due - total.payment
principal.payment <- start.balance - end.balance
db[[month.idx]] <- c(month = month.idx,
start.balance = start.balance,
balance.due = balance.due,
payment.due = payment.due,
total.payment = total.payment,
prepayment = prepayment,
interest.payment = interest.payment,
principal.payment = principal.payment,
end.balance = end.balance)
month.idx <- month.idx + 1L
start.balance <- end.balance
}
as.data.frame(do.call(rbind, db))
}
source("http://pastebin.com/raw.php?i=q7tyiEmM") # for pmt()
overPayments <- data.frame(
month = c(20, 25, 30, 35, 40),
payment = c(20000, 20000, 20000, 20000, 20000))
cap1 <- mortgage()
cap2 <- mortgage(prepayments = overPayments)
par(mfcol=c(1, 2))
plot(c(cap1$start.balance, 0), type="l",
main = "Without Prepayments", xlab = "Month", ylab = "Balance")
plot(c(cap2$start.balance, 0), type="l",
main = "With Prepayments", xlab = "Month", ylab = "Balance")Context
StackExchange Code Review Q#60097, answer score: 6
Revisions (0)
No revisions yet.