Validating against CDC's published estimates
Source:vignettes/articles/validation.Rmd
validation.RmdA data package asks to be trusted twice: that the bytes it serves are the bytes it published, and that estimates computed from them reproduce what the data’s owner reports. The first is mechanical and automatic here. The second is checkable in a few lines, and this article shows how, because the strongest validation is the one you run yourself.
The bytes
Every hosted file has a published sha256, twice over. The data
manifest carries a per-asset hash that read_brfss()
verifies on every download, and each release asset also has a plain
.sha256 sidecar next to it. Both records come from the same
hashing step at publish time, so the sidecar is a second copy, not a
second opinion. What it catches is drift: a manifest and an asset that
no longer agree after a partial republish, or either file corrupted in
transport or storage. A weekly integration job downloads real years
fresh, verifies the sidecars against the downloaded bytes, re-asserts
the recorded row counts, and computes the known-answer estimate below;
the package does not ship until that passes.
Reproducing a published prevalence
CDC publishes design-based BRFSS estimates in the Prevalence & Trends tool (https://www.cdc.gov/brfss/brfssprevalence/). For 2023, Health Status, the published share of adults reporting fair or poor general health is 19.4 percent. The same figure from this package:
library(brfssdata)
library(srvyr)
des <- brfss_design(2023, vars = "GENHLTH", quiet = TRUE)
des |>
summarize(fair_poor = survey_mean(GENHLTH >= 4, na.rm = TRUE, vartype = "ci"))
#> # A tibble: 1 × 3
#> fair_poor fair_poor_low fair_poor_upp
#> <dbl> <dbl> <dbl>
#> 1 0.194 0.191 0.196Two details make this line match the published figure. The design
carries the era-correct weight, strata, and PSUs, and
na = TRUE (the default here) has set the don’t-know and
refused codes to NA, so GENHLTH >= 4
divides fair-or-poor by substantive answers, which is the denominator
CDC uses. Run the same check on any measure you are about to build a
paper on; if you cannot reproduce the published number, resolve that
before going further.
Reliability screening
CDC suppresses BRFSS estimates built on fewer than 50 respondents or
with a relative standard error above 30 percent, and reviewers
increasingly expect the same screen. Both inputs come straight from a
srvyr summary, with unweighted() supplying the denominator
that suppression rules actually mean (respondents, not weighted
counts):
library(dplyr)
screened <- brfss_design(
2023,
vars = c("GENHLTH", "_AGE_G"),
states = "VT",
quiet = TRUE
) |>
group_by(`_AGE_G`) |>
summarize(
fair_poor = survey_mean(GENHLTH >= 4, na.rm = TRUE, vartype = c("se", "cv")),
n_unweighted = unweighted(sum(!is.na(GENHLTH)))
) |>
mutate(
suppress = n_unweighted < 50 | fair_poor_cv > 0.30
)
screened
#> # A tibble: 6 × 6
#> `_AGE_G` fair_poor fair_poor_se fair_poor_cv n_unweighted suppress
#> <dbl> <dbl> <dbl> <dbl> <int> <lgl>
#> 1 1 0.101 0.0219 0.217 256 FALSE
#> 2 2 0.105 0.0142 0.135 604 FALSE
#> 3 3 0.120 0.0176 0.146 910 FALSE
#> 4 4 0.135 0.0133 0.0981 1058 FALSE
#> 5 5 0.177 0.0154 0.0869 1672 FALSE
#> 6 6 0.162 0.0118 0.0725 3121 FALSEAnything flagged gets suppressed or footnoted, not reported as if it
were solid. The package deliberately leaves this as your code rather
than a function: the right denominator depends on the estimate (the
respondents actually contributing to it, after subsetting and missing
handling), and a convenience flagger fed the wrong n would
lend false authority exactly where caution is needed.
Intervals near zero
Wald intervals misbehave for proportions near 0 or 1, where much BRFSS subgroup work lives. The survey package provides the Korn-Graubard interval, which is what NCHS presentation standards call for:
library(survey)
svyciprop(~I(GENHLTH == 5), des, method = "beta", na.rm = TRUE)
#> 2.5% 97.5%
#> I(GENHLTH == 5) 0.0446 0.0432 0.0459The na.rm = TRUE is not optional on a design built with
na = TRUE. svyciprop() defaults to
na.rm = FALSE, and one NA anywhere in the
column carries through the whole calculation, so the estimate and both
limits come back NA with no warning to say why.
survey_mean() takes the same argument for the same
reason.
Compare the result with the symmetric interval from
survey_mean() for the same quantity; for prevalences under
about five percent, or any estimate whose Wald interval crosses zero,
report the Korn-Graubard one.
What to report
A reproducible BRFSS result states the weighted estimate with its
confidence interval and method, the unweighted number of respondents
behind it, the suppression rule applied, a one-sentence design statement
(final weight, strata, PSU, and the lonely-PSU handling, which this
package sets to "adjust" when the design contains a
single-PSU stratum), and the per-year data citation, which
brfss_citation() writes:
brfss_citation(2023)
#> Centers for Disease Control and Prevention (CDC) (2023). "Behavioral
#> Risk Factor Surveillance System Survey Data, 2023."
#> <https://www.cdc.gov/brfss/>.
#>
#> Masum M (2026). _brfssdata: Access CDC Behavioral Risk Factor
#> Surveillance System Data_. R package version 0.1.0,
#> <https://muntasirmasum.github.io/brfssdata/>.