AI Models
VotePredictor forecasts with TabPFN, a tabular foundation model. To check that's the right choice, we raced it against the whole machine-learning zoo — boosted trees, random forests, a Gaussian process, a neural net, kernel and nearest-neighbor methods, regularized linears, and an ensemble — on the same features, the same walk-forward splits, and the same races. Here's how they all did.
| # | Model | Family | Margin error▲ | Winner | Brier |
|---|---|---|---|---|---|
| 1 | TabPFN | Tabular foundation model | 5.20 | 88% | 0.085 |
| 2 | Blend (TabPFN+RF+CatBoost) | Ensemble | 5.26 | 88% | 0.086 |
| 3 | Poll avg (no ML) | Baseline — no ML | 5.48 | 87% | 0.089 |
| 4 | CatBoost | Gradient boosting | 5.49 | 88% | 0.087 |
| 5 | RandomForest | Tree ensemble | 5.65 | 87% | 0.094 |
| 6 | ExtraTrees | Tree ensemble | 5.69 | 87% | 0.095 |
| 7 | XGBoost | Gradient boosting | 5.71 | 86% | 0.093 |
| 8 | ElasticNet | Linear | 5.87 | 86% | 0.094 |
| 9 | LightGBM | Gradient boosting | 6.02 | 85% | 0.101 |
| 10 | BayesianRidge | Linear | 6.08 | 85% | 0.102 |
| 11 | HistGBM | Gradient boosting | 6.22 | 86% | 0.102 |
| 12 | GaussianProcess | Gaussian process | 6.50 | 84% | 0.108 |
| 13 | MLP (64,32) | Neural net | 6.87 | 83% | 0.115 |
| 14 | kNN (k=15) | Nearest neighbors | 7.26 | 83% | 0.116 |
| 15 | SVR (RBF) | Kernel | 7.67 | 84% | 0.108 |
15 approaches · 1,606 races · walk-forward, 2008+ · same features and splits for all. Margin error = average miss in points (the bar scales with it — shorter is better); Brier scores the win probability. Lower is better on both.
Why TabPFN wins — the small-data story
Election forecasting is a small-data problem: a few hundred comparable races per cycle, not millions of rows. That's the regime where conventional machine learning struggles — gradient-boosted trees, random forests and neural nets have the capacity to overfit, and with so little data they do. The tell is right there in the table: the bias-corrected poll average, with no machine learning at all, beats every model except TabPFN. The best boosting library (CatBoost) only ties it; everything fancier lands behind it.
TabPFN is built for exactly this regime — it's a transformer pre-trained on millions of synthetic small-data tasks, so it predicts in a single forward pass without overfitting the way a freshly-trained tree ensemble would. It's the one learner that adds real value over simply averaging the polls — and even blending it with the runners-up doesn't help (the ensemble buys a sliver of winner accuracy at worse margin error and Brier).
Every number here is strictly out-of-sample: to score any past race, each model only ever trained on earlier cycles. Full detail in the methodology; the bake-off is reproducible in aielect/exp_model_zoo.py.
Does stacking help? (the "run a lot of stacked models" question)
A fixed average barely moves the needle, so we tried the real thing — a stack: a meta-learner trained on the out-of-fold predictions of diverse base models (the polls-only model, a campaign-spending model, a fundamentals-only model, random forests), strictly walk-forward. Overall it still loses to TabPFN — with only ~9 cycles and bases that mostly read the same polls, the meta-learner overfits the blend.
| Model / stack | Margin MAE | Winner | Brier |
|---|---|---|---|
| TabPFN+spend | 5.204 | 87.3% | 0.088 |
| TabPFNproduction | 5.286 | 87.6% | 0.088 |
| Stack-NNLS | 5.322 | 86.3% | 0.091 |
| Avg(TabPFN+RF+Cat) | 5.338 | 88.0% | 0.089 |
| Stack-Ridge | 5.474 | 86.7% | 0.093 |
| Fundamentals | 9.920 | 74.2% | 0.170 |
But the interesting part is where. Split by how many polls a race has, stacking helps exactly where the bases actually disagree — thin-poll races, the same place campaign spending adds signal. Lowest error per row in green:
| Poll density | races | TabPFN | TabPFN+spend | Stack-NNLS |
|---|---|---|---|---|
| ≤1 poll | 354 | 7.03 | 6.66 | 6.82 |
| 2 polls | 206 | 6.35 | 6.24 | 6.18 |
| 3–5 polls | 354 | 5.18 | 5.29 | 5.34 |
| 6+ polls | 503 | 3.70 | 3.69 | 3.91 |
On 2-poll races the stack is the single best model, and on ≤2-poll races the spending-aware models beat the polls-only TabPFN; on well-polled races (3+) the polls dominate and stacking only adds noise. So production stays TabPFN, with the spending/stacking edge reserved for the sparse-poll races where it's real — reproducible in aielect/exp_stack.py.