Spaces:
Running
Running
| """Combine two probabilistic forecasts via a logarithmic opinion pool. | |
| For each outcome i, the blended probability is proportional to | |
| ``p_i_1 ** weight * p_i_2 ** (1 - weight)``, then renormalized so the three | |
| outcomes sum to 1. This is the standard way to combine independently-derived | |
| probability forecasts (the "log-odds" or "logarithmic opinion pool" blend) -- | |
| simple, well-precedented, and requires no additional fitting beyond the | |
| blend weight itself. | |
| """ | |
| from __future__ import annotations | |
| from types import SimpleNamespace | |
| from typing import Any | |
| def blend_probabilities(first: Any, second: Any, weight: float) -> dict[str, float]: | |
| """Return a logarithmic pool with ``weight`` assigned to ``first``.""" | |
| if not 0.0 <= weight <= 1.0: | |
| raise ValueError("weight must be between 0 and 1") | |
| raw = { | |
| outcome: ( | |
| getattr(first, f"p_{outcome}") ** weight | |
| * getattr(second, f"p_{outcome}") ** (1.0 - weight) | |
| ) | |
| for outcome in ("home", "draw", "away") | |
| } | |
| total = sum(raw.values()) | |
| return {outcome: value / total for outcome, value in raw.items()} | |
| def blend_forecasts(first: Any, second: Any, weight: float) -> SimpleNamespace: | |
| """Blend two forecasts with ``weight`` on ``first`` (0 <= weight <= 1).""" | |
| blended = blend_probabilities(first, second, weight) | |
| lambda_home = weight * first.lambda_home + (1.0 - weight) * second.lambda_home | |
| lambda_away = weight * first.lambda_away + (1.0 - weight) * second.lambda_away | |
| most_likely_score = first.most_likely_score if weight >= 0.5 else second.most_likely_score | |
| return SimpleNamespace( | |
| p_home=blended["home"], | |
| p_draw=blended["draw"], | |
| p_away=blended["away"], | |
| lambda_home=lambda_home, | |
| lambda_away=lambda_away, | |
| most_likely_score=most_likely_score, | |
| ) | |