Research Note · R-BAH.regression · Fitted 2026-05-21

What predicts BAH 5-year growth?

The Department of Defense publishes 14 years of Basic Allowance for Housing (BAH) by Military Housing Area — 349 installations across roughly 250 MHA codes. The question: across the 2021→2026 window, what local economic or geographic features predict an MHA's annualised BAH growth rate? We pre-registered five hypotheses, ran an OLS regression, and report the honest result — including the parts that did not work.

Sample
n = 177
unique MHAs (CONUS)
Target mean
6.33%
annualised BAH growth, 2021–2026, E5 w/ dep.
R² in-sample
0.240
adjusted 0.200, F-test p < 1e-5
R² out-of-sample
−0.27
held-out 20% (n_test = 35) — model does not extrapolate
Best predictor
ZHVI 5y CAGR
β = +0.0090, p < 0.001
v2 update
zip_master features tested · v1 still stands
Question & data
Pre-registered

What we measured

One row per Military Housing Area. Target = the 5-year compound annual growth rate of BAH (with-dependents, E5 tier) from 2021→2026, taken from DoD's annual rate releases. Features include the same MHA's Zillow rent index (ZORI) 5y CAGR, Zillow home value index (ZHVI) 5y CAGR, log troop count, years the installation has existed, latitude, longitude, and haversine distance to the nearest coastal city.

349 installations collapse to 177 unique CONUS MHA codes (many bases share an MHA — e.g. the National Capital Region has multiple installations under a single BAH rate). We drop overseas MHAs (codes starting with ZZ) because OHA, not BAH, governs them.

Source — DoD Defense Travel Management Office (BAH 2013-2026) · Zillow Research (ZORI, ZHVI) · DoD open rosters (lat/lon, troops, years_active)
Hypotheses (stated before running the model)
Five priors, two untestable
H1
BAH growth follows local rent growth (Zillow ZORI 5y CAGR), lagged.
If DoD appraisers track market rent, the same MHA's ZORI CAGR should positively predict its BAH CAGR.
supported  β_std = +0.0054 · p = 0.005 · bivariate r = +0.26
H2
BAH growth follows median household income growth (ACS).
Higher local income growth ⇒ higher rents ⇒ higher BAH.
untestable here  No per-MHA ACS series cached locally. Not fabricated. Follow-up data ticket.
H3
Coastal MHAs grew faster than inland MHAs.
Predicted: negative coefficient on dist_to_coast_km.
rejected (inconclusive)  β_std = −0.0003 · p = 0.89 · bivariate r = −0.07. Coast distance has essentially zero explanatory power once rent indices are controlled — the "coastal premium" is already in ZORI/ZHVI.
H4
BAH growth is higher in MHAs with growing population (Census).
Demand-side story for the rental market.
untestable here  No per-MHA Census population series cached locally. Follow-up data ticket.
H5
Larger garrisons (more troops) drive higher BAH growth.
More demand on the local rental market ⇒ landlords raise ⇒ BAH catches up.
rejected  β_std on log_troops = −0.0024 · p = 0.19 · bivariate r = −0.09. Wrong sign. BAH is set by local market rent, not by garrison size — the marginal Marine does not move the Camp Lejeune rent index. (Caveat: tested as level, not change-in-troops, because we only have a single snapshot.)
OLS coefficients (standardized features)
Load-bearing model
Feature β 95% CI low 95% CI high t p bivariate r
const+0.0641+0.0606+0.0676+36.30<0.001
zhvi_5y_cagr p<.001+0.0090+0.0052+0.0127+4.73<0.001+0.37
zillow_zori_5y_cagr p<.01+0.0054+0.0016+0.0091+2.850.005+0.26
log_troops n.s.−0.0024−0.0059+0.0012−1.330.186−0.09
abs_lat n.s.+0.0015−0.0021+0.0051+0.840.403+0.02
lon n.s.−0.0014−0.0052+0.0025−0.710.479+0.14
dist_to_coast_km n.s.−0.0003−0.0041+0.0035−0.140.887−0.07
years_active n.s.+0.0001−0.0036+0.0038+0.060.949−0.00

Read: a one-standard-deviation increase in ZHVI 5y CAGR is associated with a +0.90 percentage-point increase in BAH 5y CAGR (an MHA at mean+1σ of home-value growth grew BAH about 0.90 pp/yr faster than the average MHA). ZORI gives +0.54 pp/yr per sigma. Everything else is statistical noise.

Source — scripts/run_regression.py · regression_results.json
Fit quality — the honest part
In-sample is not out-of-sample

In-sample

R² (train, n=142)0.240
Adjusted R²0.200
F-test p-value3.8×10⁻⁶
MAPE train0.513

The model is jointly significant in-sample. Rent and home-value indices carry the load.

Out-of-sample

R² (test, n=35)−0.272
RMSE test0.0236
MAPE test0.436
Target std0.0230

Worse than predicting the unconditional mean. RMSE (0.024) is the same order as the target's std (0.023). The model has directional signal but cannot underwrite a specific MHA out-of-sample.

What this means for the WindMayor thesis
Partially supported, not proven
Limitations & what we did not do
No p-hacking
Reproduce
Single command

