Download the notebook here! Interactive online version: colab

Minimax regret portfolio allocation

This tutorial walks through a complete minimax regret allocation. We define a set of investment initiatives with scenario returns and confidence scores, preprocess them (filtering and confidence penalization), and solve for the portfolio that minimizes the maximum regret across all scenarios.

Workflow overview

  1. Define investment opportunities

  2. Run the solver

  3. Inspect results

Initial Setup

[1]:
from impact_engine_allocate.allocation import MinimaxRegretAllocation, preprocess, empty_rule_result
import pandas as pd

Step 1 — Define investment opportunities

Each initiative has a cost, return estimates under three scenarios (R_best, R_med, R_worst), and a confidence score between 0 and 1. Low-confidence initiatives have their effective returns pulled toward the worst case.

[2]:
investment_opportunities = [
    {"id": "A", "cost": 4, "R_best": 15, "R_med": 10, "R_worst": 2, "confidence": 0.9},
    {"id": "B", "cost": 3, "R_best": 12, "R_med": 8, "R_worst": 1, "confidence": 0.6},
    {"id": "C", "cost": 3, "R_best": 9, "R_med": 6, "R_worst": 2, "confidence": 0.80},
    {"id": "D", "cost": 2, "R_best": 7, "R_med": 5, "R_worst": 3, "confidence": 0.40},
    {"id": "E", "cost": 5, "R_best": 18, "R_med": 9, "R_worst": 0, "confidence": 0.50},
]

Inspect the initiatives as a DataFrame. Initiative D has confidence below the threshold we set next and will be filtered out.

[3]:
investments = pd.DataFrame(investment_opportunities)
investments.set_index("id", inplace=True)
investments
[3]:
cost R_best R_med R_worst confidence
id
A 4 15 10 2 0.9
B 3 12 8 1 0.6
C 3 9 6 2 0.8
D 2 7 5 3 0.4
E 5 18 9 0 0.5

Step 2 — Run the solver

Set the budget, minimum confidence threshold, and minimum aggregate worst-case return. preprocess() filters out low-confidence initiatives and computes confidence-penalized effective returns. The solver then selects the portfolio that minimizes the maximum regret across the three scenarios.

[4]:
BUDGET = 10
MIN_CONFIDENCE = 0.5
MIN_RETURN = 4

processed = preprocess(investment_opportunities, MIN_CONFIDENCE)
if not processed:
    results = empty_rule_result("No Eligible Initiatives", "minimax_regret")
else:
    solver = MinimaxRegretAllocation()
    results = solver(processed, BUDGET, MIN_RETURN)

print(f"Solver Status: {results['status']}")
if results["status"] == "Optimal":
    print(f"Minimized Maximum Regret: {results['objective_value']:.2f}")
    print(f"Selected Initiatives: {results['selected_initiatives']}")
    print(f"Total Cost of Selected Initiatives: {results['total_cost']:.2f}")
Solver Status: Optimal
Minimized Maximum Regret: 0.00
Selected Initiatives: ['A', 'B', 'C']
Total Cost of Selected Initiatives: 10.00

Step 3 — Inspect results

The result dict contains scenario-level detail: effective returns for the selected portfolio, optimal per-scenario returns (\(V_j^*\)), and the calculated regrets. The maximum regret across scenarios matches the objective value.

[5]:
if results["status"] == "Optimal":
    print("Effective Returns of Selected Portfolio:")
    for s, r_val in results["total_actual_returns"].items():
        print(f"  Scenario {s}: {r_val:.2f}")

    detail = results.get("detail", {})
    v_j_star = detail.get("v_j_star", {})
    regrets = detail.get("regrets", {})

    print("\nOptimal Returns for Each Scenario (V_j_star):")
    for s, v_val in v_j_star.items():
        print(f"  Scenario {s}: {v_val:.2f}")

    print("\nCalculated Regrets for the Selected Portfolio:")
    for s, r_val in regrets.items():
        print(f"  Scenario {s}: {r_val:.2f}")

    if regrets:
        actual_max_regret = max(regrets.values())
        print(f"\nActual Max Regret: {actual_max_regret:.2f}")
        matches = abs(actual_max_regret - results["objective_value"]) < 1e-6
        print(f"Matches minimized max regret? {matches}")
else:
    print("Optimization did not find an optimal solution.")
Effective Returns of Selected Portfolio:
  Scenario best: 28.90
  Scenario med: 19.60
  Scenario worst: 5.00

Optimal Returns for Each Scenario (V_j_star):
  Scenario best: 28.90
  Scenario med: 19.60
  Scenario worst: 5.00

Calculated Regrets for the Selected Portfolio:
  Scenario best: 0.00
  Scenario med: 0.00
  Scenario worst: 0.00

Actual Max Regret: 0.00
Matches minimized max regret? True