Health insurance coverage and the ACA Medicaid expansion, US states 2009-2019
Source:R/data-medicaid.R
medicaid.RdA real public state-year panel of health insurance coverage for the 50 US states and the District of Columbia, 2009 to 2019, built from the American Community Survey (ACS) 1-year estimates of the US Census Bureau, with the date on which each state implemented the Affordable Care Act (ACA) Medicaid expansion to adults with incomes up to 138 percent of the federal poverty level. Adoption was staggered (2014, 2015, 2016, 2017, 2019, with ten states never adopting and several adopting after 2019), which makes it the standard teaching example for staggered difference-in-differences. The panel unit is the state and the time unit is the calendar year; the panel is balanced (51 x 11).
Format
A data frame with 561 rows (51 states x 11 years) and 20
columns. The panel index pair is c("state", "year").
- state
factor, state name (51 levels, District of Columbia included); the panel individual index
- year
integer calendar year of the ACS estimate (2009-2019); the panel time index
- abb
factor, two-letter postal abbreviation
- fips
integer state FIPS code
- region
factor, Census region (Northeast, Midwest, South, West)
- pop
integer civilian noninstitutionalized population (all ages), the universe of the health insurance tables (B27001)
- uninsured
numeric percent of the civilian noninstitutionalized population without health insurance coverage, all ages (B27001)
- adultpop
integer adults aged 18 to 64 (2009-2016) or 19 to 64 (2017-2019) for whom poverty status is determined (B27016); the ACS moved its age break from 18 to 19 in 2017, for all states at once
- uninsadult
numeric percent of
adultpopwithout health insurance coverage- medicaidadult
numeric percent of
adultpopwith Medicaid or other means-tested public coverage (alone or with other coverage)- povpop
integer adults (same ages) with family income below 100 percent of the poverty threshold
- povbase
integer
povpopof the state in 2013, the last year before the main expansion wave: a time-invariant weight, as needed byattgt()(weights constant within individuals)- uninspov
numeric percent of
povpopwithout health insurance coverage; the low-income outcome available in every year- medicaidpov
numeric percent of
povpopwith Medicaid or other means-tested public coverage- lowpop
integer adults (same ages) with family income below 138 percent of the poverty threshold, the ACA eligibility cut;
NAin 2009-2010, when the ACS tables cut at 100 and 150 percent- uninslow
numeric percent of
lowpopwithout health insurance coverage;NAin 2009-2010- adopt
integer expansion year: the first calendar year coded as treated,
NAfor the 10 states that had not adopted by October 2026. Convention of Miller, Johnson and Wherry (2021): the year of implementation if the expansion took effect before July 1, otherwise the following year (so Michigan, April 2014, is 2014; New Hampshire, August 2014, is 2015; Louisiana, July 2016, is 2017). Values after 2019 (Idaho and Utah 2020, Nebraska 2021, Missouri and Oklahoma 2022, North Carolina and South Dakota 2024) are kept: in this panel those states are never treated, andattgt()treats cohorts first treated after the last period as never treated- expdate
Date the expansion took effect (KFF);
NAif not adopted- expanded
integer 0/1 indicator that
year >= adopt- prior
factor coverage of low-income adults before 2014:
"full"for DE, DC, MA, NY and VT, which already covered adults up to the poverty line or more (Miller, Johnson and Wherry 2021, who drop them);"early"for CA, CT, MN, NJ and WA, which made limited early expansions in 2010-2012 under the ACA option or waivers (Sommers et al. 2013);"none"otherwise. A standard robustness check dropsprior != "none". Wisconsin is coded as a non-adopter but has covered adults up to 100 percent of poverty since 2014 through a waiver
Source
US Census Bureau, American Community Survey 1-year estimates, 2009-2019, detailed tables B27001 (health insurance coverage status by sex by age) and B27016 (health insurance coverage status and type by ratio of income to poverty level by age), Summary File, https://www2.census.gov/programs-surveys/acs/summary_file/. Works of the US federal government are in the public domain (17 U.S.C. 105); the Census Bureau states "You do not need permission to use American Community Survey data" (https://www.census.gov/help/topics/faq.how-do-i-cite-or-source-acs-data.html). Attribution: "Source: U.S. Census Bureau, American Community Survey 1-year estimates." This product is not endorsed or certified by the Census Bureau. Expansion dates: KFF, Status of State Action on the Medicaid Expansion Decision (State Health Facts), status as of October 2026, https://www.kff.org/affordable-care-act/state-indicator/state-activity-around-expanding-medicaid-under-the-affordable-care-act/.
Details
The ACS starts asking about health insurance in 2008, but the 2008
Summary File has no table by ratio of income to poverty nor a Medicaid
table, so the panel starts in 2009. It stops in 2019 because the Census
Bureau did not release standard 2020 ACS 1-year estimates (only
experimental ones), after the pandemic disrupted data collection.
The rates are computed from the published counts of tables B27001 and
B27016 (state level), so they reproduce the published percentages; see
data-raw/medicaid.R in the package's GitHub repository for the build
and its validation checks.
Teaching use
Did the expansion reduce uninsurance among low-income adults? Staggered DiD with
attgt()(treat = "adopt", never-treated or not-yet-treated controls), aggregated by cohort and event time withsummary.panglm_attgt(). Weights must be constant within a state: use the base-year populationpovbase(povpopin 2013).How large is the bias of the single-coefficient two-way fixed effects regression? Compare
did()(orpanglm()withmodel = "within",effect = "twoways"and theexpandeddummy) withattgt()andetwfe(): the effect grows with time since expansion, so early cohorts serve as controls for later ones.Are pre-trends parallel, and how does the effect build up?
eventstudy()with leads and lags, andsummary(type = "dynamic")ofattgt(). Louisiana (July 2016, coded 2017) is partly treated in 2016: compareanticipation = 1.Inference with 51 clusters: the TWFE coefficient from
panglm()withmodel = "within"andeffect = "twoways"has a classical standard error less than half the cluster-robust one (vcov = "cluster"), the default ofdid().Did Medicaid enrolment (
medicaidpov,medicaidadult) rise, and is the fall in uninsurance among all ages (uninsured) smaller, as expected from a policy aimed at low-income adults? Same estimators with other outcomes (a "first stage" and a dilution check).Robustness: drop the states with prior coverage (
prior != "none"), restrict to the ACA income cut (uninslow, 2011-2019), or use population weights versus none. Weighted fixed-effects fits are not available inpanglm(); a weighted TWFE ismodel = "pooling"with state and year dummies.
References
Miller, S., Johnson, N. and Wherry, L. R. (2021). Medicaid and mortality: new evidence from linked survey and administrative data. Quarterly Journal of Economics, 136(3), 1783-1829. doi:10.1093/qje/qjab004
Courtemanche, C., Marton, J., Ukert, B., Yelowitz, A. and Zapata, D. (2017). Early impacts of the Affordable Care Act on health insurance coverage in Medicaid expansion and non-expansion states. Journal of Policy Analysis and Management, 36(1), 178-210. doi:10.1002/pam.21961
Sommers, B. D., Arntson, E., Kenney, G. M. and Epstein, A. M. (2013). Lessons from early Medicaid expansions under health reform: interviews with Medicaid officials. Medicare and Medicaid Research Review, 3(4). doi:10.5600/mmrr.003.04.a02
Smith, J. C. and Medalia, C. (2015). Health Insurance Coverage in the United States: 2014. Current Population Reports P60-253, US Census Bureau.
Callaway, B. and Sant'Anna, P. H. C. (2021). Difference-in-differences with multiple time periods. Journal of Econometrics, 225(2), 200-230. doi:10.1016/j.jeconom.2020.12.001
Goodman-Bacon, A. (2021). Difference-in-differences with variation in treatment timing. Journal of Econometrics, 225(2), 254-277. doi:10.1016/j.jeconom.2021.03.014
Examples
data(medicaid)
str(medicaid)
#> 'data.frame': 561 obs. of 20 variables:
#> $ state : Factor w/ 51 levels "Alabama","Alaska",..: 1 1 1 1 1 1 1 1 1 1 ...
#> $ year : int 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 ...
#> $ abb : Factor w/ 51 levels "AK","AL","AR",..: 2 2 2 2 2 2 2 2 2 2 ...
#> $ fips : int 1 1 1 1 1 1 1 1 1 1 ...
#> $ region : Factor w/ 4 levels "Northeast","Midwest",..: 3 3 3 3 3 3 3 3 3 3 ...
#> $ pop : int 4616028 4702769 4720067 4741698 4755381 4767383 4781070 4782529 4793512 4810094 ...
#> $ uninsured : num 13.8 14.6 14.3 13.3 13.6 ...
#> $ adultpop : int 2832614 2895534 2907812 2902592 2910291 2898691 2892886 2882442 2814970 2807923 ...
#> $ uninsadult : num 19.9 21.2 21 20.1 20.3 ...
#> $ medicaidadult: num 9.79 10.13 9.94 10.94 10.95 ...
#> $ povpop : int 459298 508921 517524 510882 511039 526239 511475 467307 445307 447100 ...
#> $ povbase : int 511039 511039 511039 511039 511039 511039 511039 511039 511039 511039 ...
#> $ uninspov : num 41.6 44.1 42.8 42.8 41.4 ...
#> $ medicaidpov : num 30.5 28.6 27.4 29.1 29.4 ...
#> $ lowpop : int NA NA 749349 749124 741692 741218 720302 677523 643151 633544 ...
#> $ uninslow : num NA NA 41.8 40.1 40.1 ...
#> $ adopt : int NA NA NA NA NA NA NA NA NA NA ...
#> $ expdate : Date, format: NA NA ...
#> $ expanded : int 0 0 0 0 0 0 0 0 0 0 ...
#> $ prior : Factor w/ 3 levels "none","early",..: 1 1 1 1 1 1 1 1 1 1 ...
# adoption cohorts (first treated year; NA = not adopted by 2026)
table(unique(medicaid[, c("state", "adopt")])$adopt, useNA = "ifany")
#>
#> 2014 2015 2016 2017 2019 2020 2021 2022 2024 <NA>
#> 26 3 2 1 2 2 1 2 2 10
ix <- c("state", "year")
# Callaway-Sant'Anna ATT(g, t) for the uninsured rate of adults below
# the poverty line, never-treated controls, states weighted equally
cs <- attgt(uninspov ~ 1, data = medicaid, index = ix, treat = "adopt",
control = "never")
summary(cs)
#>
#> Callaway and Sant'Anna group-time ATT
#> Aggregation: overall (simple weighted average)
#> Control group: never treated Estimator: unconditional Individuals: 51
#>
#> ATT: -6.54 (SE 1.295, 95% CI -9.078 to -4.002)
#>
summary(cs, type = "dynamic") # leads test pre-trends, lags the build-up
#>
#> Callaway and Sant'Anna group-time ATT
#> Aggregation: event study (by time since treatment)
#> Control group: never treated Estimator: unconditional Individuals: 51
#>
#> ATT (mean of e >= 0): -6.562 (SE 1.317, 95% CI -9.143 to -3.98)
#>
#> event estimate se lower upper
#> -9 0.97529 0.8140 -0.62013 2.5707
#> -8 -0.09853 0.6997 -1.46999 1.2729
#> -7 1.16922 1.0103 -0.81093 3.1494
#> -6 -1.12859 1.1047 -3.29372 1.0365
#> -5 1.48015 0.7483 0.01347 2.9468
#> -4 -0.63116 0.4610 -1.53479 0.2725
#> -3 -0.29785 0.5167 -1.31050 0.7148
#> -2 0.40279 0.3635 -0.30965 1.1152
#> -1 -0.38889 0.5469 -1.46071 0.6829
#> 0 -5.15564 0.9010 -6.92165 -3.3896
#> 1 -6.39822 1.2670 -8.88146 -3.9150
#> 2 -7.08836 1.3521 -9.73834 -4.4384
#> 3 -7.59013 1.4220 -10.37726 -4.8030
#> 4 -6.52274 1.5487 -9.55810 -3.4874
#> 5 -6.61563 1.8257 -10.19386 -3.0374
#>
# weighted by the pre-expansion (2013) low-income adult population,
# with not-yet-treated controls
cs2 <- attgt(uninspov ~ 1, data = medicaid, index = ix, treat = "adopt",
control = "notyet", weights = "povbase")
summary(cs2)
#>
#> Callaway and Sant'Anna group-time ATT
#> Aggregation: overall (simple weighted average)
#> Control group: not yet treated Estimator: unconditional Individuals: 51
#>
#> ATT: -7.018 (SE 1.428, 95% CI -9.815 to -4.22)
#>
# extended TWFE and the single-coefficient TWFE for comparison (the
# latter can be biased under staggered adoption with dynamic effects)
ew <- etwfe(uninspov ~ 1, data = medicaid, index = ix, treat = "adopt")
summary(ew)
#>
#> Extended TWFE (Wooldridge), linear model, control group: not yet treated
#> Observations: 561 Individuals: 51 vcov: cluster
#> Fixed effects: cohort and period
#>
#> ATT (response scale): -7.116 (SE 1.4, 95% CI -9.86 to -4.373, p = 3.705e-07)
#>
tw <- did(uninspov ~ 1, data = medicaid, index = ix, treat = "adopt")
coef(tw)["treated"]
#> treated
#> -7.206276
# event-study regression with a joint pre-trend test
eventstudy(uninspov ~ 1, data = medicaid, index = ix, treat = "adopt",
leads = 4, lags = 5)
#>
#> Event study (two-way fixed effects, gaussian)
#> n = 561 groups = 51 vcov: cluster
#> Reference event time: -1 (end points binned)
#>
#> event estimate se lower upper
#> -4 0.7173 0.7083 -0.6709 2.106
#> -3 0.7217 0.5023 -0.2628 1.706
#> -2 0.2804 0.4149 -0.5327 1.094
#> -1 0.0000 0.0000 0.0000 0.000
#> 0 -5.5588 0.8460 -7.2170 -3.901
#> 1 -7.4610 1.1993 -9.8115 -5.110
#> 2 -7.6228 1.3074 -10.1853 -5.060
#> 3 -7.7790 1.5098 -10.7382 -4.820
#> 4 -6.5449 1.6404 -9.7601 -3.330
#> 5 -6.3989 2.0020 -10.3228 -2.475
#>
#> Pre-trend test: Chi-squared = 2.334, df = 3, p-value = 0.506
# first stage: Medicaid coverage of adults below the poverty line
summary(attgt(medicaidpov ~ 1, data = medicaid, index = ix,
treat = "adopt", control = "never"))
#>
#> Callaway and Sant'Anna group-time ATT
#> Aggregation: overall (simple weighted average)
#> Control group: never treated Estimator: unconditional Individuals: 51
#>
#> ATT: 10.24 (SE 1.406, 95% CI 7.48 to 12.99)
#>
# \donttest{
# the same TWFE coefficient with classical and cluster-robust SEs
fe <- panglm(uninspov ~ expanded, data = medicaid, index = ix,
model = "within", effect = "twoways")
fecl <- update(fe, vcov = "cluster")
cbind(classical = sqrt(diag(vcov(fe))), cluster = sqrt(diag(vcov(fecl))))
#> classical cluster
#> expanded 0.5604576 1.202028
# weighted TWFE: a pooled fit with state and year dummies
tww <- panglm(uninspov ~ expanded + state + factor(year), data = medicaid,
index = ix, model = "pooling", weights = "povbase",
vcov = "cluster")
coef(summary(tww))["expanded", ]
#> Estimate Std. Error z value Pr(>|z|)
#> -7.022593e+00 1.496188e+00 -4.693658e+00 2.683625e-06
# robustness: drop the states with pre-2014 coverage of these adults
summary(attgt(uninspov ~ 1, data = subset(medicaid, prior == "none"),
index = ix, treat = "adopt", control = "never"))
#>
#> Callaway and Sant'Anna group-time ATT
#> Aggregation: overall (simple weighted average)
#> Control group: never treated Estimator: unconditional Individuals: 41
#>
#> ATT: -8.878 (SE 1.255, 95% CI -11.34 to -6.418)
#>
# }