Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome to the final lesson of Module 3.
Throughout this module, we've focused on calculating the core metrics used to evaluate a single property's financial viability and value. You've learned to compute Net Operating Income (NOI), Capitalization Rate (Cap Rate), Gross Rent Multiplier (GRM), and even a single-period Total Return.
Today, we'll bring these concepts together in a practical, hands-on way that leverages your background in software engineering. Our goal is to fulfill the final learning outcome of this module: Write Python code to compare multiple properties using cap rate, CoC return, and GRM. We will build a foundational script that can take data for several properties and output a clear, comparative analysis, allowing you to quickly screen and rank potential investments.
1. Recapping the Comparison Metrics
Before we dive into the code, let's quickly review the three metrics we'll be implementing. They each offer a different perspective on an investment:
- Capitalization Rate (Cap Rate): Measures the unleveraged return on a property based on its income. It's great for comparing properties independent of financing.
- Cash-on-Cash (CoC) Return: Measures the return on the actual cash you've invested (your down payment and closing costs). It's a crucial metric for understanding the performance of your capital.
- Gross Rent Multiplier (GRM): A quick, back-of-the-napkin metric that compares the property's price to its gross income. It's useful for a very fast initial screening.
The image below provides a great visual summary of the formulas for Cap Rate and CoC Return, which are central to our script.

