Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Rental Property NOI Calculation in Python

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.

Net Operating Income (NOI) Formula Illustration
This image provides the fundamental formula: NOI is the total income generated by the property (Revenue) minus the costs associated with running it (Operating Expenses).

To model this in code, we need to break it down into programmable steps:

  1. Potential Gross Income (PGI): The maximum possible rental income.
  2. Effective Gross Income (EGI): The income you actually expect to collect after accounting for vacancies or non-payment.
  3. 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.
  4. 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:

  1. PGI: €950 * 12 = €11,400
  2. Vacancy Loss: €11,400 * 0.05 = €570
  3. EGI: €11,400 - €570 = €10,830
  4. 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
  5. 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.

Can't find a good explanation? Sign up and we'll make it for you

Sign up