A Python package for analyzing real estate affordability based on your financial situation. Filter properties, calculate mortgage costs, and visualize how different financial parameters affect your home buying power.
A small note: this was one of the very first things I've ever uploaded to github. It's not complicated. It's simply a first naive attempt at developing a basic Python package for processing real estate data which instantly yields information on property affordability.
What it does:
- Filters property datasets by affordability based on your downpayment and monthly budget
- Calculates mortgage insurance, optimal payments, and total interest costs
- Provides detailed affordability metrics for each property
- Visualizes the relationship between interest rates, payments, and costs
pip install mortgage-pkgRequirements:
- Python >= 3.7
- pandas
- numpy
✅ Property Filtering - Find affordable properties from your dataset ✅ Mortgage Calculations - Compute interest, payments, and amortization periods ✅ Insurance Analysis - Calculate Canadian mortgage insurance requirements ✅ Cost Optimization - Determine optimal monthly payments to minimize interest ✅ Visual Analytics - Generate publication-quality visualizations with seaborn
from mortgage_filter import property_filter
import pandas as pd
# Load your property data (2 columns: name/area, price)
properties = pd.read_csv('your_properties.csv')
# Filter by affordability
affordable = property_filter(
property_data=properties,
downpayment=190000,
mortgage_rate=2.8,
max_monthly_payment=4800
)The function returns a dataframe with detailed affordability metrics:
- Price - Listed property price
- Minimum_Downpayment - Legal minimum (5% of value)
- Mortgage_Insurance - Insurance cost when downpayment < 20%
- Principal - Loan amount (price - downpayment + insurance)
- Monthly_Payment - Optimal payment for shortest amortization
- Shortest_Amortization - Years to pay off with your budget
- Total_Interest - Cumulative interest over loan lifetime
- Net_Cost - Total cost (price + insurance + interest)
The package includes powerful visualization capabilities. See the interactive demo notebook for detailed examples.
How mortgage term length affects estimated interest rates
How your monthly payment amount affects total interest paid over the life of the loan
Relationship between monthly payments and loan duration
How downpayment size affects mortgage insurance (20% threshold for Canadian mortgages)
For a comprehensive walkthrough with all features and visualizations, see the Interactive Demo Notebook.
The demo covers:
- 3 real-world affordability scenarios
- 5 detailed analysis questions with visualizations
- Vancouver, BC housing data examples
- Canadian mortgage insurance rules
properties = pd.read_csv('data/vancouver_testset.csv')
properties.head()| Area | House Price | |
|---|---|---|
| 0 | Downtown & Westside | 3118200 |
| 1 | West Vancouver | 2743600 |
| 2 | North Vancouver | 1665100 |
| 3 | Richmond | 1581600 |
| 4 | South Burnaby | 1564000 |
from mortgage_filter import property_filter
# Scenario: Unknown interest rate, specify term length
results = property_filter(
property_data=properties,
downpayment=190000,
mortgage_term=15,
max_monthly_payment=4800
)
# Output: "You can afford 5 properties from the 25 you've provided."| Property/Area | Price | Minimum_Downpayment | Mortgage_Insurance | Principal | Monthly_Payment | Shortest_Amortization | Total_Interest | Net_Cost | |
|---|---|---|---|---|---|---|---|---|---|
| 20 | Pitt Meadows | 974800 | 72480.0 | 18793.83 | 803593.83 | 4767.48 | 24 | 569440.84 | 1563034.67 |
| 21 | North Delta | 972500 | 72250.0 | 18702.26 | 801202.26 | 4753.29 | 24 | 567746.53 | 1558948.79 |
| 22 | Maple Ridge | 884200 | 63420.0 | 0.00 | 694200.00 | 4698.63 | 19 | 377088.77 | 1261288.77 |
| 23 | Abbotsford | 873600 | 62360.0 | 0.00 | 683600.00 | 4782.72 | 18 | 349446.77 | 1223046.77 |
| 24 | Mission | 726000 | 47600.0 | 0.00 | 536000.00 | 4662.46 | 13 | 191344.02 | 917344.02 |
-
We're left with all the affordable home indexes as well as:
- the listed prices
- the minimum downpayment (5% of value)
- the mortgage insurance
- the principal (price - downpayment)
- the optimal monthly payment for the shortest amortization period (years)
- the cumulative interst
- the net cost
-
We note there's a printed warning saying that terms must be less than or equal to 10 years (banks don't typically offer terms beyond 10 years).
The function can handles the exception and extrapolates from the term to interest rate function; however, this could yield misleading interest rates.
Lets see what the function looks like (interest as a function of term length):
terms = np.arange(1, 15)
rate = [mort_rate(t) for t in terms]
plt.plot(terms, rate)
plt.xlabel('Terms'); plt.ylabel('Interest Rate as %')
plt.axvline(x = 10, linestyle='--', color = 'black')
plt.grid()
plt.savefig('output_7_1.svg', transparent = False, format = 'svg')
plt.show()Lengths greater than 10 years are not typically available.
Terms must range from 1 to 10 years, but calculation will be performed anyway.
Lengths greater than 10 years are not typically available.
Terms must range from 1 to 10 years, but calculation will be performed anyway.
Lengths greater than 10 years are not typically available.
Terms must range from 1 to 10 years, but calculation will be performed anyway.
Lengths greater than 10 years are not typically available.
Terms must range from 1 to 10 years, but calculation will be performed anyway.
property_filter(property_data = properties,
downpayment = 190000,
mortgage_rate = 2.8,
max_monthly_payment = 4800)You can afford 5 properties from the 25 you've provided.
| Property/Area | Price | Minimum_Downpayment | Mortgage_Insurance | Principal | Monthly_Payment | Shortest_Amortization | Total_Interest | Net_Cost | |
|---|---|---|---|---|---|---|---|---|---|
| 20 | Pitt Meadows | 974800 | 72480.0 | 18793.83 | 803593.83 | 4740.54 | 18 | 220350.90 | 1213944.73 |
| 21 | North Delta | 972500 | 72250.0 | 18702.26 | 801202.26 | 4726.43 | 18 | 219695.22 | 1210897.48 |
| 22 | Maple Ridge | 884200 | 63420.0 | 0.00 | 694200.00 | 4727.53 | 15 | 156743.49 | 1040943.49 |
| 23 | Abbotsford | 873600 | 62360.0 | 0.00 | 683600.00 | 4655.34 | 15 | 154350.26 | 1027950.26 |
| 24 | Mission | 726000 | 47600.0 | 0.00 | 536000.00 | 4722.69 | 11 | 87383.70 | 813383.70 |
- Lets try one more time with a high downpayment/low monthly payment scenario.
- Lets also assume a loan limit of $600,000 and $700,000 to see how this might affect a buying decision.
property_filter(property_data = properties,
downpayment = 500000,
mortgage_rate = 2.8,
max_monthly_payment = 3200,
max_loan = 600000).head()You can afford 12 properties from the 25 you've provided.
| Property/Area | Price | Minimum_Downpayment | Mortgage_Insurance | Principal | Monthly_Payment | Shortest_Amortization | Total_Interest | Net_Cost | |
|---|---|---|---|---|---|---|---|---|---|
| 13 | Langley | 1090800 | 218160.0 | 0 | 590800 | 3103.53 | 21 | 191282.32 | 1282082.32 |
| 14 | Cloverdale | 1087400 | 217480.0 | 0 | 587400 | 3199.21 | 20 | 180410.55 | 1267810.55 |
| 15 | Central Surrey | 1086300 | 217260.0 | 0 | 586300 | 3193.22 | 20 | 180065.18 | 1266365.18 |
| 16 | Ladner | 1042000 | 208400.0 | 0 | 542000 | 3197.35 | 18 | 148627.69 | 1190627.69 |
| 17 | Port Coquitlam | 1034400 | 206880.0 | 0 | 534400 | 3152.52 | 18 | 146536.03 | 1180936.03 |
property_filter(property_data = properties,
downpayment = 500000,
mortgage_rate = 2.8,
max_monthly_payment = 3200,
max_loan = 700000).head()You can afford 14 properties from the 25 you've provided.
| Property/Area | Price | Minimum_Downpayment | Mortgage_Insurance | Principal | Monthly_Payment | Shortest_Amortization | Total_Interest | Net_Cost | |
|---|---|---|---|---|---|---|---|---|---|
| 11 | Tsawwassen | 1153300 | 230660.0 | 0 | 653300 | 3117.86 | 24 | 244644.99 | 1397944.99 |
| 12 | New Westminster | 1127200 | 225440.0 | 0 | 627200 | 3184.83 | 22 | 213587.50 | 1340787.50 |
| 13 | Langley | 1090800 | 218160.0 | 0 | 590800 | 3103.53 | 21 | 191282.32 | 1282082.32 |
| 14 | Cloverdale | 1087400 | 217480.0 | 0 | 587400 | 3199.21 | 20 | 180410.55 | 1267810.55 |
| 15 | Central Surrey | 1086300 | 217260.0 | 0 | 586300 | 3193.22 | 20 | 180065.18 | 1266365.18 |
In Canada, downpayments >20% of the home price do not require mortgage insurance.
If you'd like to dive deeper at one scenario in particular, base functions could be used as follows:
- How quickly does cumulative interest increase as the mortgage rate increases?
- How does cumulative interest change as monthly contributions increase??
- How does the amortization period change as as monthly contributions increase?
- What's my monthly payment on a $500,000 home for a 20 year amortization on 2% interest?
- How does mortgage insurance change with downpayment?
# Parameters
princ = 500000 # principal of 500k
mth_pay = 2000 # monthly payment of 2k
mort_rate = 2.0 # mortgage rate of 2%# Question 1
rate = np.linspace(1, 5, 20)
interest = [total_interest(princ, mortgage_rate = i, monthly_payment = mth_pay) for i in rate]
plt.plot(rate, interest)
plt.ticklabel_format(axis='y', style='plain')
plt.xlabel('Interest Rate as %'); plt.ylabel('Cumulative Interest'); plt.grid()
plt.savefig('output_15_1.svg', transparent = False, format = 'svg')
plt.show()Monthly contribution is insufficient to pay off the original Principal.
# Question 2
payments = np.linspace(800, 6000, 20)
interest = [total_interest(princ, mort_rate, monthly_payment = p) for p in payments]
plt.plot(payments, interest)
plt.ticklabel_format(axis='y', style='plain')
plt.xlabel('Monthly Payment Amount'); plt.ylabel('Cumulative Interest'); plt.grid()
plt.savefig('output_16_1.svg', transparent = False, format = 'svg')
plt.show()Monthly contribution is insufficient to pay off the original Principal.
- Note that as the monthly payment increases, not only does cumulative interest decrease, but the amortization period decreases substantially but this isn't see in the plot above.
- Lets just peek at how the amortization period decreases.
# Question 3
years = [optimal_monthly_payment(princ, mort_rate, max_monthly_payment = p)[1] for p in payments]
plt.plot(payments, years)
plt.xlabel('Max Monthly Payment'); plt.ylabel('Years to Pay Off'); plt.grid()
plt.savefig('output_18_1.svg', transparent = False, format = 'svg')
plt.show()# Question 4
monthly_payment(princ, mort_rate, amortization = 20)2529.42
# Question 5
downpayment_size = np.linspace(22000, 125000, 28)
mort_ins = [mortgage_insurance(princ, d) for d in downpayment_size]
plt.plot(downpayment_size, mort_ins)
plt.xlabel('Downpayment on $500,000'); plt.ylabel('Mortgage Insurance'); plt.grid()
plt.savefig('output_20_1.svg', transparent = False, format = 'svg')
plt.show()Downpayment must be at least 5% the asset value
Input value is too low to be legally considered.
- Notice how a message prints for downpayments less than 5% the property value (minimal downpayment).
- Also notice how at a downpayment of 20% mortgage insurance no longer applies.
All functions include detailed docstrings. Use Python's help() function for inline documentation:
help(mortgage_filter)Help on module mortgage_filter.mortgage_filter in mortgage_filter:
NAME
mortgage_filter.mortgage_filter - # coding: utf-8
FUNCTIONS
property_filter(property_data, downpayment, mortgage_rate=None, mortgage_term=None, max_monthly_payment=None, max_loan=None)
Given a dataframe of properties, their prices, and some basic financial information, it returns a dataframe with only the affordable properties and other affordability metrics (ie. how long it would take to pay off, monthly payments, total interest, etc.).
| Arguments |
|---|
| data : dataframe |
| Areas/properties in column index 0 (str) |
| Respective prices in column index 1 (numeric) |
downpayment : numeric
Your maximal possible downpayment
mortgage_rate : numeric
Interest rate on the mortgage loan (leave empty if mortgage_term is provided)
mortgage_term : int
Contract length in years (1 to 10) for the mortgage interest rate.
Only specify if you do not know what mortgage_rate to enter (leave empty if mortgage_rate provided)
max_monthly_payment : numeric
Your max affordable or bank limited monthly payment towards your home
max_loan : numeric
Max eligible loan based on your downpayment
| Return |
|---|
| dataframe |
| Properties/Areas |
| Prices/Average area price |
| Minimum_Downpayment |
| Mortgage_Insurance |
| Principal |
| Monthly_Payment |
| Shortest_Amortization |
| Total_Interest |
| Net_Cost (assuming no other fees) |
help(total_interest)Help on function total_interest in module mortgage_filter.mortgage_base:
total_interest(principal, mortgage_rate, monthly_payment)
Returns the cumulative interest paid on a given principal, mortgage rate, and monthly payment.
| Arguments |
|---|
| principal : numeric |
mortgage_rate : float
Annual mortgage rate (loan interest)
amortization: int
Amortization period in years (or in months if months == True)
monthly_payment : bool
Monthly contribution towards the principal
| Return |
|---|
| float |
| Cumulative interest paid |
help(optimal_monthly_payment)Help on function optimal_monthly_payment in module mortgage_filter.mortgage_base:
optimal_monthly_payment(principal, mortgage_rate, max_monthly_payment)
Returns the first amortization period which has a monthly payment
less than your max_monthly_payment (ie. within budget). The shortest
possible amortization period has the lowest long term interest cost.
| Arguments |
|---|
| principal : numeric |
mortgage_rate : float
Annual mortgage rate (loan interest)
max_monthly_payment: numeric
Your max affordable monthly contribution
| Return |
|---|
| list |
| mp: monthly payment for a given amortization |
| i: amortization period in years |
help(mortgage_insurance)- Required when downpayment < 20% of home value
- Not required when downpayment ≥ 20% of home value
- Insurance rates vary by loan-to-value ratio (calculated automatically)
- Estimates rates based on mortgage term length (1-10 years typical)
- Uses regression model fitted to Canadian mortgage rate data
- Terms > 10 years use extrapolation (less accurate)
- Calculates optimal monthly payment for shortest amortization
- Minimizes total interest paid over loan lifetime
- Respects your maximum monthly budget constraint
- ✅ Comprehensive test suite with >95% coverage
- ✅ Automated CI testing via GitHub Actions
- ✅ Compatible with Python 3.7-3.11
- ✅ Tests run on every commit to
main
Run tests locally:
python TEST_SUITE.pyQuestions or feature requests? Contact: luka.vuko@outlook.com
Found a bug? Open an issue
Want to contribute? Pull requests are welcome!
MIT License - see LICENSE.md for details
This project was created to better understand the home buying process and associated costs. It's designed for educational purposes and personal financial planning.
Disclaimer: This tool provides estimates based on typical Canadian mortgage scenarios. Always consult with a qualified mortgage professional for official financial advice.