Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome to the final lesson of Module 1: Foundations of Real Estate Investment.
In our previous lessons, we've built a solid conceptual foundation. We've learned to calculate gross income, account for vacancy, itemize operating expenses, and distinguish them from capital expenditures. You now understand all the individual components of a property's basic profit and loss statement.
Today, we'll bring it all together. The learning outcome for this lesson is to write Python code to model basic rental property income and expenses to calculate Net Operating Income (NOI). As a software engineer, you'll find this a natural next step: translating financial concepts into a structured, reusable model. This lesson serves as the capstone for our foundational module and will create the core engine for all the more complex analyses we'll perform in the rest of the course.
1. From Formula to Code: Deconstructing NOI
Let's start by revisiting the core concept of Net Operating Income. It is a measure of a property's profitability before considering financing and income taxes.

To model this in code, we need to break it down into programmable steps:
- Potential Gross Income (PGI): The maximum possible rental income.
- Effective Gross Income (EGI): The income you actually expect to collect after accounting for vacancies or non-payment.
- Total Operating Expenses (OpEx): The sum of all recurring costs to operate the property. This includes property taxes, insurance, community fees, maintenance, property management, utilities, etc.
- Net Operating Income (NOI): The final calculation.
Crucially, remember that NOI excludes:
- Debt Service: Mortgage principal and interest payments.
- Income Taxes: Such as IRPF in Spain.
- Capital Expenditures (CapEx): Major improvements or replacements.
This makes NOI a pure measure of the property's operational performance, independent of how it's financed or taxed.
2. Structuring the Model: Data and Logic
A robust model separates data from the logic that operates on it. In our case, the "data" is the specific property's characteristics, and the "logic" is the financial calculation itself.
For the data part, Python's dataclasses are an ideal tool. They provide a clean, type-hinted way to define the structure for our property information.
For the logic, we'll create a simple function that takes the property data and our assumptions as input and returns the calculated NOI.
Let's examine a couple of examples from online articles to see how this can be implemented.
Building an Intelligent Real Estate Investment Analyzer ...
This article, 'Building an Intelligent Real Estate Investment Analyzer', demonstrates an excellent object-oriented approach. It uses a dataclass to define the property and a separate class to handle calculations. This is a common and effective pattern in software development.
First, review the 'Data Models' section to see how a Property dataclass is defined. Then, in the 'FinancialCalculator' class, focus specifically on the code lines that calculate effective_monthly_rent, total_monthly_expenses, and annual_noi. Notice how it cleanly separates income from expenses. We will adapt this logic.
Building a Comprehensive Rental Property Calculator
Alternatively, this article, 'Building a Comprehensive Rental Property Calculator', uses a more procedural approach with a single, large function. It shows a different way to structure the code.
Skim the 'Key Components of Our Investment Model' section to see the inputs. Then, in the 'Python Implementation', find the line that calculates net_annual_rent. You'll see it's essentially gross_annual_rent minus losses and all operating expenses—which is the formula for NOI.
Both resources, though US-centric and including metrics we haven't covered yet, correctly implement the core logic for calculating NOI. We will adopt the structured approach from the first resource, as it will be easier to expand in future lessons.
3. Implementing the NOI Calculator in Python
Let's write our first model. We'll create a complete, runnable script.
First, we define the dataclass for our property's financial inputs. Then, we write a function to calculate NOI.
# main.py
from dataclasses import dataclass
# --- 1. Data Model ---
# We define a structure to hold all the necessary inputs for a property.
# Using a dataclass makes the code clean and the data structure explicit.
@dataclass
class PropertyFinancials:
"""Holds all financial inputs for a rental property analysis."""
monthly_rent: float
property_tax_annual: float
insurance_annual: float
# In Spain, this would be 'gastos de comunidad'
hoa_fees_monthly: float = 0
# For other recurring costs like utilities paid by the landlord
other_monthly_expenses: float = 0
# --- 2. Calculation Logic ---
# This function encapsulates the business logic for calculating NOI.
# It takes the property data and our investment assumptions as arguments.
def calculate_noi(property_data: PropertyFinancials, vacancy_rate: float, maintenance_rate: float, management_rate: float) -> dict:
"""
Calculates the Net Operating Income (NOI) for a property.
Args:
property_data: A dataclass object with the property's income/expense figures.
vacancy_rate: The estimated percentage of time the property will be vacant.
maintenance_rate: The estimated maintenance costs as a percentage of collected rent.
management_rate: The property management fee as a percentage of collected rent.
Returns:
A dictionary containing the detailed breakdown and final NOI.
"""
# --- Income Calculation ---
potential_gross_income = property_data.monthly_rent * 12
vacancy_loss = potential_gross_income * vacancy_rate
effective_gross_income = potential_gross_income - vacancy_loss
# --- Operating Expenses Calculation ---
# Note: Maintenance and Management are often estimated as a % of EFFECTIVE rent.
maintenance_annual = effective_gross_income * maintenance_rate
management_annual = effective_gross_income * management_rate
# Sum all annual operating expenses
total_operating_expenses = (
property_data.property_tax_annual +
property_data.insurance_annual +
(property_data.hoa_fees_monthly * 12) +
(property_data.other_monthly_expenses * 12) +
maintenance_annual +
management_annual
)
# --- Final NOI Calculation ---
net_operating_income = effective_gross_income - total_operating_expenses
return {
"Potential Gross Income": potential_gross_income,
"Vacancy Loss": -vacancy_loss,
"Effective Gross Income": effective_gross_income,
"Total Operating Expenses": -total_operating_expenses,
"Net Operating Income": net_operating_income,
"Breakdown of OpEx": {
"Property Tax": -property_data.property_tax_annual,
"Insurance": -property_data.insurance_annual,
"HOA/Community Fees": -(property_data.hoa_fees_monthly * 12),
"Maintenance": -maintenance_annual,
"Management": -management_annual,
"Other": -(property_data.other_monthly_expenses * 12)
}
}
# --- 3. Running the Analysis ---
if __name__ == "__main__":
# Define a hypothetical property in Spain
my_property = PropertyFinancials(
monthly_rent=1200, # €1,200/month
property_tax_annual=600, # IBI (Impuesto sobre Bienes Inmuebles)
insurance_annual=350, # Home insurance
hoa_fees_monthly=80 # Gastos de comunidad
)
# Define our assumptions
# These are critical and should be based on market research
VACANCY_RATE = 0.05 # 5%
MAINTENANCE_RATE = 0.05 # 5% of collected rent
MANAGEMENT_RATE = 0.08 # 8% of collected rent
# Calculate the financial metrics
analysis_result = calculate_noi(my_property, VACANCY_RATE, MAINTENANCE_RATE, MANAGEMENT_RATE)
# Print a clear summary
print("--- Rental Property NOI Analysis ---")
print(f"Potential Gross Income: €{analysis_result['Potential Gross Income']:10,.2f}")
print(f"Vacancy Loss ({VACANCY_RATE:.0%}): €{analysis_result['Vacancy Loss']:10,.2f}")
print("---------------------------------------")
print(f"Effective Gross Income: €{analysis_result['Effective Gross Income']:10,.2f}")
print(f"Total Operating Expenses: €{analysis_result['Total Operating Expenses']:10,.2f}")
print("---------------------------------------")
print(f"Net Operating Income (NOI): €{analysis_result['Net Operating Income']:10,.2f}\n")
print("--- Annual Operating Expense Breakdown ---")
for expense, amount in analysis_result['Breakdown of OpEx'].items():
print(f"{expense:<25}: €{amount:10,.2f}")
Copy this code into a file named main.py and run it. You'll see a clean summary of the property's performance. This simple script is a powerful first step. It's readable, reusable, and easily expandable.
Test your understanding!
Consider a new property with the following details:
- Monthly Rent: €950
- Annual Property Tax (IBI): €450
- Annual Insurance: €280
- Monthly Community Fees: €40
Using the same assumptions as our example (Vacancy_Rate=5%, Maintenance_Rate=5%, Management_Rate=8%), first calculate the NOI manually on paper or a calculator. Then, modify the my_property object in the Python script with these new values and run it to check your answer.
Show answer
Manual Calculation:
- PGI: €950 * 12 = €11,400
- Vacancy Loss: €11,400 * 0.05 = €570
- EGI: €11,400 - €570 = €10,830
- OpEx:
- Property Tax: €450
- Insurance: €280
- Community Fees: €40 * 12 = €480
- Maintenance: €10,830 * 0.05 = €541.50
- Management: €10,830 * 0.08 = €866.40
- Total OpEx: €450 + €280 + €480 + €541.50 + €866.40 = €2,617.90
- NOI: €10,830 - €2,617.90 = €8,212.10
Running the Python script with the updated my_property values will yield the same result.
Conclusion
Congratulations on completing the first module! You have successfully built your first real estate financial model.
Key Takeaways:
- You can translate the financial formula for NOI directly into Python code.
- Separating your model into a data structure (like a
dataclass) and calculation logic (a function or class) is a robust and scalable approach. - The model we built calculates a property's unlevered operating profit by systematically subtracting vacancy loss and all operating expenses from the potential gross income.
- This NOI figure is the single most important metric for valuing an income-producing property, a topic we will explore in depth in Module 3.
Preview of the Next Module:
Our model currently shows the property's performance in a vacuum. But most real estate investments involve a mortgage. In our next lesson, we will begin Module 2: Financing, Leverage, and Pre-Tax Returns. We will start by learning how to calculate monthly mortgage payments under Spanish market conditions and introduce the concept of debt service. This will allow us to move from NOI to our first measure of investor return: Pre-Tax Cash Flow.