2. Structuring the Data for Analysis
As a software engineer, you know that a good program starts with well-structured data. To compare multiple properties, we first need a consistent way to represent them. A list of Python dictionaries is a great starting point, where each dictionary holds the key financial data for one property.
For this exercise, let's imagine we are analyzing three different properties in Spain. Here is how we could structure the input data:
properties_data = [
{
"id": "PIS-001",
"address": "Calle Ficticia 1, Madrid",
"price": 300000,
"closing_costs": 24000, # Assuming ~8% ITP for this example
"gross_annual_rent": 18000, # 1500/month
"annual_operating_expenses": 4000,
"financing": {
"down_payment_pct": 0.20,
"interest_rate": 0.04, # 4% annual rate
"loan_term_years": 30
}
},
{
"id": "PIS-002",
"address": "Avenida Imaginaria 2, Valencia",
"price": 220000,
"closing_costs": 17600, # 8% ITP
"gross_annual_rent": 13200, # 1100/month
"annual_operating_expenses": 3000,
"financing": {
"down_payment_pct": 0.20,
"interest_rate": 0.04,
"loan_term_years": 30
}
},
{
"id": "PIS-003",
"address": "Plaza de Ejemplo 3, Sevilla",
"price": 250000,
"closing_costs": 20000, # 8% ITP
"gross_annual_rent": 16200, # 1350/month
"annual_operating_expenses": 3500,
"financing": {
"down_payment_pct": 0.25, # Higher down payment
"interest_rate": 0.038, # Slightly better rate
"loan_term_years": 25
}
}
]
Note: In a more advanced model, annual_operating_expenses would be broken down (IBI, community fees, insurance, etc.) and closing_costs would be calculated based on whether IVA or ITP applies, as we'll see in Module 9. For now, we use single figures.
3. Implementing the Calculation Logic
Now, let's write the functions to calculate our metrics. We'll create a main function that processes a single property's data and returns its key metrics. A great example of this structure can be found in the following resource.
Building an Intelligent Real Estate Investment Analyzer ...
The article 'Building an Intelligent Real Estate Investment Analyzer' provides an excellent Python class structure for calculating financial metrics. We will adapt this logic for our script.
Read section 3, 'Financial Calculator Agent'. Pay close attention to the calculate_metrics method. Notice how it takes a property's data and sequentially calculates the necessary components to arrive at the final metrics like Cap Rate and Cash-on-Cash return. We will replicate this pattern.
Drawing inspiration from that structure, let's build our analysis function. We'll combine the logic for all three metrics. Note that we're simplifying the mortgage calculation to an annual figure for this example; you already know how to calculate precise monthly payments from Module 2.
import numpy_financial as npf
def analyze_property(property_data):
"""
Calculates key financial metrics for a single property.
"""
# --- Basic Data ---
price = property_data["price"]
closing_costs = property_data["closing_costs"]
gross_annual_rent = property_data["gross_annual_rent"]
op_ex = property_data["annual_operating_expenses"]
# --- 1. Calculate NOI and Cap Rate ---
net_operating_income = gross_annual_rent - op_ex
cap_rate = (net_operating_income / price) * 100
# --- 2. Calculate GRM ---
gross_rent_multiplier = price / gross_annual_rent
# --- 3. Calculate Financing and CoC Return ---
# Financing details
down_payment_pct = property_data["financing"]["down_payment_pct"]
interest_rate = property_data["financing"]["interest_rate"]
loan_term_years = property_data["financing"]["loan_term_years"]
# Investment
down_payment = price * down_payment_pct
total_cash_invested = down_payment + closing_costs
# Debt Service (Mortgage)
loan_amount = price - down_payment
# Using numpy_financial to calculate monthly payment
monthly_mortgage = -npf.pmt(rate=interest_rate / 12, nper=loan_term_years * 12, pv=loan_amount)
annual_debt_service = monthly_mortgage * 12
# Cash Flow
pre_tax_cash_flow = net_operating_income - annual_debt_service
# CoC Return
# Avoid division by zero if total_cash_invested is 0 (e.g., 100% financing with no costs)
if total_cash_invested > 0:
cash_on_cash_return = (pre_tax_cash_flow / total_cash_invested) * 100
else:
cash_on_cash_return = float('inf') # Or handle as an exceptional case
return {
"id": property_data["id"],
"price": price,
"cap_rate": cap_rate,
"coc_return": cash_on_cash_return,
"grm": gross_rent_multiplier,
"pre_tax_cash_flow": pre_tax_cash_flow
}
Test your understanding!
Why is the Cap Rate for a property independent of its financing details, while the Cash-on-Cash Return is heavily dependent on them?
Show answer
Cap Rate is calculated using Net Operating Income (NOI), which is defined as Gross Income - Operating Expenses. It deliberately excludes debt service (mortgage payments). This allows you to evaluate the property's intrinsic profitability. Cash-on-Cash Return, on the other hand, is calculated using Pre-Tax Cash Flow (NOI - Debt Service) and your actual cash invested (Down Payment + Closing Costs). Both of these components are direct results of your financing choices.
4. Comparing the Properties
Now that we have our data structure and our analysis function, the final step is to loop through our properties, calculate the metrics for each, and display them in a way that's easy to compare.
Using the pandas library is ideal for this, as it allows us to create a clean, sortable table (a DataFrame) with just a few lines of code.
import pandas as pd
# 1. Analyze all properties
analysis_results = []
for prop in properties_data:
results = analyze_property(prop)
analysis_results.append(results)
# 2. Create a pandas DataFrame for comparison
df = pd.DataFrame(analysis_results)
# 3. Format the output for better readability
df['price'] = df['price'].map('€{:,.0f}'.format)
df['pre_tax_cash_flow'] = df['pre_tax_cash_flow'].map('€{:,.2f}'.format)
df['cap_rate'] = df['cap_rate'].map('{:.2f}%'.format)
df['coc_return'] = df['coc_return'].map('{:.2f}%'.format)
df['grm'] = df['grm'].map('{:.2f}x'.format)
# Set the property ID as the index
df.set_index('id', inplace=True)
# 4. Print the comparison table
print("--- Property Investment Comparison ---")
print(df)
Expected Output:
Running this script will produce a table similar to this:
--- Property Investment Comparison ---
price cap_rate coc_return grm pre_tax_cash_flow
id
PIS-001 €300,000 4.67% 3.93% 16.67x €3,300.91
PIS-002 €220,000 4.64% 4.01% 16.67x €2,472.07
PIS-003 €250,000 5.08% 4.98% 15.43x €4,111.44
This output immediately provides powerful insights:
- Property PIS-003 shows the highest Cap Rate and CoC Return, and the lowest (best) GRM. This is despite not being the cheapest property. The combination of a lower interest rate, a shorter loan term (leading to faster equity buildup, although not shown here), and strong rent relative to its price makes it stand out.
- Properties PIS-001 and PIS-002 have very similar Cap Rates and GRMs, suggesting they are priced similarly from an income perspective before leverage. However, their CoC returns differ slightly, reflecting the different numbers involved.
This kind of comparative table is exactly what you need to quickly filter a long list of potential deals down to a few promising candidates for deeper analysis. You could easily sort this table by coc_return or cap_rate to instantly see the top performers.
Conclusion
Congratulations on completing the final lesson of this module! You have successfully built a practical Python tool for comparing real estate investments. This script is a powerful asset that combines several of the key metrics we've studied into one actionable output.
Key Takeaways:
- Structuring property data using dictionaries or classes is the first step to programmatic analysis.
- Python functions can encapsulate the logic for calculating metrics like Cap Rate, CoC Return, and GRM, making your code reusable and clean.
- Libraries like
pandasare extremely effective for creating comparative tables that help you quickly rank and analyze multiple investment opportunities. - A combined view of Cap Rate, CoC Return, and GRM provides a more robust comparison than any single metric alone.
Preview of the Next Module:
With this lesson, you've solidified your understanding of property-level financial metrics. You can now analyze a deal's numbers with confidence. However, a property's success is also heavily influenced by its environment.
In our next module, Module 4: Market Analysis and Property Due Diligence, we will broaden our perspective. We'll move from the spreadsheet to the real world, starting with the first lesson: Analyze key market demographic and economic indicators (population growth, job growth, median income). You'll learn how to assess the health and potential of a city or region, which is a critical factor in any long-term investment strategy.