尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Election modeling based on historical data
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Election modeling based on historical data
Model Democratic/Republican vote margins in Massachusetts State Senate and State Representative races.
bwbensonjr/ma-election-db - District-level elections and fine-grained census databwbensonjr/ocpf-cli - OCPF command-line toolbwbensonjr/mapoli - Scripts for gathering precinct-level election resultsThe current mapoli legislative model uses a Bayesian regression model with this structure:
dem_margin ~ PVI_N + incumbent_status + pres_elec
dem_margin = (dem_percent - gop_percent) * 100PVI_N = Partisan Voter Index (PVI) which is Democratic/Republican point margin averaged over the last two presidential elections and normalized by the US-wide point margin.incumbent_status
1 if there is a Democratic incumbent in the race0 if there is no incumbent in the race-1 if there is a GOP incumbent in the racepres_elec
1 - The legislative election coincides with a U.S. presidential general election0 - The legislative election does not coincide with a U.S. presidential general electionEvery training row needs two things at the same granularity: the
Democratic/Republican margin for a legislative race, and the PVI_N
built from the two presidential elections preceding that race. State
Senate and State Representative districts split municipalities, so PVI
has to be aggregated to the district from precinct-level
presidential results. Town-level presidential returns cannot be
allocated to a district that contains only part of a town. Precinct
availability for presidential results is therefore the binding
constraint on how far back the data set can reach.
The download/{election_id}/precincts_include:1/ endpoint returns
town-level rows with empty Ward/Pct columns for elections before
precinct reporting begins. It does not error, so the cutoff has to be
detected by inspecting the returned rows.
| Office | First year with precinct results |
|---|---|
| President, Governor, U.S. Senate, U.S. House, State Senate, Governor's Council | 2002 |
| State Representative | 1980 |
State Representative is the exception because rep districts are built out of precincts, so results were always broken out that way. Every other office reports one row per municipality (351 rows) through 2000 and roughly 2,170 precinct rows from the 2002 general onward.
Presidential elections with precinct-level results: 2004, 2008, 2012, 2016, 2020, 2024.
The earliest PVI computable from two precinct-level presidential
elections is the 2008 PVI (2004 + 2008). A race in year Y uses the
PVI from the two most recent presidential elections completed before
Y, which puts the floor at the 2010 legislative elections.
| Race year | PVI year | Source presidentials | District map |
|---|---|---|---|
| 2010 | 2008 | 2004 + 2008 | 2001 |
| 2012 | 2008 | 2004 + 2008 | 2011 (remap required) |
| 2014, 2016 | 2012 | 2008 + 2012 | 2011 |
| 2018, 2020 | 2016 | 2012 + 2016 | 2011 |
| 2022, 2024 | 2022 | 2016 + 2020 | 2021 (remap required) |
| 2026 | 2024 | 2020 + 2024 | 2021 |
pvi_year in mapoli/pvi/ma_state_leg_pvi_2008_2024.csv names the
later of the two presidential elections, except for 2022, which is
the 2020 PVI reallocated to the 2021 district map.
Usable range: the 2010 through 2024 legislative elections, eight cycles, plus 2026 as the forward prediction target. 2008 and earlier are out of reach at any useful granularity: they would require 2000 precinct-level presidential results, which do not exist.
2012 and 2022 are the first elections under new maps, so the prior
presidential votes must be reallocated from old precinct geography to
new districts. This is what mapoli's ma_precincts_districts_*.csv
files do via areal interpolation, and it is why post-2021 vote totals
are fractional rather than integers. Rows on either side of a
redistricting boundary are not strictly comparable and the remapped
PVI values carry interpolation error that the natively-mapped years do
not.
mapoli/pvi holds precinct presidential results
for 2012, 2016, 2020, and 2024, normalized into ma-election-db
as precinct_presidential_vote. This covers races from 2018
onward.District-level legislative results run back to 1990 in
ma-election-db, State Representative precinct results back to 1980,
and State Senate precinct results back to 2002. In every case the
presidential precinct data runs out first.
Splitting by year rather than randomly matters here because districts repeat across cycles and a random split would leak the same district's behavior between train and test.
A single split on the 2021 redistricting -- train on 2010 through 2020, test
on 2022 and 2024 -- was the initial plan. The implemented procedure generalises
it to a rolling-origin schedule over every election year from 2014 onward,
which scores 424 races instead of 128 and, by including the odd years, puts 24
special elections in the holdout instead of none. The redistricting boundary
is still tested: the 2022 fold trains entirely on the old map and predicts on
the new one, and redistricting_cycle is a reported segment. See
docs/scoring.md.
The precinct-level data set described by this section is built and committed
by the collection pipeline in docs/pipeline.md, with the
column-by-column schema in docs/schema.md.
data/precinct/ma_precinct_training_set.csv.gz
dem_margin, dem_margin_two_party and PVI_N computed at precinct
grain, with incumbent_status, pres_elec, is_special,
num_candidates and the eligibility flags carried from the raceThe table is built at the most permissive eligibility rule -- any candidate
named in the returns counts, write-in or ballot line -- and carries the flags
by which a stricter rule excludes a race, so who counts as a candidate is a
downstream selection rather than a property baked in. That is 10 races more
than the 623 the ballot-line rule admits. See
docs/definitions.md.
Coverage matches the cutoff derived above. Of the 1,651 legislative elections
in the window, 1,650 have precinct-level returns; the 2011 3rd Berkshire
special reports municipality totals only. 2.4% of rows have no PVI_N,
almost all of them precincts created after 2020 that no earlier presidential
election covers, and 1.1% sit in precincts split between two districts. Both
are flagged per row rather than dropped.
The baseline model is reproduced on the collected data and evaluated by a fixed scoring procedure, so later variables and algorithms are measured against an unmoved yardstick.
dem_margin ~ PVI_N + incumbent_status + pres_elec,
Gaussian likelihood, fit at race grain with Bambi/PyMC. It remains the stable
reproduction and historical benchmark, but it is not the control for a
change proposed for the operational forecast. The selected horizon-specific
forecast_14d or forecast_60d composite is that experiment's control.docs/scoring.md.data/race/ma_race_training_set.csv.gz,
633 races rolled up from the precinct table, described in
docs/race_schema.md, with the candidate roster in
data/race/ma_race_candidates.csv.gz.data/race/ma_race_finance.csv.gz, one
row per candidate per race, carrying receipts and expenditures accumulated to
a stated pre-election date. Rolled up into the race table's money columns.
Collected by uv run maprecinct finance; see
docs/money_results.md.uv run legmodel definitions # list the data definitions
uv run legmodel score # score under the adopted definition
uv run legmodel folds # print the fold schedule
uv run legmodel compare baseline baseline_year # paired comparison of variants
uv run legmodel compare-definitions current two_party_or_strongest
uv run legmodel tenure # run the operational tenure replacement
uv run legmodel tenure --legacy # reproduce the historical incremental test
uv run legmodel importance # variable importance and effect sizes
uv run legmodel target-finance --horizon 60d --dry-run
uv run legmodel forecast --horizon 60d
uv run legmodel forecast-web --horizon 60d # export the static explorer bundle
The GitHub Pages forecast explorer, its local development commands, and its
deployment process are documented in docs/interactive_forecast.md.
Under the pre-change current definition. Superseded by the table in
Answered Questions below, which uses the adopted
definition; both remain reproducible.
| Segment | Races | RMSE | Coverage (90%) | Win accuracy |
|---|---|---|---|---|
| Pooled | 424 | 15.61 | 0.889 | 0.920 |
| General elections | 400 | 14.96 | 0.900 | 0.925 |
| Special elections | 24 | 23.98 | 0.708 | 0.833 |
| No Democratic candidate | 11 | 28.43 | 0.364 | 0.818 |
is_special help?Not overall. Pooled RMSE moves from 15.608 to 15.641, a difference of -0.033 with a 90% interval of [-0.143, +0.078], which contains zero: the data does not separate the two models. The pooled figure hides two real and opposite effects. The term lowers RMSE on the 24 holdout special elections by 1.13 points, and raises it on the other 400 races by 0.14, and because specials are 5.7% of the holdout the second outweighs the first.
On specials it does cut the baseline's large pessimism about Democrats, from
-11.41 points of bias to -7.91. The recommendation is to keep the pooled model
as the reference and use special_pooled_term --- or equivalently
special_split --- when the question is about a special election. That
question has since widened into a three-arm comparison; full writeup in
docs/special_handling_result.md.
The comparison also surfaced larger failures than the one under test: races with no Democratic candidate (36% interval coverage) and an unabsorbed presidential-year bias swinging from -6.4 to +2.7 points. Both are settled in Answered Questions below.
docs/money_results.mddocs/tenure_result.mdbwbensonjr repositories:
bwbensonjr/ma-election-db
bwbensonjr/mapoli/pvi - Scripts for precinct-level election resultsdocs/scoring.mddocs/is_special_result.mddocs/definition_result.mdnum_candidates - docs/variant_results.mddocs/money_results.mdThe five questions deferred from the baseline work are settled, and campaign finance -- the enhancement this README has carried as planned work since the project started -- is answered alongside them. Questions 1 through 3 were decided together, because each rebuilds the training table and invalidates the scorecard; 4, 5 and 6 are variant declarations scored by the existing harness, though 6 also adds a collection stage and new columns.
Full writeups: docs/definition_result.md for the
data definitions, docs/variant_results.md for the
variants, docs/money_results.md for campaign
finance, docs/variable_importance.md for which
predictors carry the model and what each is worth, and
docs/definitions.md for the mechanism.
Who counts as a candidate, and what the margin is measured against, is now a named definition applied over the published tables rather than a property baked into them. Testing an alternative is a filter and a column choice, not a rebuild.
uv run legmodel definitions
uv run legmodel compare-definitions current two_party_or_strongest
Adopted: two_party_or_strongest — the two-party margin where both major
parties stood, the margin against the strongest non-Democrat on that pair's own
two-candidate denominator where no Republican ran, races with no Democrat
excluded, and the write-in threshold left at the ballot-line rule. 610 races,
413 in the pooled holdout, all 24 holdout specials retained.
Four of the five definition comparisons came back undecided, so the choice
rests on a principle fixed before the numbers were seen: the response and the
predictor should be measured against the same denominator. PVI_N is a
two-party quantity, so the response should be too.
1. A two-party response — undecided, and the pooled figure is a trap. On
its own holdout the strict two_party definition posts 12.76 RMSE against
current's 15.61, which looks decisive. On the 346 races the two share, the
difference is -0.31 [-0.84, +0.38]. The gap is not accuracy: it is the 78 races
two_party drops, which current scores at an RMSE of 25.3. Restricting to
Democrat-versus-Republican races removes the hard ones, and comparing pooled
figures would have credited the model for declining to predict them.
The README's own sub-question answers itself. two_party_or_strongest recovers
93 of the 106 dropped races, keeps 24 holdout specials instead of 22, and costs
nothing measurable (+0.111, undecided). There is no case for paying 67 holdout
races and two specials for a restriction the data does not reward.
2. The write-in threshold — undecided, left at the ballot-line rule.
write_in_5pct against current is -0.020 [-0.050, +0.007]; because both
share a response, that comparison isolates the eligibility rule cleanly. What
settles it is the other direction: the two races a 5% threshold admits score at
an RMSE of 63.2. Admissions also stop changing between 8% and 10%, so any
threshold in that range is the same rule under a different name.
The asymmetry the question identified is nonetheless fixed. A definition's threshold now governs both decisions a write-in affects — whether a race is contested and whether the write-in enters the denominator — so a write-in can no longer count for one and not the other.
3. Races with no Democratic candidate — decided: exclude them, from training as well as scoring. This is the only definition comparison the data settles: +0.094 [+0.049, +0.143]. The control makes it informative — keeping them in training while withholding them from scoring is undecided (+0.006), so the gain comes specifically from not training on them. Those 13 races were distorting the fit for the other 610, not merely resisting prediction. Most of the apparent improvement is still removal rather than accuracy, and the writeup says so.
4. Presidential-date bias — partly fixed. The baseline runs +3.0 points
too Democratic on presidential-date general elections and -6.0 too Republican
on midterm-date ones, a gap of 9.0 that one binary term cannot correct. (Both
figures are over general elections only; every special carries
pres_elec = False, so the old blended "non-presidential" figure mixed 237
midterm generals with 24 specials.)
baseline_year, a hierarchical election-date intercept, is the adopted
variant: RMSE 14.34, a difference of +0.663 [+0.283, +1.026], coverage 0.935 ---
the best of any variant scored --- and a bias gap of -0.56. It is decided under
three definitions of four. It cannot shift a forward date's mean, because that
date's effect is unobserved and drawn from the hyperprior.
baseline_national_env adds a term signed by the party holding the presidency:
-1 off-cycle under a Democratic president, +1 under a Republican one, 0 on a
presidential ballot. It still cuts pooled bias from -2.90 to -0.56 and is
the only variant that can shift a forward year's mean --- it carries to 2026,
where it takes the value +1. But it is undecided under three definitions of
four (+0.488 [-0.311, +1.221] under the adopted one), and it is exactly
collinear with pres_elec across the first nine folds, because every midterm
before 2017 had a Democratic president. It is not adopted on the current
evidence; the forward-prediction argument for it is structural rather than
measured. See docs/variant_results.md.
baseline_pres_incumbent is undecided.
5. num_candidates — undecided; it does not belong in the baseline. A wash
under current (+0.026) and under the adopted definition (-0.033), and
decidedly worse under the strict two-party definition (-0.064
[-0.107, -0.023]). Once the response is measured on a two-party denominator the
term adds variance without adding signal, exactly as anticipated.
6. Campaign finance — the largest gain measured so far.
baseline_money_logratio adds the log ratio of Democratic to opponent
receipts, measured 14 days before each race's own election, and lowers pooled
RMSE by +1.700 [+1.128, +2.255] — roughly twice what the year intercept
was worth. The effect is concentrated in open seats (+3.686
[+2.481, +4.872]) and undecided against a Democratic incumbent.
Money is never read from OCPF's published cumulative figure, which is a full
calendar-year total fetched after the fact and therefore includes money raised
after the polls closed — 28% and 70% of two cycles' totals for one Boston
filer. Every figure is reconstructed from report line items over a window
ending at a stated pre-election date. The result is not evidence that
spending changes outcomes; see
docs/money_results.md.
7. Incumbency tenure — operational replacement undecided; retain the selected
forecasts. Replacing incumbent_status with signed cap-four tenure in the
14-day operational composite changes general-election RMSE from 12.039 to
12.122. The control-minus-replacement difference is -0.083 with an
election-date-clustered interval of [-0.345, +0.181]. The 60-day sensitivity is
also undecided. The earlier baseline-plus-tenure test remains a historical
incremental experiment, not the operational selection result. See
docs/tenure_result.md.
| Variant | Segment | Races | RMSE | Coverage (90%) | Win accuracy |
|---|---|---|---|---|---|
baseline | Pooled | 413 | 15.01 | 0.896 | 0.915 |
baseline | General elections | 389 | 14.19 | 0.910 | 0.915 |
baseline | Special elections | 24 | 24.76 | 0.708 | 0.833 |
baseline_national_env | Pooled | 413 | 14.52 | 0.915 | 0.915 |
baseline_national_env | General elections | 389 | 13.55 | 0.931 | 0.915 |
baseline_national_env | Special elections | 24 | 22.68 | 0.792 | 0.917 |
baseline_year | Pooled | 413 | 14.34 | 0.935 | 0.927 |
baseline_year | General elections | 389 | 13.45 | 0.938 | 0.933 |
baseline_year | Special elections | 24 | 23.25 | 0.750 | 0.833 |
baseline_money_logratio | Pooled | 398 | 13.34 | 0.894 | 0.920 |
baseline_money_logratio | General elections | 376 | 12.81 | 0.904 | 0.928 |
baseline_money_logratio | Special elections | 22 | 20.73 | 0.727 | 0.773 |
baseline_spend_logratio | Pooled | 398 | 13.05 | 0.895 | 0.920 |
baseline_spend_logratio | General elections | 376 |
These supersede the baseline table above but are not the same measurement:
different races and a different response. current stays registered and
scorable, so the earlier figures remain reproducible.
The money row is scored on 398 races, not 413. It excludes the 15 holdout races where a candidate could not be resolved to an OCPF filer, because a missing filer and a candidate who raised nothing are different facts and the variant may not impute one into the other. The paired comparison above scores both models on those same 398 races, so it is a like-for-like difference.
The same four contrasts over expenditures rather than receipts are
registered and scored too. baseline_spend_logratio is the most accurate
variant on this holdout, but it beats the receipts term by only +0.322
[+0.056, +0.599] and that margin is undecided under two of the four
definitions, so baseline_money_logratio stays the adopted money variant —
spending is also the more endogenous measure, being the quantity a campaign
adjusts in response to how close the race looks. See
docs/money_results.md.
Of the money and pre-money variants, baseline_money_logratio is much the most accurate, including
on special elections, which are the model's worst segment. It loses a little
interval calibration relative to baseline_year and gives up the ability to
score the 15 unmatched races at all. baseline_year is best calibrated;
baseline_national_env is the only one of the older pair usable for a forward
prediction, and the money term is too — a figure measured 14 days out is
knowable before the election it predicts.
Known defects and limitations carried forward, as distinct from the planned work in Model Enhancements and Plan.
pres_elec costs accuracy in the money modelDropping pres_elec from baseline_money_logratio improves pooled holdout
RMSE by 0.705 [−1.100, −0.299] — decided — even though its coefficient is
+7.09 [+5.24, +8.97] and nowhere near zero. The finding survived the refold
essentially unchanged: it was 0.722 [−1.116, −0.320] on year folds.
pres_elec is a property of a race's election date, and a fold is one
election date, so its value is constant across every race in a holdout by
construction — a wrong shift lands on all of them in the same direction rather
than averaging out. Across the folds its coefficient ranges from 7.09 to 13.54
while PVI_N holds between 1.40 and 1.65, and the presidential years
themselves disagree (2012 +27.5 mean margin, 2020 +13.9).
This extends the presidential-date bias already recorded under question 4 rather than contradicting it. Not yet acted on: a variant dropping the term is a registry entry and a rescore, and no alternative is adopted until that is compared under every scored definition.
What the special-election work added to this. special_split's general
component excludes special elections from training, which changes what
pres_elec is estimated from, since every special carries
pres_elec = False. It beats both pooled arms on presidential-date general
elections under all four definitions, and loses to them on midterm-date ones.
So part of what looks like a special-election question is really a question
about the timing term. See
docs/special_handling_result.md and
docs/variable_importance.md.
national_env is collinear with pres_elec for nine foldsResolved. The confounding refusal now runs predictor-versus-predictor, not
only predictor-versus-grouping-factor, and baseline_national_env is refused
on exactly the nine folds 2014-01-07 through 2017-07-25.
The defect was real and is now measured rather than argued. national_env is
pres_elec - 1 identically on every training window whose midterms all fell
under a Democratic president, so the two terms were one column carrying two
parameters. Nothing refused it, because the harness only ever tested a
predictor against a grouping factor --- which is why baseline_year_pres
was refused for this exact shape while baseline_national_env was not. The
check is now pairwise over the expanded design columns, the separating count
is published per fold, and the refusal reason travels on each row of
fit_diagnostics.csv.
What the measurement showed: the term becomes identified on 2017-10-17, and
for the five folds from there to 2018-11-06 its entire basis for distinguishing
a Republican from a Democratic presidency is between one and six special
elections --- races coded as a midterm-backlash electorate they are not. Only
from 2020-03-03, once the 71 races of the 2018 general enter the training
window, does the separating count reach 77. The variant's holdout falls from
413 races to 253 under the adopted definition and to 240 under
generals_only; its previously published figures over 413 races are superseded
by the refusal, not by a rescore.
Its replacement, baseline_timing, keeps all 413. See
docs/timing_result.md.
Resolved. Ballot timing is now one declared four-level categorical,
ballot_timing, with presidential as the reference; special elections take
their own level rather than being coded by the president's party.
Every special election in the record carries pres_elec = False, because none
has ever fallen on a presidential general date, and that produced three
consequences, none deliberate: any "non-presidential" figure blended midterm
generals with specials; specials were the only races separating pres_elec
from a per-date effect; and specials received a national_env value
describing an electorate they are not. Restricted to general elections there
are exactly three timing groups --- presidential ballot (231 races), midterm
under a Democratic president (271), midterm under a Republican one (71) ---
and pres_elec plus national_env saturated them, with the third group
resting on the single 2018 election.
What the measurement showed: baseline_timing beats baseline by
+0.771 [+0.394, +1.163] under the adopted definition and
+0.754 [+0.343, +1.169] under generals_only --- decided under both ---
and the gain is almost entirely the 71 races of 2018, where it is +4.265. It
narrows the presidential-date bias gap from 8.99 to 6.60 and halves pooled
bias, without the collinearity baseline_national_env bought its bias
reduction with. Against baseline_year it is undecided.
The scorecard's ballot_timing segment now takes its levels from the
predictor's declaration, so a segment row and a coefficient describing the
same population cannot disagree. midterm_general has split into
midterm_dem_pres and midterm_gop_pres; any figure quoted against the old
three-level names describes a different population.
baseline_year fits do not convergeResolved. Every fold under every definition now samples cleanly: zero
divergent transitions across all 40 fits, worst R-hat 1.0027, lowest bulk ESS
2436. The variant is adopted alongside baseline_national_env.
The fix is not the one this section originally proposed, and the way it was wrong is worth keeping. It suggested a non-centred parameterisation first --- but bambi builds group effects non-centred by default and the project never overrode it, so the sampler had always been running the recommended fix. There was no funnel to straighten.
The two real causes were:
1|election_year ~ Normal(0, HalfNormal(135)) from the intercept's scale,
against a response whose own standard deviation is 25.2 points, and asked it
to inform a between-year SD estimated from as few as four year groups. The
variant now declares HalfNormal(5).pres_elec collinear with the year grouping. pres_elec is a property
of a race's election date, not of its calendar year: it is computed as
election_date in PRESIDENTIAL_ELECTION_DATES, and a special election held
in a presidential year but on its own date carries False. What made it
collinear with a per-year intercept is narrower --- every special election
in the record falls off the presidential general date, so specials are the
only races that separate the two. Across 610 races pres_elec varies within
a year only in 2016 and 2020, on 8 races, all of them specials; in the
2010-2013 training window, not at all. The variant now drops it. The
refusal rests on that count measured per fold, not on the calendar.The improvement grew rather than shrank once the fits were clean: RMSE 14.209 against the superseded 14.503, a paired difference of +0.804 [+0.410, +1.179] against the superseded +0.510, and coverage holding at 0.927.
Those figures are themselves now superseded. Under the date schedule the
variant groups on election_date rather than election_year --- a calendar
year spans several fold dates, so a year-level effect would leave the holdout's
own level partially observed --- and posts 14.34, +0.663 [+0.283, +1.026],
coverage 0.935, still the best-calibrated variant scored. The bias gap
closes from 8.99 to -0.56, driven by the term removed rather than the one
added, since the baseline's fitted pres_elec coefficient was applying a
shift that miscalibrated out of sample.
baseline_national_env remains the only variant that can move a forward
prediction: a date intercept cannot shift a future date's mean, because that
date's effect is unobserved and drawn from the hyperprior, whereas the national
environment is known before the votes are cast. That argument is structural
and stands. Its measured advantage does not --- it is undecided under three
definitions of four, for reasons recorded in Open Issues above. The two answer
different questions and are reported together rather than ranked.
Three things carried forward from the fix:
fit_diagnostics.csv records what each fit actually ran under, so a result
obtained at a raised target_accept is distinguishable from one obtained at
the default.baseline_national_env carries national_env
alongside pres_elec for nine folds without complaint (see Open Issues).baseline_year is no longer nested in baseline, and its comparison is
labelled non-nested, naming the term added and the term removed.Full writeup: docs/variant_results.md.
24 holdout races at 24.77 RMSE against a pooled 15.01, with 0.708 interval coverage against a nominal 0.90. The model is overconfident about them, and no arm tested fixes it.
Three arms were scored --- specials pooled with an is_special term, pooled
without one, and split into a general model that never sees a special plus a
special model fit on everything. Pooled, the three are indistinguishable
under three definitions of four. The segments disagree consistently: both
distinguishing arms beat the plain one on specials and lose to it on midterm
generals, and the split arm beats both on presidential-date generals under
every definition.
Under the date schedule baseline_year is the only variant here that improves
the segment (23.68 RMSE, 0.792 coverage). baseline_national_env reverses ---
best of the four on the year schedule, worst now at 26.40 --- which is
consistent with the entanglement recorded above: every special gets a nonzero
national_env, describing an electorate it is not.
Whatever is left is a thin-data problem: 24 holdout races spread over 17 fold
dates is not much to diagnose from, and the arms differ by less than the Monte
Carlo noise between two fits of the same model. Full writeup:
docs/special_handling_result.md.
Four of the five definition comparisons came back undecided, so
two_party_or_strongest was adopted on the pre-registered tie-break rather
than because the data preferred it. That is the honest outcome at this holdout
size, but it means the decision is provisional: more cycles, or a variable that
interacts with the response definition, could settle it empirically. Every
superseded definition stays registered and scorable so the question can be
reopened without re-running collection.
The write-in threshold is provisional for the same reason. It sits at the ballot-line rule because the two races a 5% threshold admits score at 63.18 RMSE, not because a threshold is wrong in principle. If genuine write-in campaigns become more common, this is worth revisiting.
docs/forecasting.mdbaseline_year convergence failure is resolved; special elections and the provisional data definition remain.| 0.904 |
| 0.931 |
baseline_spend_logratio | Special elections | 22 | 19.06 | 0.727 | 0.773 |