Python  

How to Find Bowley’s Index Number using Python

Table of Contents

  • Introduction

  • What Is Bowley’s Index Number?

  • Why Use a Symmetric Index?

  • Real-World Scenario: Tracking Fair Inflation in Urban Rental Markets

  • Step-by-Step Calculation

  • Complete Implementation with Test Cases

  • Best Practices and Limitations

  • Conclusion

Introduction

When measuring price changes over time, traditional indices like Laspeyre’s or Paasche’s can be biased—Laspeyre’s overstates inflation, Paasche’s understates it. Enter Bowley’s Index Number, a clever compromise that averages both to deliver a more balanced view. In this article, we’ll demystify Bowley’s Index, implement it in clean, tested Python code, and apply it to a pressing real-world issue: fairly measuring rent inflation in fast-growing cities—where both tenant and landlord perspectives matter.

What Is Bowley’s Index Number?

Bowley’s Index (also called the weighted arithmetic mean of Laspeyre’s and Paasche’s indices) is defined as:

qw

It’s a symmetric index, meaning it treats base-period and current-period quantities more fairly than either Laspeyre’s or Paasche’s alone. This makes it ideal for policy discussions, housing studies, and economic reporting where neutrality is key.

Why Use a Symmetric Index?

  • Laspeyre’s uses old quantities → favors past consumption patterns

  • Paasche’s uses new quantities → reflects current behavior but is harder to compute

  • Bowley’s splits the difference → reduces bias and improves fairness

For public-facing metrics—like city rent reports—this balance builds trust with both residents and investors.

Real-World Scenario: Tracking Fair Inflation in Urban Rental Markets

You’re a data analyst for a metropolitan housing authority in Denver. Each year, you track average rents and occupied units across 5 neighborhood types: downtown lofts, suburban apartments, student housing, family homes, and micro-units.

In 2023 (base year), you recorded:

  • Average rent per type

  • Number of occupied units per type

In 2024 (current year), both rents and occupancy shifted.

Your mandate: “Report a fair, unbiased measure of rental inflation that doesn’t favor 2023 or 2024 living patterns.”

Bowley’s Index is the perfect solution—combining insights from both years without overcommitting to either.

Step-by-Step Calculation

  1. Compute Laspeyre’s Index using 2023 quantities as weights

  2. Compute Paasche’s Index using 2024 quantities as weights

  3. Take the arithmetic average of the two

  4. Multiply by 100 for standard index format

Complete Implementation with Test Cases

from typing import List
import unittest

def laspeyres_index(p0: List[float], p1: List[float], q0: List[float]) -> float:
    num = sum(p1_i * q0_i for p1_i, q0_i in zip(p1, q0))
    den = sum(p0_i * q0_i for p0_i, q0_i in zip(p0, q0))
    if den == 0:
        raise ValueError("Base-period total expenditure is zero")
    return (num / den) * 100

def paasche_index(p0: List[float], p1: List[float], q1: List[float]) -> float:
    num = sum(p1_i * q1_i for p1_i, q1_i in zip(p1, q1))
    den = sum(p0_i * q1_i for p0_i, q1_i in zip(p0, q1))
    if den == 0:
        raise ValueError("Current-period base-cost is zero")
    return (num / den) * 100

def bowleys_index(
    base_prices: List[float],
    current_prices: List[float],
    base_quantities: List[float],
    current_quantities: List[float]
) -> float:
    """
    Calculate Bowley's Index Number as the average of Laspeyre's and Paasche's indices.
    
    Returns:
        Bowley's Index rounded to 2 decimal places
    """
    if not all([base_prices, current_prices, base_quantities, current_quantities]):
        raise ValueError("All input lists must be non-empty")
    
    n = len(base_prices)
    if not all(len(lst) == n for lst in [current_prices, base_quantities, current_quantities]):
        raise ValueError("All lists must have the same length")
    
    L = laspeyres_index(base_prices, current_prices, base_quantities)
    P = paasche_index(base_prices, current_prices, current_quantities)
    bowley = (L + P) / 2
    return round(bowley, 2)

class TestBowleysIndex(unittest.TestCase):
    
    def test_rent_inflation_scenario(self):
        # 2023 (base) data: rent per unit type (USD/month)
        p0 = [2200, 1600, 1300, 2800, 1100]
        q0 = [1200, 3000, 2500, 800, 4000]   # occupied units in 2023
        
        # 2024 (current) data
        p1 = [2350, 1680, 1350, 2900, 1180]
        q1 = [1100, 3200, 2300, 900, 4500]   # occupied units in 2024
        
        index = bowleys_index(p0, p1, q0, q1)
        # Should be around 106–107 (6–7% fair inflation)
        self.assertGreater(index, 105)
        self.assertLess(index, 108)

    def test_identical_periods(self):
        prices = [100, 200]
        qty = [10, 20]
        index = bowleys_index(prices, prices, qty, qty)
        self.assertEqual(index, 100.0)

    def test_error_cases(self):
        with self.assertRaises(ValueError):
            bowleys_index([], [1], [1], [1])
        with self.assertRaises(ValueError):
            bowleys_index([1, 2], [3], [4, 5], [6, 7])

if __name__ == "__main__":
    # Denver rental market data
    rent_2023 = [2200, 1600, 1300, 2800, 1100]      # Downtown, Suburban, Student, Family, Micro
    units_2023 = [1200, 3000, 2500, 800, 4000]
    
    rent_2024 = [2350, 1680, 1350, 2900, 1180]
    units_2024 = [1100, 3200, 2300, 900, 4500]
    
    index = bowleys_index(rent_2023, rent_2024, units_2023, units_2024)
    
    print("=== Denver Fair Rental Inflation Index ===")
    print(f"Bowley's Index (2023 → 2024): {index}")
    
    if index > 100:
        print(f"→ Fair estimate of rental inflation: {index - 100:.2f}%")
    elif index < 100:
        print(f"→ Rental costs decreased by {100 - index:.2f}%")
    else:
        print("→ No net change in rental cost pressure")
    
    # Run tests
    unittest.main(argv=[''], exit=False, verbosity=2)

Output:

=== Denver Fair Rental Inflation Index ===
Bowley's Index (2023 → 2024): 105.47
→ Fair estimate of rental inflation: 5.47%

test_error_cases (__main__.TestBowleysIndex.test_error_cases) ... ok
test_identical_periods (__main__.TestBowleysIndex.test_identical_periods) ... ok
test_rent_inflation_scenario (__main__.TestBowleysIndex.test_rent_inflation_scenario) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

Best Practices and Limitations

  • Use when fairness matters—ideal for public reporting, policy, or stakeholder communication

  • Ensure quantity data is accurate for both periods; garbage in = garbage out

  • Not ideal for real-time dashboards—requires current-period quantity data, which may lag

  • Always report alongside Laspeyre’s and Paasche’s to show the full picture

  • Combine with visualizations—show how Bowley’s sits between the two extremes

Conclusion

Bowley’s Index Number offers a principled middle ground in inflation measurement—balancing historical and current realities without bias. In domains like housing, healthcare, or education—where price changes impact real lives—this fairness isn’t just statistical elegance; it’s an ethical necessity.

With the implementation above, you can compute Bowley’s Index reliably and transparently, turning raw price and quantity data into trustworthy insights that serve everyone, not just one side of the market.