The regression is one Python script. It reads projects/milbase/web/data.json, builds the MHA panel in parallel threads, fits the OLS, and writes the JSON result file. No external network calls, no hidden state.

$ python3 projects/bah-research/scripts/run_regression.py
[data] kept 294 installations after CONUS+target filter (of 454 total)
[panel] 177 MHA rows
[fit] OLS on n_train=142 features=7

=== OLS coefficient summary (standardized) ===
  const                  beta=+0.0641  t=+36.30  p=0.000
  zillow_zori_5y_cagr    beta=+0.0054  t= +2.85  p=0.005 ***
  zhvi_5y_cagr           beta=+0.0090  t= +4.73  p=0.000 ***
  log_troops             beta=-0.0024  t= -1.33  p=0.186
  years_active           beta=+0.0001  t= +0.06  p=0.949
  abs_lat                beta=+0.0015  t= +0.84  p=0.403
  lon                    beta=-0.0014  t= -0.71  p=0.479
  dist_to_coast_km       beta=-0.0003  t= -0.14  p=0.887

  R^2 train = 0.2402
  R^2 test  = -0.2716
v2 — with the nationwide ZIP data spine
Re-run · Fitted 2026-05-21

What changed

v1 flagged H2 (income growth) and H4 (population growth) as untestable because no per-MHA ACS series were cached. The projects/zipdata/data/zip_master.json spine (31,646 ZCTAs, 13 datasets) closes that gap. v2 keeps the v1 features and adds: median HH income, total population, bachelor+ pct, ACS unemployment, BLS LAUS unemployment, IRS net migration per capita, HUD FMR 2BR, and median age — aggregated to the installation's feeder county.

Honest framing up front: zip_master holds latest cross-sectional values, not time series, for the new features. So H2/H4/H8 are tested as levels against BAH 5y CAGR, not as growth-on-growth. That's an honest re-formulation, not a re-write of the hypothesis — it is stated in the pre-registration in RESEARCH.md.

Headline

Metricv1v2 (main)v2 (joined-only)v2 (parsimonious)
n (MHAs)177177126177
n_train / n_test142 / 35142 / 35101 / 25142 / 35
features715153
R² train0.2400.2890.3180.222
R² test−0.272−0.510−0.265−0.133

Main v2 spec adds explanatory power in-sample (R² train 0.240 → 0.289) but loses ground out-of-sample — classic overfit with k=15 features on n_train=142. The parsimonious sensitivity (only the three features with |t|≥2 in the full v2) is the cleanest out-of-sample result and still negative.

Top 5 features by |t| in the v2 spec

FeatureβtpSource dataset
zhvi_5y_cagr p<.001+0.0076+3.820.000Zillow ZHVI time series
zillow_zori_5y_cagr p<.05+0.0049+2.500.014Zillow ZORI time series
log_income p<.05 (WRONG SIGN)−0.0096−2.190.031ACS B19013 (level)
z_bls_unemployment_pct p<.10 (WRONG SIGN)+0.0044+1.730.085BLS LAUS 2024 (level)
z_unemployment_pct_acs n.s.−0.0037−1.370.173ACS B23025 (level)

Hypothesis verdicts — v2

H1
BAH growth ↑ ZORI 5y CAGR
supported (re-confirmed)  β_std=+0.0049, p=0.014
H2_v2
BAH growth ↑ median HH income LEVEL
rejected (wrong sign)  β_std=−0.0096, p=0.031 — affordable/low-income MHAs saw BAH grow FASTER. Plausible: catch-up / Sun-Belt-affordable story. In the 3-feature parsimonious spec the effect disappears (p=0.59) → the "income" signal is collinear noise on ZHVI/ZORI.
H3
Coastal MHAs grew faster (control)
rejected (as in v1)  β_std=+0.0003, p=0.91
H4_v2
BAH growth ↑ population LEVEL
inconclusive  β_std=−0.0011, p=0.66 — market size doesn't add signal once ZHVI/ZORI control for it
H5
More troops ↑ BAH growth (control)
rejected (as in v1)  β_std=−0.0024, p=0.32
H6
BAH growth ↑ bachelor+ pct (educational attainment, NEW)
inconclusive  β_std=+0.0038, p=0.28 (sign right, not significant)
H7
BAH growth ↑ IRS net migration per capita (NEW)
inconclusive (wrong sign)  β_std=−0.0015, p=0.46 — IRS 2022 single-year migration is noisy at the county level
H8
BAH growth ↑ HUD FMR LEVEL (NEW)
inconclusive  β_std=+0.0031, p=0.47 — FMR growth (not level) would be the right test; we only have FY26 cross-section. Flagged.
H9
BAH growth ↑ when BLS unemployment is LOW (NEW)
rejected (wrong sign, weak)  β_std=+0.0044, p=0.085 — HIGHER unemployment areas saw HIGHER BAH growth, weakly. Likely an artifact of the same affordable-Sun-Belt story. Not robust in the parsimonious spec.

Verdict

Sensitivity table

Two sensitivity models reported alongside the main spec, both pre-stated as robustness checks, not re-specifications:

Source — scripts/run_regression_v2.py · regression_results_v2.json · zip_master.json