A verification companion to the US Atlas of Childhood Experiences, written for BRFSS data experts: every mapping, recode, formula, and threshold in the pipeline, with worked examples computed live from the same code and database that serve the Explore and Compare views. Numbers on this page are either imported from the production code or computed as you load it. The few hand-written claims are either re-derived by the project’s database-backed test suite, which is a release gate rather than a per-commit one, or dated measurements that say when they were taken (see versioning for exactly what is checked where).
Every statistic on the dashboard is computed from one fact table seeded from one committed file. The fingerprint below identifies exactly which data this page (and the dashboard) is describing right now; all numbers on this page are computed live from that table or imported from the code that computes them.
Source file
prisma/data/brfss-ace.csv.gz (committed via Git LFS), extracted from 32 CDC files: the main file of each of the 10 ACE module years, plus 22 questionnaire-version and landline files for the 41 split-sample state-years (see the pipeline)
SHA-256 of the seeded file
7b29cdedbc41f0affefd10d78fd6fc161aad8581954164cf253407272c0e687d After git lfs pull, shasum -a 256 prisma/data/brfss-ace.csv.gz must print this value; it is also the file’s Git LFS object id, so the file you pull is verifiably the file we seeded.
Rows loaded / skipped at seed
758,236 loaded, 0 skipped (skip rule: unknown state FIPS, or missing/non-positive survey weight; nothing else drops a row)
Transform version
5 in the database, 5 in the code (in sync)
Seeded at
2026-11-09T11:33:41.522Z
Participating states/territories
49 of 54 seeded jurisdictions have respondents
Suppression threshold
unweighted n < 30 suppresses a value (see the suppression section)
Income question eras
INCOME2 through 2020, INCOME3 from 2021 (see recodes)
Survey years in the table
Rows per year, the states and territories they come from, and the pooled survey weight each year contributes. How many jurisdictions fielded the ACE module changes from year to year, from 5 to 28, and that is most of why the respondent counts swing so widely: each year averages between 4,653 and 8,133 respondents per jurisdiction.
Every file’s weight is calibrated to its state’s whole adult population, whichever file a state-year was read from; the respondents who reached the ACE module carry most of that total, so each year’s weights sum to somewhat less than that year’s covered adult population. We pool them without renormalization, so the total weight is a person-years-style quantity of about 529,007,713 across 758,236 respondents, which should never be read as a population count.
The arbitrary scale cancels in every ratio we publish. What the pooling does decide is what each ratio averages over: a year enters in proportion to its own total weight, which is not its share of the respondents. In this seed the widest gap is 2020, which carries 21.8% of the pooled weight but 19.5% of the respondents. A state that fielded the module in 7 years therefore contributes far more weight than one that fielded it once, and the pooled figure is neither a single-year estimate nor an equal-weight average of the years. See the limitations register for what that means when comparing states.
Year
States and territories
Respondents (asked population)
Sum of weights (full precision)
2009
5
27,545
16050599.754872076
2010
11
57,703
34258199.93933551
2011
10
81,330
47687959.9332437
2012
5
32,824
20380761.34004659
2019
21
126,601
87657069.52163696
2020
28
147,616
115316853.11900589
2021
16
92,238
56855251.90099033
2022
12
65,348
45797414.407177955
2023
12
66,539
56384238.8929073
2024
13
60,492
48619364.089473605
What this audit found (and fixed)
Assembling this page in July 2026 surfaced a real extraction defect: the pipeline matched each CDC variable by a single name, but the CDC renames variables across years, so 14 columns were silently blank for whole year blocks. The worst case was the physical-abuse item, blank for all of 2009-2012, which made a complete ACE score impossible for 21% of the table and made six states (Louisiana, Minnesota, North Carolina, Oklahoma, Vermont, and Washington) appear to have no score data at all.
The fix (2026-07-30) repaired the alias lists, added era-aware recodes where a rename also changed response coding, and made the extractor refuse to write a CSV when a column resolves to nothing without a documented reason, so this class of defect now fails loudly instead of shipping silently. The scored population grew from 393,535 respondents (effectively 2019-2024) to 501,877 spanning 2009-2024, and the six states above went from N/A to real values.
What the recovered data changed, and the era-comparability questions it raised (a break in series in the physical-abuse item, a melanoma reclassification, definition drift in the drinking flags), are documented in the limitations register below; headline effects were modest (mean ACE score 1.634 to 1.605).
September 2026: the split-sample state-years
A second audit (2026-09-22) found that the dashboard had been missing ACE data CDC publishes outside its main annual files. When a state asks a module on only some versions of its questionnaire, CDC blanks those answers in the main file and publishes them in per-version files with their own weights, and in 2011 it did the same for modules asked on the landline survey only. The extractor read the main files alone, so 40 state-years never reached the dashboard, including every year of eight states (California, Kansas, Maine, Maryland, Massachusetts, Nebraska, New York, and Ohio). Oklahoma 2012 was loaded, but from the main file, where CDC had left its version-2 answers under the main-file weight: that weight summed to 1.34 million over those respondents, about half the state’s adults, where the version weight sums to 2.74 million.
The fix (2026-09-22) added a registry of which file each such state-year comes from (the last column of the pipeline’s source table), reads each from its version or landline file with that file’s own weight, rescales the two state-years asked on two versions, and makes the extractor stop with an error if any file it reads carries ACE answers the registry does not account for. Rows went from 557,014 to 758,236, jurisdictions with ACE data from 41 to 49, and state-years from 93 to 133; the scored population, 501,877 before, is 673,232 now (live). Every other state-year is unchanged row for row. Headline effects were again small (mean ACE score 1.605 to 1.600; one or more ACEs 64.45% to 64.40%), and the new state-years’ own caveats (subsample size, landline-only 2011 rows, California 2011’s missing incarceration item) are in the limitations register.
The pipeline, end to end
CDC download. The BRFSS public-use files (XPT inside ZIP) listed below. Each ACE module year has a main annual file, three questionnaire-version files and, in 2011-2012, a landline-only file. CDC also publishes three landline-only version files for 2012 (LAND12V1-V3), which the extractor does not read: CDC’s 2012 module tables put the ACE module on none of them, only on Iowa (every version) and Oklahoma (version 2), and both come from the files above. A state that asked the ACE module on only some versions of its questionnaire (a split sample) has its answers in those version files alone, blanked in the main file, and in 2011 the versions, like the landline file, cover the landline survey only. The main files for 2013-2018 are probed too but contain no ACE module variables, so they contribute zero rows.
Extraction (scripts/process-brfss.ts). Every state-year is read from exactly one source: its year’s main file, unless the split-sample registry (src/lib/brfss/split-sample.ts, the last column below) lists it, in which case its rows come from the listed version or landline file(s) only and its copies in every other file are ignored. That is what keeps a respondent from being counted twice: version files repeat the states that asked the module on every version, and the 2012 main file repeats Oklahoma’s version-2 answers under the main-file weight. Each row keeps its own file’s final weight. For the state-years asked on two versions (CA 2011 and NE 2011), each version’s weight is multiplied by that version’s share of the state-year’s ACE respondents, P_k = n_k / (n_1 + n_2) (CDC’s rule for combining versions), so the two together sum to one state population rather than two. The run stops with an error if any file it reads carries ACE answers the registry does not account for, or a listed state-year yields no rows; that is the only reason the files that supply nothing are read. Within a file, each standardized column takes the first alias present (see the variable mapping table). A respondent’s row is kept if any ACE item cell is non-blank: BRFSS codes a refusal or “don’t know” as 7 or 9 and leaves a never-asked item blank, so non-blank-anywhere is exactly the asked population, refusers included. The two 2021+ neglect items count for this test even though they are not scored.
Transform and seed (src/lib/brfss/transform.ts, prisma/seed.ts). Recodes every demographic and outcome, derives the ACE-8 score and category flags (see scoring), derives ace_module_asked by testing that all 11 scored item cells are non-blank on the raw strings (a replication must test missingness on the raw variable, not the recode, to keep refusers in the asked base), then bulk-COPYs into the respondents table. Only two rules drop a row entirely: unknown state FIPS, or a missing/non-positive weight.
Variable mappings and recodes
Extraction aliases
The CDC renames a variable whenever a question changes, so one concept can carry several names across 2009-2024. Each standardized column lists its CDC names newest first, and in each file it reads the extractor takes the first name present. A year’s version and landline files use that year’s names too, apart from the weight: the weight row lists the main files’ weights, and every other file is weighted by its own final weight instead (the Weight column of the pipeline’s source table). Where a rename also changed the response coding, the recode branches on year (see the outcome rules below) rather than pretending the codes are compatible. Years listed as “not asked” are documented absences; the extractor refuses to write a CSV if a column fails to resolve in any file of any other year, so a future CDC rename breaks the pipeline loudly instead of leaving a silently hollow column.
Our column
CDC names, newest first
Not asked in
state
_STATE
sex
SEXVAR, SEX1, SEX
age_group
_AGE_G
race
_RACE1, _RACE, RACE2
education
EDUCA
income
INCOME3, INCOME2
ace_depressed
ACEDEPRS
ace_alcoholic
ACEDRINK
ace_drugs
ACEDRUGS
ace_prison
ACEPRISN
ace_divorce
ACEDIVRC
ace_punch
ACEPUNCH
ace_hurt
ACEHURT1, ACEHURT
ace_swear
ACESWEAR
ace_touch
ACETOUCH
ace_touched
ACE-8 scoring
The 11 scored items collapse into the 8 categories CDC scores in its published BRFSS analyses (Merrick et al., JAMA Pediatrics 2018; Swedo et al., MMWR 2023), under three rules:
Item validity. A binary item is valid iff its code is 1 or 2, and endorsed iff 1. A frequency item is valid iff 1-3, and endorsed iff 2 or more (“Once” counts). A reverse protective item (the two 2021+ neglect items) is valid iff 1-5 and endorsed, meaning neglect present, iff 1 or 2 (Never or A little of the time), per CDC’s published cut.
Category flag. Null unless every item in the category is valid; true iff any item is endorsed. So one refused item nulls its whole category even when another item in it is a clear yes.
Score. Strict complete case: null unless all 11 items are valid; otherwise the count of endorsed categories, 0-8. No imputation, no partial scores.
The two 2021+ neglect items add two categories on top of the eight: Emotional Neglect = ace_safe; Physical Neglect = ace_needs. The expanded score is complete-case over all 13 items (0-10, mean only, no bands) and is scored separately, so the ACE-8 score never moves. Its basis is strictly smaller: the neglect items were fielded 2021-2024 by 29 of the 49 ACE jurisdictions, so expanded-score and neglect-indicator numbers are not comparable to the pooled metrics (see the limitations register). One computed contrast, live from the production scorers: a respondent answering No to all 11 pooled items who was never made to feel safe scores ACE-8 = 0 but ACE-10 = 1.
Worked scoring examples
Only the input vectors are authored; every flag and score below is computed by the production scoring functions as this page renders.
Example
Sexual Abuse
Statistic definitions
Let w be the survey weight: each respondent’s final weight from the file their state-year was read from (see the pipeline), unmodified except that a state-year read from two questionnaire-version files has each version’s weight multiplied by that version’s share of the state-year’s respondents (P_k = n_k / Σn), pooled over every year and state in scope with no renormalization. Every statistic is a ratio of two weighted sums from one scan, published as round(numerator / denominator, digits) unless suppressed. The table names the exact SQL aggregate aliases; the worked examples below show the statement that computes them and the live values.
Statistic
Numerator (Σw over ...)
Denominator (Σw over ...)
Suppression count
Digits
Average ACE score
score_ws
score_w
score_n
3
Average expanded ACE score (10 items)
score10_ws
score10_w
score10_n
3
Percent of adults with 0 ACEs
w_eq0
score_w
score_n
4
Percent of adults with 1+ ACEs
w_ge1
score_w
score_n
4
Percent of adults with 2+ ACEs
w_ge2
score_w
score_n
4
Percent of adults with 3+ ACEs
w_ge3
score_w
score_n
4
Percent of adults with 4+ ACEs
w_ge4
score_w
score_n
4
Emotional/Verbal Abuse
ace_emotional_abuse_s
ace_emotional_abuse_w
ace_emotional_abuse_n
4
Physical Abuse
ace_physical_abuse_s
ace_physical_abuse_w
ace_physical_abuse_n
4
Sexual Abuse
ace_sexual_abuse_s
ace_sexual_abuse_w
ace_sexual_abuse_n
4
Emotional Neglect
Suppression, rounding, display
The full chain for one value, in order:
The scan returns a float8 numerator, float8 denominator, and unweighted count n.
If n < 30 or the weighted denominator is not positive, the value is nulled. The count n is kept, so the UI can distinguish suppressed (n > 0, rendered “--”, interpret with caution) from no data (n = 0, rendered “N/A”). The gate reads the unweighted count; there is no relative-standard-error rule.
Otherwise deterministic half-up rounding: proportions to 4 decimals, the average score to 3 (a scaled Math.round, avoiding toFixed float drift).
Display: proportions x 100 to one decimal (“63.4%” from 0.63385 via 0.6339), the average to one decimal.
For replication: match the rounded API value, not the display string; your unrounded estimate should agree with our unrounded quotient to well under 5e-5 before rounding. The boundary behavior below is computed by calling the production makeCell() as this page renders:
Case
num
den
n
Cell value
Status
Rendered
n one below the threshold
60
100
29
null
suppressed
--
n exactly at the threshold
60
100
30
0.6
value
60.0%
large cell
60
100
5000
0.6
value
60.0%
no data at all
0
0
0
null
nodata
N/A
count without weight (defensive)
0
0
50
null
suppressed
--
Age-adjusted values (Compare view)
An age-adjusted value passes the same gate and one more. It is an average of six age-group ratios on fixed weights, so a cell with plenty of respondents overall can still rest on a handful in one age group that the standard counts heavily. Two extra conditions apply, and failing either renders “--” with the cell’s real n:
every age group in scope has at least one respondent with a valid answer, and
the estimate’s effective sample size (the size of a simple sample that would be as precise, defined in the age-adjustment formulas) is at least 30.
How a value becomes a color
Both maps are cartograms: one equal-size tile per jurisdiction, laid out in an approximate geography, so tile area carries no meaning and no state is visually larger than another. The color is the encoding. This section states the rule for each map, because a color is a published statistic like any other, and on the Compare map it is the only thing a classed tile shows.
Explore: a 3-class ramp in this scope, recomputed per view
Every state’s value for the selected metric is classed together. The domain is the observed [minimum, maximum] across the states currently on the map, cut by scaleQuantize into as many equal-width classes as the ramp has stops (3). Each cut is then snapped to what the tiles print: it moves to the half-step just above the last value a tile can print at or below it, and every state is classed by its printed value, so two tiles that read the same always share a color. If every state prints the same value there is nothing to cut, and a single stop carries the whole map, which is why the count above is 3 for the scope you are looking at. The table below is not a description of that arithmetic; it is the scale’s own cut points, read back out of the object the tiles are filled from. Three consequences a reader must not miss:
The breaks are relative, not absolute. They are recomputed whenever the metric, population, or demographic filter changes, so the same fill means different values in two different views, and colors are never comparable across screenshots.
The cut is by value, not by rank: classes hold equal value ranges rather than equal numbers of states, so a skewed distribution can leave a class nearly empty. Compare the widths in the table to see it.
The legend ticks the lowest and highest values the tiles print and, at each color change, the first value of the class above; the “Tiles reading” column below spells out each class’s range. Read the tile values, not the fills, to compare states. The respondents display, below, labels its cuts in a note instead. A tile rounds to one decimal; its hover probe and the selected-state card give one more (the national value reads 1.7 on a tile and 1.70 in a probe). Snapping keeps equal tiles together, but two states a hair apart still fall on either side of a boundary when their tiles print different values.
Live for the current scope (metric Average ACE score), classed over 49 jurisdictions:
Class
Fill
Hex
Tiles reading
Values from
up to
1
Worked examples, computed live
Everything below runs the production statements against the live database as this page renders, shows the raw intermediate aggregates, and re-derives the published value step by step. The intermediates obey the same publication rule as the values: weighted sums appear only for cells of at least 30 respondents, and a suppressed or empty cell shows its unweighted n and the decision but withholds the sums, because for a tiny cell the weighted numerator and denominator would be a single respondent’s own survey weight. Per-respondent weights never appear (this page’s one deliberate extension of the API’s “ratios and counts only” rule stops exactly there).
Reproduce it yourself
Get the files from the source table above: the main file of every ACE module year (2009-2012 and 2019-2024; 2010 and earlier use the CDBRFS naming), plus each questionnaire-version or landline file the table lists as supplying a state, 32 files in all.
Take each state-year from one file. A state-year comes from its year’s main file unless the source table lists it under a version or landline file; then take that state’s rows from those files only, and none from any other. Version files repeat the states that asked the module on every version, and the 2012 main file repeats Oklahoma’s version-2 answers under the main weight, so reading every file would count respondents twice. For the state-years asked on two versions (CA 2011 and NE 2011), multiply each version’s weight by that version’s share of the state-year’s respondents with any ACE answer, P_k = n_k / (n_1 + n_2), CDC’s rule for combining versions.
Read the variables per the alias table: in each file, the first listed candidate present. Weight: that file’s own final weight, in the source table’s Weight column (_LLCPWT or _FINALWT for a main file; a version or landline file carries only its own).
Row filter (asked population): keep respondents with any of the 13 ACE variables non-missing. In SAS/R, an XPT blank arrives as missing; codes 7 and 9 are not missing and keep the row. The module-asked flag additionally requires all 11 scored items non-missing (ACEDEPRS, ACEDRINK, ACEDRUGS, ACEPRISN, ACEDIVRC, ACEPUNCH, ACEHURT1, ACESWEAR, ACETOUCH, ACETTHEM, ACEHVSEX).
Apply our row drops: state FIPS outside our 54 seeded jurisdictions, or missing/non-positive weight (the provenance banner reports how many rows the seed skipped, for exact reconciliation).
Recode per the scoring and outcome tables above, exactly: complete-case ACE-8, divorce code 8 invalid, frequency endorsed at 2+.
Estimate: weighted ratios pooling all years, no renormalization. Your unrounded estimate should match our unrounded quotient (worked examples and the aggregates CSV) to well under 5e-5, then round as documented.
Do not expect standard errors to match anything: we publish none. The design variables (_STSTR, _PSU) are not extracted, so nothing on the dashboard carries design-based variance; the n < 30 suppression rule is the only reliability guard. Weighted point estimates are the comparison target.
Fast path: work from our extract
Skip the 32 downloads by using the committed prisma/data/brfss-ace.csv.gz (verify its SHA-256 against the provenance banner). Its header, rendered from the transform that reads it:
Cross-checks against published estimates
A sanity check, not a validation: given the enumerated design differences (year windows, state sets), agreement within a few points is the expected outcome, and exact agreement would itself be suspicious. Every “ours” value is computed live from the current seed (national, all respondents, no filters); published values were verified against the corrected primary sources on the retrieval date shown. Neither source age-adjusts: both report crude weighted estimates (re-read for this on 2026-11-09), as we do here, so no row compares an adjusted figure with a crude one.
50 states + DC, one survey year per jurisdiction (2011-2020, most 2019-2020), 8 ACE categories from 11 items, complete case, N = 264,882.
Close agreement expected. Swedo takes one year per jurisdiction across 50 states + DC; we pool each participating state's years with a smaller state set. We score the same 8 categories from the same 11 items, but pool 2009-2012 with 2019-2024, with most of the weight in the later years. The physical-abuse item reads about 9 points lower pre-2013 (a break in series; see the limitations register), which drags pooled score-based estimates slightly below a 2019-2024-only figure.
50 states + DC, one survey year per jurisdiction (2011-2020, most 2019-2020), 8 ACE categories from 11 items, complete case, N = 264,882.
Close agreement expected. Our window runs through 2024, when several items read higher than in Swedo's mostly 2019-2020 years, while our pre-2013 years pull the other way. We score the same 8 categories from the same 11 items, but pool 2009-2012 with 2019-2024, with most of the weight in the later years. The physical-abuse item reads about 9 points lower pre-2013 (a break in series; see the limitations register), which drags pooled score-based estimates slightly below a 2019-2024-only figure.
No standard errors, confidence intervals, or design effects appear anywhere; the design variables (_STSTR, _PSU) are never extracted. Every number is a weighted point estimate presented as exact, and the n < 30 suppression rule is the only reliability guard. Do not run significance tests against these values; return to the raw files for design-based variance.
2. Years are pooled with raw weights
Each state contributes every year it fielded the module (1 to 7 years across the 49 participating jurisdictions; the full state-year grid, with the file each state-year was read from, is in coverage.csv). Each file’s weight is calibrated to the whole state’s adults, whichever file a state-year comes from; the respondents who reached the module carry most of that total but not all (interviews that ended before it are excluded), and the state-years are pooled without renormalization, so state comparisons partially reflect when each state ran the module, and weighted totals are not population counts.
Years fielded, per jurisdiction (live; a year not read from the main file names its source)
Jurisdiction
Years in the data
Alabama
2019, 2020, 2021
Arizona
2020, 2022 from version 1, 2024 from version 2
Arkansas
2009, 2021, 2022
California
2011 from landline versions 1+2 (rescaled), 2020 from version 2
Delaware
2019, 2023
District of Columbia
2010, 2020
Florida
2019, 2020, 2022, 2023, 2024
Georgia
2020, 2023, 2024
Hawaii
2010, 2020, 2024
Idaho
2020
Indiana
2019
Iowa
2012, 2019, 2020, 2021, 2022, 2024 from version 1
Kansas
2019 from version 2, 2020 from version 2, 2021 from version 1
Kentucky
2020
Louisiana
2009
Maine
2010 from version 2, 2011 from landline version 1, 2021 from version 1, 2024 from version 1
Maryland
2020 from version 3, 2023 from version 3
Massachusetts
2020 from version 1
Michigan
2019
Versioning and change record
Every data payload (and this page) is keyed by an opaque version, currently 9: the id of the latest seed run. A reseed (new data, or a transform change that bumps TRANSFORM_VERSION) mints a new id, so a given versioned URL is immutable and two people looking at the same version are looking at identical numbers. The provenance banner’s SHA-256 and transform version pin what this version was built from.
The authoritative change record for the methodology is the git history of the extraction, transform and scoring modules (scripts/process-brfss.ts, src/lib/brfss/variable-mappings.ts, src/lib/brfss/split-sample.ts, src/lib/brfss/transform.ts, src/lib/brfss/scoring.ts, src/lib/queries/sql.ts). Every registry and formula rendered on this page is imported from that same code, so a rename or a changed rule reaches this page by construction rather than by anyone remembering to edit it.
What is checked, and where
Being precise about this matters more here than anywhere else on the page, because it is what a reader is entitled to assume when they take a number on trust:
Checked as this page renders. Every worked-example ledger recomputes its value from the raw scan row and compares it to the production API value for the same scope; a mismatch replaces the ledger with a failure notice rather than a number. The class breaks, the co-occurrence gap, the suppression boundary table, and every registry table are calls into the production functions themselves, so they cannot disagree with the dashboard.
Checked in continuous integration. The unit and component suite (npm test), which covers the pure modules behind those functions: scoring, recodes, suppression, formatting, classing, the URL codec, the CSV builders.
Checked only against a seeded database, and therefore not in CI. The claims that need real data (the worked-example equalities, the coverage footnotes’ empirically-zero cells, the cross-check tolerance bands, and those of the limitations register’s typed figures the database can re-derive) live in *.dbtest.ts and run via npm run test:db. CI has no database and does not run them. They are a release gate, not a per-commit one, so a data-dependent claim on this page can in principle go stale between reseeds; the render-time self-checks above are what stand behind the numbers in the meantime.
Not re-checked: dated measurements. A few register figures (the item-level physical-abuse panel, the codes 1-3 neglect cut, the _MICHD agreement count) need raw CDC item codes the database does not keep. Each names the extract it was measured on, and the test suite pins a live neighbour where one exists, so a reseed that moves it flags the dated figure for re-measurement.
November 2026: witnessed partner violence as its own category
Until November 2026 the score grouped the 11 items into 7 categories, not the 8 CDC scores. It counted ACEPUNCH, which asks how often the adults in the respondent’s home slapped, hit, kicked, punched or beat each other up, as physical abuse alongside ACEHURT, which asks whether a parent or adult in the home hit, beat, kicked or physically hurt the respondent. CDC’s published BRFSS analyses, including both sources in the cross-checks section, score ACEPUNCH as a household-dysfunction category of its own, witnessed intimate partner violence. The grouping appears to trace to the prototype’s data dictionary, which gave ACEPUNCH the wording of the physical-abuse question.
An outside review of the dashboard’s maps in November 2026 recommended scoring the two separately, for two reasons. First, they are different experiences: physical abuse is violence aimed at the child, while witnessed partner violence is violence between the adults the child lived with. Counting them as one category treated a direct exposure and a vicarious one as the same thing. Second, merging them squeezed the top of the score. A respondent who reported both counted once, so the score stopped at 7 and every such adult scored a point lower than CDC’s method scores them, which put fewer people in the four-or-more band than belong there.
The score now runs 0-8, and the expanded score with the two neglect items 0-10. On the same respondents, physical abuse went from 31.08% to 24.65% (CDC: 23.3%), and witnessed intimate partner violence reads 17.58% (CDC: 17.2%). Respondents with both experiences now score one point higher, so the mean ACE score rose from 1.600 to 1.704, and four or more ACEs from 15.67% to 17.62% (CDC: 17.3%). The share with at least one ACE did not change, since anyone who reported either item already counted once, and neither did who is scored. The figures earlier in this section are on the old 7-category score; the new ones here are live.
Query (src/lib/queries/sql.ts). One GROUPING SETS scan computes every weighted numerator, weighted denominator, and unweighted count the dashboard needs; there are no precomputed rollup tables. The worked examples below show the exact statements.
Shaping, suppression, rounding (src/lib/queries/shape.ts, suppress.ts). Numerator over denominator, suppression when the unweighted count is under the threshold, deterministic half-up rounding. Suppression happens only here, after aggregation; the database never stores a suppressed value.
Survey weight: each row’s own file’s final weight, in the Weight column below. _LLCPWT (2011 onward) or _FINALWT (2009-2010) for a main file; _FINALQn (2009-2010), _LNDWTVn (2011) or _LCPWTVn (2012 onward) for questionnaire version n; _LANDWT for the landline file. CDC weights each of them to the whole state’s adult population, cell-only adults included even for the landline-only 2011 files, so a state-year read from a version or landline file is on the same scale as one read from a main file. The rows we keep, the respondents who reached the ACE module, carry most of that total but not all of it: interviews that ended before the module are not in the data, so a state-year’s weights sum to somewhat less than its adult population, by more where more interviews broke off. Weights are stored raw apart from the two-version rescale. Years are pooled with no renormalization, no year filter exists anywhere in the query layer, and no design variables (_STSTR, _PSU) are extracted.
The one state-year CDC did not blank in the main file is Oklahoma 2012: LLCP2012 carries its version-2 answers under _LLCPWT, which projects those respondents onto only about half the state’s adults. The same respondents are taken from LLCP12V2 with _LCPWTV2 instead, as CDC’s 2012 guidance directs.
Source files
41 state-years come from a version or landline file; the last column names them.
Percent of that year’s respondents with a non-null recoded value, computed live from the database (for ace_module_asked, percent with the flag true). A 0.0 cell means the column is entirely absent for that year; every such cell carries a footnote. The practical consequence: each statistic has its own implicit year window, and therefore its own state mix.
Column
2009
2010
2011
2012
2019
2020
2021
2022
2023
2024
sex
100.0
100.0
100.0
100.0
100.0
100.0
100.0
100.0
100.0
100.0
age_group
100.0
100.0
100.0
100.0
100.0
100.0
100.0
100.0
100.0
100.0
race
99.1
98.6
99.2
99.3
98.2
98.1
97.9
97.4
98.0
98.5
education
99.8
99.8
99.6
99.7
99.7
99.7
99.6
99.6
99.6
99.7
income
86.2
88.3
88.7
83.8
83.4
83.0
80.4
81.5
81.4
83.1
ace_score
90.8
93.2
80.5
89.0
90.5
89.8
88.3
88.6
88.0
90.4
ace_score_10*
0.0
0.0
0.0
0.0
0.0
0.0
87.6
87.8
87.2
89.6
ace_module_asked
99.7
99.8
86.2
99.6
99.4
99.3
99.1
99.0
98.9
99.0
ace_emotional_abuse
96.1
98.2
96.2
94.3
97.3
96.6
96.0
96.0
96.5
97.3
ace_physical_abuse
96.6
98.9
96.9
94.9
97.9
97.1
96.5
96.6
97.0
97.6
ace_sexual_abuse
95.4
98.0
95.9
93.8
96.9
96.1
95.3
95.4
95.7
96.6
ace_emotional_neglect*
0.0
0.0
0.0
0.0
0.0
0.0
96.9
96.7
97.3
97.6
ace_physical_neglect*
0.0
0.0
0.0
0.0
0.0
0.0
97.0
96.8
97.3
97.7
ace_witnessed_violence
96.0
98.1
96.3
94.1
97.3
96.6
96.1
96.2
96.5
97.1
ace_household_depression
96.8
98.9
96.9
95.2
98.6
97.8
97.3
97.3
97.8
98.6
ace_incarceration
97.4
99.5
85.8
95.5
99.1
98.3
97.9
97.9
98.4
98.9
ace_substance_abuse
96.9
98.8
96.9
95.1
98.5
97.7
97.2
97.3
97.8
98.4
ace_divorce
96.5
98.8
96.8
94.2
97.3
96.6
96.0
96.2
96.1
97.1
depression*
0.0
41.1
99.6
99.6
99.5
99.5
99.5
99.4
99.4
99.5
frequent_mental_distress
98.7
98.6
98.5
98.4
97.7
98.1
98.2
97.9
98.0
98.3
fair_poor_health
99.8
99.7
99.7
99.7
99.7
99.8
99.7
99.7
99.7
99.7
frequent_physical_distress
98.1
98.0
98.1
97.8
97.3
97.9
97.8
97.5
97.3
97.8
diabetes
99.9
99.9
99.9
99.8
99.8
99.8
99.8
99.8
99.8
99.8
heart_disease
99.2
98.9
99.2
99.3
99.0
99.1
98.9
98.9
98.9
98.9
stroke
99.7
99.7
99.7
99.7
99.7
99.7
99.7
99.6
99.6
99.7
asthma
99.7
99.6
99.7
99.7
99.7
99.7
99.6
99.6
99.6
99.7
copd*
0.0
0.0
99.5
99.5
99.4
99.5
99.5
99.5
99.5
99.5
kidney_disease*
0.0
0.0
99.7
99.7
99.6
99.6
99.5
99.5
99.5
99.6
arthritis
99.2
7.4
99.4
99.5
99.4
99.5
99.3
99.4
99.4
99.4
skin_cancer*
0.0
0.0
99.7
99.8
99.7
99.7
99.7
99.1
99.2
99.3
other_cancer*
0.0
0.0
99.8
99.8
99.7
99.7
99.7
99.3
99.4
99.4
current_smoking
99.6
99.5
99.6
98.8
99.4
99.2
99.2
99.1
99.2
99.3
obesity
95.8
96.3
95.3
94.5
94.1
93.3
92.7
93.6
93.7
95.0
heavy_drinking
98.0
98.3
97.6
97.1
98.0
97.8
97.8
97.9
98.4
98.4
binge_drinking
98.7
99.0
97.7
97.1
97.9
97.8
97.9
97.6
98.3
98.4
no_insurance
99.8
99.8
99.7
99.7
99.5
99.6
96.4
96.6
95.5
96.7
cost_barrier
99.9
99.8
99.8
99.8
99.7
99.7
99.7
99.7
99.6
99.7
Why the zero cells are zero
ace_score_10 (2009, 2010, 2011, 2012, 2019, 2020)
The two neglect items (ACEADSAF, ACEADNED) were added to the module in 2021, so nothing derived from them exists for earlier ACE years (documented in variable-mappings.ts absentYears).
The two neglect items (ACEADSAF, ACEADNED) were added to the module in 2021, so nothing derived from them exists for earlier ACE years (documented in variable-mappings.ts absentYears).
The two neglect items (ACEADSAF, ACEADNED) were added to the module in 2021, so nothing derived from them exists for earlier ACE years (documented in variable-mappings.ts absentYears).
depression (2009)
BRFSS carried no depression-diagnosis question in 2009; ADDEPEV first appears in 2010 (as an optional module; see the limitations register).
copd (2009, 2010)
BRFSS did not ask this before 2011 (documented in variable-mappings.ts absentYears).
kidney_disease (2009, 2010)
BRFSS did not ask this before 2011 (documented in variable-mappings.ts absentYears).
skin_cancer (2009, 2010)
BRFSS did not ask this before 2011 (documented in variable-mappings.ts absentYears).
other_cancer (2009, 2010)
BRFSS did not ask this before 2011 (documented in variable-mappings.ts absentYears).
Demographic recodes
Sex (SEXVAR, SEX1, SEX)
Raw code
Database label
Display label
1
male
Male
2
female
Female
Age (_AGE_G)
Raw code
Database label
Display label
1
18_24
18-24
2
25_34
25-34
3
35_44
35-44
4
45_54
45-54
5
55_64
55-64
6
65_plus
65+
Race/Ethnicity (_RACE1, _RACE, RACE2)
Raw code
Database label
Display label
1
white
White, non-Hispanic
2
black
Black, non-Hispanic
3
aian
American Indian/Alaska Native
4
asian
Asian, non-Hispanic
5
nhpi
Native Hawaiian/Pacific Islander
6
other
Other race
7
multiracial
Multiracial
8
hispanic
Hispanic
Education (EDUCA)
Raw code
Database label
Display label
1
never_attended
Never attended school
2
elementary
Elementary (Grades 1-8)
3
some_high_school
Some high school
4
high_school_grad
High school graduate/GED
5
some_college
Some college
6
college_grad
College graduate
Income (INCOME3, INCOME2)
Raw code
Database label
Display label
1
under_10k
<$10K
2
10k_15k
$10-15K
3
15k_20k
$15-20K
4
20k_25k
$20-25K
5
25k_35k
$25-35K
6
35k_50k
$35-50K
7
50k_75k
$50-75K
8
75k_plus
$75K+
8
75k_100k
$75-100K
9
100k_150k
$100-150K
10
150k_200k
$150-200K
11
200k_plus
$200K+
Race is era-switched on the row’s own year, and the tables above show the 2013+ codes. RACE2 (2009-2012) and _RACE (2013+) both carry eight categories, but three of them sit at different raw codes, so for a 2009-2012 row the extractor remaps before recoding. Every other code, and every other dimension, is era-free.
Raw RACE2 code (2009-2012)
Reads as _RACE code
Database label
Display label
3
4
asian
Asian, non-Hispanic
4
5
nhpi
Native Hawaiian/Pacific Islander
5
3
aian
American Indian/Alaska Native
A replication that reads RACE2 straight onto the _RACE scale relabels Asian, Native Hawaiian/Pacific Islander and American Indian/Alaska Native respondents as each other for every 2009-2012 row.
Codes not listed above become null (the row is kept; the respondent drops out of that dimension’s breakdowns): race 9, education 9, income 77/99, and any other out-of-range code. Income is era-switched on the row’s own year: through 2020 the question is INCOME2 and code 8 means 75k_plus; from 2021 it is INCOME3 and code 8 means 75k_100k, with codes 9-11 covering the finer top buckets. The two code-8 meanings are stored as distinct labels and never unioned, so filtering on 75k_plus implicitly selects only 2009-2020 respondents and the fine buckets select only 2021+ respondents.
ACE items
Our item
BRFSS variable
Type
Valid codes
Endorsed codes
Category (the last two are 10-item only)
ace_depressed
ACEDEPRS
binary (1 = Yes, 2 = No)
1, 2
1
Household Mental Illness
ace_alcoholic
ACEDRINK
binary (1 = Yes, 2 = No)
1, 2
1
Household Substance Abuse
ace_drugs
ACEDRUGS
binary (1 = Yes, 2 = No)
1, 2
1
Household Substance Abuse
ace_prison
ACEPRISN
binary (1 = Yes, 2 = No)
1, 2
1
Household Member Incarcerated
ace_divorce
ACEDIVRC
binary (1 = Yes, 2 = No)
1, 2
1
Parental Separation/Divorce
ace_punch
ACEPUNCH
frequency (1 = Never, 2 = Once, 3 = More than once)
1, 2, 3
2 or 3
Witnessed Intimate Partner Violence
ace_hurt
ACEHURT1, ACEHURT
frequency (1 = Never, 2 = Once, 3 = More than once)
1, 2, 3
2 or 3
Physical Abuse
ace_swear
ACESWEAR
frequency (1 = Never, 2 = Once, 3 = More than once)
1, 2, 3
2 or 3
Emotional/Verbal Abuse
ace_touch
ACETOUCH
frequency (1 = Never, 2 = Once, 3 = More than once)
1, 2, 3
2 or 3
Sexual Abuse
ace_touched
ACETTHEM
frequency (1 = Never, 2 = Once, 3 = More than once)
1, 2, 3
2 or 3
Sexual Abuse
ace_sex
ACEHVSEX
frequency (1 = Never, 2 = Once, 3 = More than once)
1, 2, 3
2 or 3
Sexual Abuse
ace_safe
ACEADSAF
reverse protective (1 = Never ... 5 = All of the time)
1-5
1 or 2 (neglect present)
Emotional Neglect
ace_needs
ACEADNED
reverse protective (1 = Never ... 5 = All of the time)
1-5
1 or 2 (neglect present)
Physical Neglect
ACEDIVRC code 8 (“parents were never married”) is treated as invalid, not as “no separation or divorce”: it nulls the divorce category and, because scoring is complete-case, the whole ACE-8 score for that respondent. This matches the handling in Swedo et al. (MMWR 2023) but is a choice a replication must copy exactly.
Frequency items are endorsed at code 2 (“Once”): a single occurrence counts.
The 2021+ neglect items are reverse-coded protective questions (an adult “made you feel safe and protected” / “tried hard to make sure your basic needs were met”), so neglect present is the LOW end of the scale: codes 1 (Never) or 2 (A little of the time), per CDC’s published cut. Code 3 (“Some of the time”) is deliberately neither pole. They feed the two neglect indicators and the expanded 10-item score only; the ACE-8 score never touches them. They also count toward the asked-population test.
ACEPUNCH (adults in the home hitting each other) is its own category, witnessed intimate partner violence, as in CDC’s published 8-category scoring. It is not part of physical abuse, which is ACEHURT/ACEHURT1 alone. Versions of this dashboard before November 2026 folded the two together (see the audit section). The expanded 10-item score adds the two neglect categories on their restricted 2021+ basis; see the scoring section and the limitations register.
Health-outcome recodes
Every outcome is recoded to case / non-case / excluded (null). Excluded respondents leave both the numerator and the denominator of that outcome’s statistic; they are never counted as “no”. Each rule below is machine-checked against the recode functions in CI.
Days recode first: 88 = 0 days; 1-30 kept; 77, 99, blank = excluded. Case iff recoded days >= 14.
Diabetes
DIABETE4, DIABETE3, DIABETE2
1 = case; 2 (yes, only during pregnancy), 3 (no), 4 (pre-diabetes/borderline) = non-case; 7, 9, blank = excluded.
Heart Disease
_MICHD, CVDINFR4, CVDCRHD4
Reported MI or CHD. CDC's calculated _MICHD (2019+) wins where present: 1 = case, 2 = non-case. For 2009-2012, where CDC did not compute _MICHD, its own definition is applied to its two inputs: CVDINFR4 (heart attack) or CVDCRHD4 (angina/CHD) = 1 -> case; both = 2 -> non-case; anything else excluded. The derivation agrees with _MICHD on every row where both exist.
1 = case; 2 = non-case; 7, 9, blank = excluded (null). Note: from 2022 this item asks only about skin cancer that is not melanoma, so it is not comparable across 2022.
Other Cancer
CHCOCNC1, CHCOCNCR
1 = case; 2 = non-case; 7, 9, blank = excluded (null). Note: from 2022 this item names melanoma explicitly, previously counted under "skin cancer", so it is not comparable across 2022.
Current Smoking
SMOKE100, SMOKDAY2
smoking_status 2 (never smoked 100 cigarettes) = non-case; smoking_status 1 and smoking_current 1 (every day) or 2 (some days) = case; smoking_status 1 and smoking_current 3 (not at all) = non-case; every other combination = excluded.
Obesity
_BMI5CAT, _BMI4CAT
Calculated BMI category, era-aware. 2011+ (_BMI5CAT): 1-4 valid, 4 (BMI >= 30) = case. 2009-2010 (_BMI4CAT): only three levels, 3 (BMI >= 30) = case; reading it on the _BMI5CAT scale would misfile every obese respondent in those years.
Calculated risk flag with inverted coding: 1 = No (non-case); 2 = Yes (case); 9, blank = excluded. CDC's drink-count thresholds behind this flag changed across its renames (_RFDRHV3 through _RFDRHV9) even though the response codes never did, so the definition is not constant across years.
Binge Drinking
_RFBING6, _RFBING5, _RFBING4
Calculated risk flag with inverted coding: 1 = No (non-case); 2 = Yes (case); 9, blank = excluded. As with heavy drinking, the underlying definition shifted across CDC's renames (_RFBING4 through _RFBING6).
Era-aware. Through 20227, 9, blank = excluded. 2023+ have no yes/no item, only the PRIMINS source-of-coverage scale: 1-10 name a source (non-case), 88 = none (case), 77/99 excluded. The collapse reproduces CDC's own calculated _HLTHPLN exactly where both exist.
Couldn't Afford Care
MEDCOST1, MEDCOST
Needed to see a doctor but could not because of cost. 1 = case; 2 = non-case; 7, 9, blank = excluded.
The 10 ACE indicator columns referenced throughout are ace_emotional_abuse, ace_physical_abuse, ace_sexual_abuse, ace_emotional_neglect, ace_physical_neglect, ace_witnessed_violence, ace_household_depression, ace_incarceration, ace_substance_abuse, ace_divorce.
Physical Abuse
Witnessed Intimate Partner Violence
Household Substance Abuse
Emotional/Verbal Abuse
Household Mental Illness
Household Member Incarcerated
Parental Separation/Divorce
Score
All items answered No/Never
No
No
No
No
No
No
No
No
0
Household depression, substance use, sworn at more than once
No
No
No
Yes
Yes
Yes
No
No
3
Touched once (code 2)
Yes
No
No
No
No
No
No
No
1
Hit by an adult once, saw adults hit each other more than once
No
Yes
Yes
No
No
No
No
No
2
Parents never married (ACEDIVRC code 8)
No
No
No
No
No
No
No
null
null
Refused the incarceration item (code 7)
No
No
No
No
No
No
null
No
null
Physical-abuse item never asked (blank)
No
null
No
No
No
No
No
No
null
All items answered No/Never: All 11 items valid, nothing endorsed: score 0 (a real zero, not missing).
Household depression, substance use, sworn at more than once: Three categories endorsed: score 3.
Touched once (code 2): Frequency items are endorsed at 2 (Once), so sexual abuse counts: score 1.
Hit by an adult once, saw adults hit each other more than once: Two separate categories, physical abuse (ACEHURT) and witnessed intimate partner violence (ACEPUNCH), as CDC scores them: score 2. Until November 2026 this dashboard counted both as physical abuse and scored this respondent 1.
Parents never married (ACEDIVRC code 8): Code 8 is invalid, not No: the divorce flag is null and the complete-case score is null even though the other 10 items are valid.
Refused the incarceration item (code 7): Refusal nulls that category and the score; the other seven categories keep their flags.
Physical-abuse item never asked (blank): One blank item nulls its category and the score. An extraction gap once made this true for every 2009-2012 respondent; the audit behind this page caught and fixed it.
ace_emotional_neglect_s
ace_emotional_neglect_w
ace_emotional_neglect_n
4
Physical Neglect
ace_physical_neglect_s
ace_physical_neglect_w
ace_physical_neglect_n
4
Witnessed Intimate Partner Violence
ace_witnessed_violence_s
ace_witnessed_violence_w
ace_witnessed_violence_n
4
Household Mental Illness
ace_household_depression_s
ace_household_depression_w
ace_household_depression_n
4
Household Member Incarcerated
ace_incarceration_s
ace_incarceration_w
ace_incarceration_n
4
Household Substance Abuse
ace_substance_abuse_s
ace_substance_abuse_w
ace_substance_abuse_n
4
Parental Separation/Divorce
ace_divorce_s
ace_divorce_w
ace_divorce_n
4
ACE module completion
score_w
asked_w
asked_n
4
Aggregate glossary (aliases as they appear in the SQL):
score_n, score_w, score_ws
Unweighted count, Σw, and Σ(w x score) over respondents with a complete ACE-8 score (the “scored base”). The average score is score_ws / score_w.
w_eq0, w_ge1 ... w_ge4
Σw over scored respondents with score = 0 and score >= 1..4. The score bands are cumulative and share the scored-base denominator: “score 0” and “1 or more” sum to 1, but the 1+/2+/3+/4+ bands are nested, not a partition.
<indicator>_n, _w, _s
Per ACE indicator: unweighted count and Σw over respondents whose category flag is non-null (that indicator’s own valid-response base), and Σw over flag-true. Each indicator has its own denominator; the 10 prevalences are not on a common base and do not sum to anything meaningful.
score10_n, score10_w, score10_ws
The same three quantities over respondents with a complete expanded 10-item score (all 13 items valid, neglect included). Always a subset of the 8-item scored base: the neglect items exist only for 2021+ in the states that fielded them, so this mean has its own, smaller denominator and can read no data where the 8-item score publishes. Mean only; the expanded score has no cumulative bands.
asked_n, asked_w
Unweighted count and Σw over respondents asked all 11 scored items (refusers included). Module completion is score_w / asked_w: the share of the asked population that produced a complete score.
Population semantics
An ACE-indicator population narrows the scan itself, adding WHERE <flag> IS TRUE. “Any ACE score” adds no WHERE clause at all.
A score-group population is the one case that is neither, because the same band is doing two jobs. Its own map metric is the share of the scored base in that band, so WHERE ace_score >= 4 would pin it at a trivial 100% in every state. Everything else on screen is a rate within the band. So the band is applied as a FILTER conjunct rather than a WHERE: the score family (the mean and the cumulative bands) keeps scanning the unnarrowed scored base, while every indicator prevalence, Compare y variable and co-occurrence arm carries AND ace_score >= 4 on both its numerator and its denominator. A replication that row-filters to score >= 4 before computing indicator prevalence will match this view; one that also recomputes the “percent with 4 or more” off that filtered set will not, and should read it off the full scored base instead. The exact statement for the current scope, bands included, is in the worked example below.
Compare-view y variables
A health outcome: Σw(outcome true) / Σw(outcome non-null), each outcome on its own valid-response base.
Any outcome: numerator is “at least one of the 19 flags true”; denominator is “at least one of the 19 flags non-null”.
No outcome: numerator is “at least one flag non-null AND none true” over the same denominator. Note the asymmetry: a respondent with one valid “no” and 18 nulls counts as having no outcome.
The era-safe buckets: less than high school (education in never_attended, elementary, some_high_school over education non-null) and household income under $35K (the five sub-35k labels over income non-null). There is deliberately no $100K+ bucket: pre-2021 income is topcoded at $75K+.
Module completion: score non-null over ace_module_asked, only offered for the unnarrowed “Any ACE score” population (an ACE population would condition on having answered the category whose nonresponse is being measured).
Age adjustment (Compare view only)
Explore publishes crude estimates. Compare publishes age-adjusted estimates by default, with crude one switch away (adj=crude), because age moves both of the things Compare sets side by side: older adults report fewer ACEs and have more long-term conditions, and jurisdictions differ in how old their adults are (limitation 5 below). The method is direct standardisation:
Split the scope into the six age groups the data carries (_AGE_G).
Compute each ratio within each age group, on that ratio’s own valid-response denominator, with the survey weights: Σw(numerator) / Σw(denominator), exactly as the crude statistic does.
Average the six ratios with one fixed set of weights, the standard population, the same for every jurisdiction and for both co-occurrence arms: adjusted = Σa popa · ratioa / Σa popa.
The standard is the 2000 U.S. projected population (Klein and Schoenborn, Healthy People Statistical Notes no. 20, NCHS 2001, Table 2), the standard CDC uses for age-adjusted BRFSS prevalence. Its published Distribution #9 merges ages 45-64; the two ten-year counts below are the same table’s rows and sum to that group.
Age group
Standard population (thousands)
Weight
18-24
26,258
12.9%
25-34
37,233
18.3%
35-44
44,659
21.9%
45-54
37,030
18.2%
55-64
23,961
11.8%
65+
34,710
17.0%
What a replication must copy:
Every Compare ratio is adjusted separately: x, y, and each co-occurrence arm, each on its own denominator. Do not rescale the survey weight once and reuse it: that is only exact when item nonresponse is the same at every age.
Filters apply first. The filtered group is what gets standardised. Under an age filter the standard is applied within the selected age groups only, and with a single age group selected the adjusted value equals the crude one.
Counts are not adjusted. The published n is the plain respondent count behind the cell, the same under either kind of estimate.
Module completion is never adjusted. As a y variable it is returned crude even when x is adjusted: it measures how the interviews went, not a rate to compare on an equal age footing.
An extra suppression gate applies to adjusted values (see the suppression section): every age group in scope must have respondents, and the estimate’s effective sample size, (Σ popa)² / Σ(popa² / na), must reach 30.
An adjusted value is a comparison, not a head count. It answers what a group would read with the standard age mix. Because that standard is younger than today’s adults, adjusted figures for conditions of older age read lower than crude ones, and ACE measures read higher.
Co-occurrence
ACE accumulation split by y. For each cumulative band, 1+ through 4+, the card publishes Σw(score at or above the band) / Σw(score non-null) twice: once among respondents with the y variable, once among those without it. “Without” is a valid y response that is not positive (depression IS FALSE; for Any outcome it is the No outcome definition, and the reverse). Respondents with no complete score or no valid y are on neither side, and each side’s four bands share one denominator and one n, so a side suppresses all four rows together.
Two adjustments keep the rows from being true by definition. When y is itself an ACE the score counts (every indicator but the two neglect items), the bands threshold ace_score - <y flag>::int, the count of the other ACEs, on both sides: otherwise everyone with y would read 100% at 1+. And a band the population already decides is not shown, because it is the same on both sides by construction: 1+ under an ACE population the score counts, every band up to k under “an ACE score of k or more”, and every band under “an ACE score of 0”. When y is module completion the whole card is skipped: completing the module is what produces a score, so the side without completion is always empty.
The number the card displays is neither side. Its value column is headed “Prevalence gap (%)” and holds with − without in percentage points: a difference of two proportions, so it carries no denominator of its own. Three properties a replication must copy exactly:
It differences the two published values, i.e. the already-rounded 4-decimal proportions, not the unrounded quotients. Differencing your own unrounded ratios can move the last displayed digit; the on-screen gap is always exactly the difference of the two bars beside it.
It is null unless both sides publish, under the same pair rule as the bivariate map tile (see the suppression section). A row with one publishable side shows no gap.
It is rendered to one decimal, as points: a gap of 0.1763 displays as 17.6, meaning 17.6 percentage points, not 17.6%.
With no demographic filter every jurisdiction passes. The gate bites on narrow filters, where a state’s age groups thin out. Computed by calling the production suppressedValue() as this page renders, for a cell of 400 respondents:
Case
n
Effective n
Value
Rendered
effective n one below the threshold
400
29
null
--
effective n exactly at the threshold
400
30
0.6
60.0%
an age group in scope has no respondents (reported as 0)
400
0
null
--
#E0F3DB
1.3 to 1.5
1.28
1.55
2
#A8DDB5
1.6 to 1.8
1.55
1.85
3
#43A2CA
1.9 to 2.1
1.85
2.10 (inclusive)
Explore, number of respondents: a log scale with a gray floor
The respondents display maps score_n itself: the unweighted count of respondents with a complete ACE score in the scope. No weight touches it, and it is never suppressed, because a count is exact at any size. Counts are skewed, so it is not classed like the statistics above (in this scope the largest classable count is 47.0 times the smallest): equal-width classes would spend most of the ramp on a few big states. Instead it cuts log10 of the count into equal intervals with the same three stops, so each step up is the same multiple. Each cut is rounded to two significant figures and the rounded value is the threshold drawn (if rounding would collapse a class, the cut falls back to the next whole respondent, which classes every count exactly as the unrounded cut would). The legend note under that map prints these cuts.
States under n = 30 stay out of the domain and take the no-data gray, and their tile reads < 30 rather than the exact count, because it is too small for any of the estimates above. In this scope that is 0 of 49 jurisdictions. Live:
Class
Fill
Hex
Respondents from
up to
1
#E0F3DB
972
3,500
2
#A8DDB5
3,500
13,000
3
#43A2CA
13,000
45,661 (inclusive)
Compare: a 2×2 split at the national value
The bivariate map does not class against the data range. It classes each state against the national value of each axis: the same two numbers the scatterplot draws as its crosshair, so a tile’s color and its dot’s quadrant can never disagree. A state is “high” on an axis when its value is greater than or equal to the national value: a state exactly on the line counts as high.
Live thresholds for the current pair. x = Average ACE score 1.79, y = Depression 19.69%:
Class
Fill
Hex
Rule
x-high / y-low
#225EA8
x >= national x AND y < national y
y-high / x-low
#E22B0F
y >= national y AND x < national x
Both low
#DEB8E5
x < national x AND y < national y
Both high
#40004B
x >= national x AND y >= national y
Tiles with nothing to say
A tile with no class is filled with the no-data gray on both maps, and keeps a text marker so suppression is never encoded in color alone. The two maps reach that state by different rules:
Explore classes one cell, so it follows the single-cell rule in the suppression section: -- below n = 30, N/A at n = 0. A jurisdiction with no ACE module data returns no row at all and so reads N/A. The respondents display is the exception: its value is the count, so a tile under the threshold is gray and reads < 30, and only n = 0 reads N/A.
Compare classes a pair, and needs both sides. A pair reads -- only when both x and y were measured (n > 0 on each) but at least one is below the threshold; if either side measured nothing at all, the pair reads N/A, because “low sample size” would be the wrong caution there. So a Compare tile can read N/A for a state whose x value publishes perfectly well on the Explore map. The same pair rule governs each co-occurrence row.
The scatterplot
The Compare scatterplot plots one dot per jurisdiction at (x, y), with the national values drawn as a crosshair: the same two thresholds the bivariate map splits on. Its axes are scaled to round ticks (the smallest 1 / 2.5 / 5 × 10ⁿ step covering the data in about four intervals, with the domain floored and ceiled to that step and the national values forced inside it), so the axis bounds are display choices and not data limits.
It also draws a line of best fit: ordinary least squares over the plotted dots. Two properties to be explicit about, since the page publishes no other fitted quantity:
It is unweighted across states. Every jurisdiction contributes one point regardless of its sample size or population, which is what makes the line match what the eye fits to the chart, but it is not a population-weighted relationship, and it is not the within-person association the co-occurrence card measures.
No slope, intercept, R², standard error, or p-value is computed for display or published anywhere. The line is a visual summary only; see the limitations register on variance estimation.
A metric or Compare x that is invalid for the chosen population coerces to that population’s default, exactly as the dashboard’s URLs do. Demographic filters have no form controls here; paste any /explore or /compare query string onto /methods instead (for example /methods?state=GA&sex=female&age=25_34 or /methods?pop=4plus).
Current scope: adults with any ACE score, metric Average ACE score. Open it on the dashboard: Explore / Compare.
Explore: the statement
One scan computes every aggregate for the scope (national total, every state, and the national demographic rows; gset is the GROUPING bitmask that routes them). This is the exact statement and bindings the dashboard executes:
SELECT state_fips,
sex::text AS sex,
age_group::text AS age_group,
race::text AS race,
education::text AS education,
income::text AS income,
GROUPING(state_fips, sex, age_group, race, education, income)::int AS gset,
COUNT(*) FILTER (WHERE ace_module_asked)::int AS asked_n,
SUM(weight) FILTER (WHERE ace_module_asked)::float8 AS asked_w,
COUNT(*) FILTER (WHERE ace_score IS NOT NULL)::int AS score_n,
SUM(weight) FILTER (WHERE ace_score IS NOT NULL)::float8 AS score_w,
SUM(weight * ace_score) FILTER (WHERE ace_score IS NOT NULL)::float8 AS score_ws,
SUM(weight) FILTER (WHERE ace_score = 0)::float8 AS w_eq0,
SUM(weight) FILTER (WHERE ace_score >= 1)::float8 AS w_ge1,
SUM(weight) FILTER (WHERE ace_score >= 2)::float8 AS w_ge2,
SUM(weight) FILTER (WHERE ace_score >= 3)::float8 AS w_ge3,
SUM(weight) FILTER (WHERE ace_score >= 4)::float8 AS w_ge4,
COUNT(*) FILTER (WHERE ace_score_10 IS NOT NULL)::int AS score10_n,
SUM(weight) FILTER (WHERE ace_score_10 IS NOT NULL)::float8 AS score10_w,
SUM(weight * ace_score_10) FILTER (WHERE ace_score_10 IS NOT NULL)::float8 AS score10_ws,
COUNT(*) FILTER (WHERE ace_emotional_abuse IS NOT NULL)::int AS ace_emotional_abuse_n,
SUM(weight) FILTER (WHERE ace_emotional_abuse IS NOT NULL)::float8 AS ace_emotional_abuse_w,
SUM(weight) FILTER (WHERE ace_emotional_abuse)::float8 AS ace_emotional_abuse_s,
COUNT(*) FILTER (WHERE ace_physical_abuse IS NOT NULL)::int AS ace_physical_abuse_n,
SUM(weight) FILTER (WHERE ace_physical_abuse IS NOT NULL)::float8 AS ace_physical_abuse_w,
SUM(weight) FILTER (WHERE ace_physical_abuse)::float8 AS ace_physical_abuse_s,
COUNT(*) FILTER (WHERE ace_sexual_abuse IS NOT NULL)::int AS ace_sexual_abuse_n,
SUM(weight) FILTER (WHERE ace_sexual_abuse IS NOT NULL)::float8 AS ace_sexual_abuse_w,
SUM(weight) FILTER (WHERE ace_sexual_abuse)::float8 AS ace_sexual_abuse_s,
COUNT(*) FILTER (WHERE ace_emotional_neglect IS NOT NULL)::int AS ace_emotional_neglect_n,
SUM(weight) FILTER (WHERE ace_emotional_neglect IS NOT NULL)::float8 AS ace_emotional_neglect_w,
SUM(weight) FILTER (WHERE ace_emotional_neglect)::float8 AS ace_emotional_neglect_s,
COUNT(*) FILTER (WHERE ace_physical_neglect IS NOT NULL)::int AS ace_physical_neglect_n,
SUM(weight) FILTER (WHERE ace_physical_neglect IS NOT NULL)::float8 AS ace_physical_neglect_w,
SUM(weight) FILTER (WHERE ace_physical_neglect)::float8 AS ace_physical_neglect_s,
COUNT(*) FILTER (WHERE ace_witnessed_violence IS NOT NULL)::int AS ace_witnessed_violence_n,
SUM(weight) FILTER (WHERE ace_witnessed_violence IS NOT NULL)::float8 AS ace_witnessed_violence_w,
SUM(weight) FILTER (WHERE ace_witnessed_violence)::float8 AS ace_witnessed_violence_s,
COUNT(*) FILTER (WHERE ace_household_depression IS NOT NULL)::int AS ace_household_depression_n,
SUM(weight) FILTER (WHERE ace_household_depression IS NOT NULL)::float8 AS ace_household_depression_w,
SUM(weight) FILTER (WHERE ace_household_depression)::float8 AS ace_household_depression_s,
COUNT(*) FILTER (WHERE ace_incarceration IS NOT NULL)::int AS ace_incarceration_n,
SUM(weight) FILTER (WHERE ace_incarceration IS NOT NULL)::float8 AS ace_incarceration_w,
SUM(weight) FILTER (WHERE ace_incarceration)::float8 AS ace_incarceration_s,
COUNT(*) FILTER (WHERE ace_substance_abuse IS NOT NULL)::int AS ace_substance_abuse_n,
SUM(weight) FILTER (WHERE ace_substance_abuse IS NOT NULL)::float8 AS ace_substance_abuse_w,
SUM(weight) FILTER (WHERE ace_substance_abuse)::float8 AS ace_substance_abuse_s,
COUNT(*) FILTER (WHERE ace_divorce IS NOT NULL)::int AS ace_divorce_n,
SUM(weight) FILTER (WHERE ace_divorce IS NOT NULL)::float8 AS ace_divorce_w,
SUM(weight) FILTER (WHERE ace_divorce)::float8 AS ace_divorce_s
FROM respondents
GROUP BY GROUPING SETS ((state_fips), (sex), (age_group), (race), (education), (income), ())
No bound parameters; this scan is unfiltered.
National: Average ACE score
Step
Quantity
Value
1
Unweighted count score_n
673,232
2
Weighted numerator score_ws
758836881.9450306
3
Weighted denominator score_w
445428544.7320479
4
Quotient (unrounded)
1.70361080564
5
Suppression check
publish (n = 673,232 >= 30)
6
Rounded to 3 decimals
1.704
7
As displayed: Map tile and demographic bar rows (unit lives in the legend / column header)
1.7
8
As displayed: Hover probe and state card (unit inline)
1.7
Self-check: this ledger’s result equals the production API value for the same scope. Recomputed and compared as the page rendered.
A deliberately suppressed cell
Suppression fires on the unweighted count, so small cells exist in every seed. This one is live from Puerto Rico’s default-scope scan: Income = $150-200K, average ACE score. Your own run WILL produce a value here; we deliberately publish neither the value nor its weighted sums, since at small n those sums approach an individual respondent’s own survey weight. The ledger shows the n and the decision, which is everything a replication needs to confirm the rule fired.
Puerto Rico, Income = $150-200K: Average ACE score
Step
Quantity
Value
1
Unweighted count score_n
16
2
Weighted numerator score_ws
withheld (below the publication threshold, same rule as the value)
3
Weighted denominator score_w
withheld (below the publication threshold, same rule as the value)
4
Quotient (unrounded)
withheld (below the publication threshold, same rule as the value)
5
Suppression check
suppress (n = 16 < 30)
6
Rounded to 3 decimals
null
7
As displayed: Map tile and demographic bar rows (unit lives in the legend / column header)
--
8
As displayed: Hover probe and state card (unit inline)
--
Self-check: this ledger’s result equals the production API value for the same scope. Recomputed and compared as the page rendered.
Compare: x and y on one scan
The Compare view computes both axes in one per-state statement; x and y have independent denominators and suppression counts. National ledgers for Average ACE score (x) versus Depression (y):
These are age-adjusted estimates, Compare’s default. The statement’s inner query (strata) is the crude scan with the age group added to every grouping set; the outer query averages each ratio over the age groups on the standard population listed in its VALUES clause. Counts and denominators come back as plain sums, so they are the crude ones; each numerator comes back as the adjusted rate times that denominator, so the same quotient applies. Add adj=crude to this page’s address to see the crude statement instead.
WITH strata AS (
SELECT state_fips,
GROUPING(state_fips)::int AS gset,
std_age,
SUM(weight * ace_score) FILTER (WHERE ace_score IS NOT NULL)::float8 AS x_num,
SUM(weight) FILTER (WHERE ace_score IS NOT NULL)::float8 AS x_den,
COUNT(*) FILTER (WHERE ace_score IS NOT NULL)::int AS x_n,
SUM(weight) FILTER (WHERE depression IS TRUE)::float8 AS y_num,
SUM(weight) FILTER (WHERE depression IS NOT NULL)::float8 AS y_den,
COUNT(*) FILTER (WHERE depression IS NOT NULL)::int AS y_n
FROM (SELECT *, age_group::text AS std_age FROM respondents) AS respondents
GROUP BY GROUPING SETS ((state_fips, std_age), (std_age))
)
SELECT state_fips, gset,
SUM(x_n)::int AS x_n,
SUM(x_den)::float8 AS x_den,
(CASE WHEN COUNT(*) FILTER (WHERE x_den > 0) = 6
THEN POWER(SUM(std.pop) FILTER (WHERE x_den > 0), 2) / SUM(POWER(std.pop, 2) / NULLIF(x_n, 0))
ELSE 0 END)::float8 AS x_n_eff,
(SUM(x_den) * SUM(std.pop * COALESCE(x_num, 0) / NULLIF(x_den, 0))
/ NULLIF(SUM(std.pop) FILTER (WHERE x_den > 0), 0))::float8 AS x_num,
SUM(y_n)::int AS y_n,
SUM(y_den)::float8 AS y_den,
(CASE WHEN COUNT(*) FILTER (WHERE y_den > 0) = 6
THEN POWER(SUM(std.pop) FILTER (WHERE y_den > 0), 2) / SUM(POWER(std.pop, 2) / NULLIF(y_n, 0))
ELSE 0 END)::float8 AS y_n_eff,
(SUM(y_den) * SUM(std.pop * COALESCE(y_num, 0) / NULLIF(y_den, 0))
/ NULLIF(SUM(std.pop) FILTER (WHERE y_den > 0), 0))::float8 AS y_num
FROM strata
JOIN (VALUES ('18_24', 26258), ('25_34', 37233), ('35_44', 44659), ('45_54', 37030), ('55_64', 23961), ('65_plus', 34710)) AS std(age, pop) ON std.age = strata.std_age
GROUP BY state_fips, gset
No bound parameters; this scan is unfiltered.
National x: Average ACE score
Step
Quantity
Value
1
Unweighted count x_n
673,232
2
Age-standardised numerator x_num (adjusted rate × the denominator below)
796554816.4734027
3
Weighted denominator x_den
445428544.7320289
4
Quotient (unrounded): the adjusted rate
1.78828866244
5
Suppression check
publish (n = 673,232 and effective n = 457,905, both >= 30)
6
Rounded to 3 decimals
1.788
7
As displayed: Compare hover probe (a classed bivariate tile prints no number; see the encoding section)
1.8
Self-check: this ledger’s result equals the production API value for the same scope. Recomputed and compared as the page rendered.
National y: Depression
Step
Quantity
Value
1
Unweighted count y_n
693,229
2
Age-standardised numerator y_num (adjusted rate × the denominator below)
96794182.6694962
3
Weighted denominator y_den
491588108.33934677
4
Quotient (unrounded): the adjusted rate
0.196900984844
5
Suppression check
publish (n = 693,229 and effective n = 477,006, both >= 30)
6
Rounded to 4 decimals
0.1969
7
As displayed: Compare hover probe (a classed bivariate tile prints no number; see the encoding section)
19.7%
Self-check: this ledger’s result equals the production API value for the same scope. Recomputed and compared as the page rendered.
Co-occurrence: ACE accumulation with versus without y
The card’s 4+ ACEs row: the share of scored adults with ace_score >= 4, among adults with Depression versus adults without it. Respondents with no complete score or no valid y are on neither side, and both sides divide every band by the same scored base.
With Depression: 4+ ACEs
Step
Quantity
Value
1
Unweighted count with_score_n
116,746
2
Age-standardised numerator with_w_ge4 (adjusted rate × the denominator below)
29686866.44428012
3
Weighted denominator with_score_w
78204747.94022307
4
Quotient (unrounded): the adjusted rate
0.379604400323
5
Suppression check
publish (n = 116,746 and effective n = 91,024, both >= 30)
6
Rounded to 4 decimals
0.3796
7
As displayed: Co-occurrence row bar and its probe
38.0%
Self-check: this ledger’s result equals the production API value for the same scope. Recomputed and compared as the page rendered.
Without Depression: 4+ ACEs
Step
Quantity
Value
1
Unweighted count without_score_n
497,568
2
Age-standardised numerator without_w_ge4 (adjusted rate × the denominator below)
48470451.94989596
3
Weighted denominator without_score_w
333636904.70738894
4
Quotient (unrounded): the adjusted rate
0.145279048169
5
Suppression check
publish (n = 497,568 and effective n = 327,795, both >= 30)
6
Rounded to 4 decimals
0.1453
7
As displayed: Co-occurrence row bar and its probe
14.5%
Self-check: this ledger’s result equals the production API value for the same scope. Recomputed and compared as the page rendered.
The number the card actually prints: the gap
The card’s value column is headed “Prevalence gap (%)” and holds the difference of the two published values above, in percentage points. It is computed by the same coOccurrenceGap() the card calls, from the same rounded proportions, so this row is the card’s number by construction and not a re-derivation.
Step
Quantity
Value
1
Published “with” share (4dp)
0.3796
2
Published “without” share (4dp)
0.1453
3
Gap = with − without (null unless both publish)
0.23429999999999998
4
As displayed: co-occurrence value column, in percentage points
23.4
The co-occurrence statement (14 aggregates, one scan)
WITH strata AS (
SELECT std_age,
SUM(weight) FILTER (WHERE ace_score IS NOT NULL AND depression IS TRUE)::float8 AS with_score_w,
COUNT(*) FILTER (WHERE ace_score IS NOT NULL AND depression IS TRUE)::int AS with_score_n,
SUM(weight) FILTER (WHERE ace_score >= 1 AND depression IS TRUE)::float8 AS with_w_ge1,
SUM(weight) FILTER (WHERE ace_score >= 2 AND depression IS TRUE)::float8 AS with_w_ge2,
SUM(weight) FILTER (WHERE ace_score >= 3 AND depression IS TRUE)::float8 AS with_w_ge3,
SUM(weight) FILTER (WHERE ace_score >= 4 AND depression IS TRUE)::float8 AS with_w_ge4,
SUM(weight) FILTER (WHERE ace_score IS NOT NULL AND depression IS FALSE)::float8 AS without_score_w,
COUNT(*) FILTER (WHERE ace_score IS NOT NULL AND depression IS FALSE)::int AS without_score_n,
SUM(weight) FILTER (WHERE ace_score >= 1 AND depression IS FALSE)::float8 AS without_w_ge1,
SUM(weight) FILTER (WHERE ace_score >= 2 AND depression IS FALSE)::float8 AS without_w_ge2,
SUM(weight) FILTER (WHERE ace_score >= 3 AND depression IS FALSE)::float8 AS without_w_ge3,
SUM(weight) FILTER (WHERE ace_score >= 4 AND depression IS FALSE)::float8 AS without_w_ge4
FROM (SELECT *, age_group::text AS std_age FROM respondents) AS respondents
GROUP BY std_age
)
SELECT SUM(with_score_n)::int AS with_score_n,
SUM(with_score_w)::float8 AS with_score_w,
(CASE WHEN COUNT(*) FILTER (WHERE with_score_w > 0) = 6
THEN POWER(SUM(std.pop) FILTER (WHERE with_score_w > 0), 2) / SUM(POWER(std.pop, 2) / NULLIF(with_score_n, 0))
ELSE 0 END)::float8 AS with_score_n_eff,
(SUM(with_score_w) * SUM(std.pop * COALESCE(with_w_ge1, 0) / NULLIF(with_score_w, 0))
/ NULLIF(SUM(std.pop) FILTER (WHERE with_score_w > 0), 0))::float8 AS with_w_ge1,
(SUM(with_score_w) * SUM(std.pop * COALESCE(with_w_ge2, 0) / NULLIF(with_score_w, 0))
/ NULLIF(SUM(std.pop) FILTER (WHERE with_score_w > 0), 0))::float8 AS with_w_ge2,
(SUM(with_score_w) * SUM(std.pop * COALESCE(with_w_ge3, 0) / NULLIF(with_score_w, 0))
/ NULLIF(SUM(std.pop) FILTER (WHERE with_score_w > 0), 0))::float8 AS with_w_ge3,
(SUM(with_score_w) * SUM(std.pop * COALESCE(with_w_ge4, 0) / NULLIF(with_score_w, 0))
/ NULLIF(SUM(std.pop) FILTER (WHERE with_score_w > 0), 0))::float8 AS with_w_ge4,
SUM(without_score_n)::int AS without_score_n,
SUM(without_score_w)::float8 AS without_score_w,
(CASE WHEN COUNT(*) FILTER (WHERE without_score_w > 0) = 6
THEN POWER(SUM(std.pop) FILTER (WHERE without_score_w > 0), 2) / SUM(POWER(std.pop, 2) / NULLIF(without_score_n, 0))
ELSE 0 END)::float8 AS without_score_n_eff,
(SUM(without_score_w) * SUM(std.pop * COALESCE(without_w_ge1, 0) / NULLIF(without_score_w, 0))
/ NULLIF(SUM(std.pop) FILTER (WHERE without_score_w > 0), 0))::float8 AS without_w_ge1,
(SUM(without_score_w) * SUM(std.pop * COALESCE(without_w_ge2, 0) / NULLIF(without_score_w, 0))
/ NULLIF(SUM(std.pop) FILTER (WHERE without_score_w > 0), 0))::float8 AS without_w_ge2,
(SUM(without_score_w) * SUM(std.pop * COALESCE(without_w_ge3, 0) / NULLIF(without_score_w, 0))
/ NULLIF(SUM(std.pop) FILTER (WHERE without_score_w > 0), 0))::float8 AS without_w_ge3,
(SUM(without_score_w) * SUM(std.pop * COALESCE(without_w_ge4, 0) / NULLIF(without_score_w, 0))
/ NULLIF(SUM(std.pop) FILTER (WHERE without_score_w > 0), 0))::float8 AS without_w_ge4
FROM strata
JOIN (VALUES ('18_24', 26258), ('25_34', 37233), ('35_44', 44659), ('45_54', 37030), ('55_64', 23961), ('65_plus', 34710)) AS std(age, pop) ON std.age = strata.std_age
Cells are raw BRFSS codes as extracted (blank = never asked), each state-year already taken from its one source file (steps 2-4). The weight cell is that file’s own weight, already multiplied by the version share for the two-version state-years; the file has no source column, so use coverage.csv’s to see which file and weight a state-year carries. Steps 5-7 still apply.
Downloads: the intermediates behind one scan
aggregates.csv: one row per scope and metric with unweighted n, weighted numerator and denominator (full precision), unrounded quotient, published value, and the suppression flag. Diff your whole run against ours in one join. Suppressed and empty cells carry only their n and the flag; their weighted sums are withheld under the same rule as the value, since at small n those sums approach an individual respondent’s weight. What is in it: every cell the current scope’s single Explore scan publishes: the national total, every participating jurisdiction, and every demographic member row (written age=25_34 and so on), for every Explore metric. The link above carries this page’s current scope, so re-scope the page and download again to get a different population or filter set. What is not: Compare cells. The per-state x/y pairs and the co-occurrence sides come from two different statements, so they are not in this file; use the worked examples above, which print those statements and their raw rows in full.
coverage.csv: one row per state and survey year with the file(s) and weight variable it was read from, row counts, asked and scored bases, and pooled weight. Reconcile your extraction row counts here before any statistic. The pooled weight obeys the same publication gate as every other weighted sum on this page: a state-year cell under the threshold carries its counts and a weight_withheld flag but no weight, since at that size the pooled weight approaches a single respondent’s own. The counts are never withheld.
Minimal snippets
R (one year, 2023, one indicator; pool years by concatenating before the ratio). The file names, state codes and weights are generated from the same registries the extractor runs on, so they are that year’s actual sources:
library(haven)
library(dplyr)
ace_vars <- c("ACEDEPRS","ACEDRINK","ACEDRUGS","ACEPRISN","ACEDIVRC",
"ACEPUNCH","ACEHURT1","ACESWEAR","ACETOUCH","ACETTHEM",
"ACEHVSEX","ACEADSAF","ACEADNED")
# One year (2023). Each state-year comes from ONE file, weighted by that
# file's own final weight: the main file, except for the split-sample states,
# whose answers are in a questionnaire-version file. Repeat per year with that
# year's files (the source table) and pool (no weight renormalization).
part <- function(file, weight, keep) {
read_xpt(file) %>%
filter(keep(`_STATE`)) %>%
select(`_STATE`, any_of(ace_vars), w = all_of(weight))
}
d <- bind_rows(
part("LLCP2023.XPT", "_LLCPWT", function(s) !(s %in% c(24, 39))),
part("LLCP23V1.XPT", "_LCPWTV1", function(s) s %in% c(39)), # OH
part("LLCP23V3.XPT", "_LCPWTV3", function(s) s %in% c(24)) # MD
)
# Asked population: any ACE item non-missing (7/9 refusals are NOT missing).
asked <- d %>% filter(if_any(any_of(ace_vars), ~ !is.na(.x)))
# One indicator, e.g. emotional abuse (ACESWEAR, frequency item):
emo <- asked %>%
mutate(flag = case_when(ACESWEAR %in% c(2, 3) ~ TRUE,
ACESWEAR == 1 ~ FALSE,
TRUE ~ NA)) %>%
filter(!is.na(flag))
emo %>% summarise(prevalence = sum(w[flag]) / sum(w))
SAS skeleton:
/* Same shape in SAS: one year, each state-year from ONE file with that file's
own weight, then a weighted ratio. No PROC SURVEYFREQ variance will match
ours, because we publish none. */
data emo;
set llcp2023 (in=f0) llcp23v1 (in=f1) llcp23v3 (in=f2);
if f0 then do; if _STATE in (24, 39) then delete; w = _LLCPWT; end;
if f1 then do; if _STATE not in (39) then delete; w = _LCPWTV1; end; /* OH */
if f2 then do; if _STATE not in (24) then delete; w = _LCPWTV3; end; /* MD */
if n(of ACEDEPRS ACEDRINK ACEDRUGS ACEPRISN ACEDIVRC ACEPUNCH ACEHURT1
ACESWEAR ACETOUCH ACETTHEM ACEHVSEX ACEADSAF ACEADNED) = 0 then delete;
if ACESWEAR in (2, 3) then flag = 1;
else if ACESWEAR = 1 then flag = 0;
else delete;
run;
proc means data=emo mean;
var flag;
weight w; /* weighted mean of flag = the prevalence */
run;
23 states, 2011-2014 (some states multiple years), 8 ACE categories, N = 214,157.
Ours should read HIGHER: Merrick's window is 2011-2014, and our 2019-2024 years read higher than our 2009-2012 ones. We score the same 8 categories from the same 11 items, but pool 2009-2012 with 2019-2024, with most of the weight in the later years. The physical-abuse item reads about 9 points lower pre-2013 (a break in series; see the limitations register), which drags pooled score-based estimates slightly below a 2019-2024-only figure.
23 states, 2011-2014 (some states multiple years), 8 ACE categories, N = 214,157.
Ours should read HIGHER: Merrick's window is 2011-2014, and our 2019-2024 years read higher than our 2009-2012 ones. We score the same 8 categories from the same 11 items, but pool 2009-2012 with 2019-2024, with most of the weight in the later years. The physical-abuse item reads about 9 points lower pre-2013 (a break in series; see the limitations register), which drags pooled score-based estimates slightly below a 2019-2024-only figure.
50 states + DC, one survey year per jurisdiction (2011-2020, most 2019-2020), 8 ACE categories from 11 items, complete case, N = 264,882.
Near-identical definitions (the single ACESWEAR item), so this row should agree within a couple of points; the difference is year window and state mix. Ours should read somewhat HIGHER: our window runs through 2024, and the item reads higher in 2021-2024 than in Swedo's mostly 2019-2020 years.
50 states + DC, one survey year per jurisdiction (2011-2020, most 2019-2020), 8 ACE categories from 11 items, complete case, N = 264,882.
Same single item (ACEHURT/ACEHURT1), so the rows should agree within a couple of points. Ours should read slightly HIGHER: the item reads higher in 2021-2024 than in Swedo's mostly 2019-2020 years, partly offset by the item's lower-reading pre-2013 era (the break in series in the limitations register).
50 states + DC, one survey year per jurisdiction (2011-2020, most 2019-2020), 8 ACE categories from 11 items, complete case, N = 264,882.
Same single item (ACEPUNCH), worded identically across every year we pool, so this row should agree within a point or two; the difference is year window and state mix.
Full citations: Swedo EA, Aslam MV, Dahlberg LL, Holditch Niolon P, Guinn AS, Simon TR, Mercy JA. Prevalence of Adverse Childhood Experiences Among U.S. Adults, Behavioral Risk Factor Surveillance System, 2011-2020. MMWR Morb Mortal Wkly Rep 2023;72(26):707-715. Merrick MT, Ford DC, Ports KA, Guinn AS. Prevalence of Adverse Childhood Experiences From the 2011-2014 Behavioral Risk Factor Surveillance System in 23 States. JAMA Pediatr 2018;172(11):1038-1044.
Minnesota
2011
Mississippi
2019, 2020, 2021
Missouri
2019, 2020, 2023
Montana
2011, 2020
Nebraska
2010 from version 3, 2011 from landline versions 1+2 (rescaled)
Nevada
2010, 2011 from landline file, 2020, 2021, 2022, 2023, 2024
New Hampshire
2021
New Jersey
2020 from version 2, 2021 from version 2, 2022 from version 2, 2023
New Mexico
2009 from version 1, 2019
New York
2019 from version 3, 2021 from version 1
North Carolina
2012
North Dakota
2019, 2020, 2021, 2022, 2024
Ohio
2010 from version 2, 2019 from version 1, 2021 from version 1, 2022 from version 1, 2023 from version 1, 2024 from version 1
Oklahoma
2012 from version 2, 2019 from version 2, 2020 from version 2, 2022 from version 2, 2024 from version 2
Oregon
2011 from landline file, 2021, 2022, 2023
Pennsylvania
2010 from version 1, 2019
Rhode Island
2019, 2020, 2023
South Carolina
2019, 2020, 2021
South Dakota
2020, 2022
Tennessee
2009 from version 1, 2012, 2019, 2023
Texas
2020
Utah
2010 from version 3, 2020
Vermont
2010, 2011
Virginia
2019, 2020, 2021, 2022, 2023, 2024
Washington
2009 from version 1, 2010 from version 1, 2011
West Virginia
2019
Wisconsin
2010, 2011, 2012, 2019, 2020, 2021
Wyoming
2020
Puerto Rico
2024
Virgin Islands
2024
3. Complete-case scoring narrows the scored population
The score is null unless all 11 items are valid, so score-based metrics (average, the bands) cover a narrower and potentially non-random subset than the indicator metrics, each of which uses its own valid-response base. Module completion quantifies exactly this gap, per state, on the dashboard.
4. A break in series in the physical-abuse item (confirmed rewording)
The CDC renamed the physical-abuse item between eras (ACEHURT in 2009-2012, ACEHURT1 from 2019), and the two eras do not read alike: endorsement is about 9 percentage points lower pre-2013 even within the 16 jurisdictions present in both eras (16.1% vs 25.3% unweighted, 2009-2012 vs 2019-2024; every one of the 16 jumps 7-13 points). ACEPUNCH (witnessed intimate partner violence), the adjacent question on the same scale, barely moves (+0.8 points), and no other ACE item rises more than 4.6 (parental divorce, about half the jump), so this is specific to the one renamed item, not a cohort or mode story. Nor is it the 2011 landline-only rows: without them the gap is 9.5 points. Physical abuse is scored from this item alone, so the indicator carries the full jump. These figures were measured on 2026-09-22, after the split-sample state-years were added, and re-derived unchanged on 2026-11-09; the 2026-07-30 audit, with eight states then in both eras, found 15.2% vs 23.9% on a sample of the raw files with 2019-2021 as the later era. CDC’s national codebook margins show the same pattern (17.9% endorsed in 2011 vs 24.5% in 2019, while ACEPUNCH sits at 17.0% in both).
The cause is a confirmed rewording, verified against the archived CDC questionnaires for 2009-2012 and 2019-2021. The early item reads: “Before age 18, how often did a parent or adult in your home ever hit, beat, kick, or physically hurt you in any way? Do not include spanking. Would you say--”. The 2019+ item reads: “Not including spanking, (before age 18), how often did a parent or adult in your home ever hit, beat, kick, or physically hurt you in any way? Was it--”. The spanking exclusion moved from a standalone sentence heard immediately before answering to a clause at the front of a long sentence, and the age frame became a parenthetical (optional for the interviewer to read). The direction of the jump is consistent with the exclusion losing salience, though no published split-ballot experiment quantifies this change; the module preamble, item order, response scale, and surrounding items are verbatim identical across eras, ruling the alternatives out. CDC’s own pooled publications (including Swedo et al. 2023) mix the two versions without a comparability note.
Weighted effect of pooling the eras here: mean ACE score 1.746 for 2019-2024 alone against 1.704 pooled, physical-abuse prevalence 26.6% against 24.6%. (The audit of 2026-07-30, before the split-sample state-years were added and on the old 7-category score, measured 1.634 to 1.605 and 32.0% to 30.8%.) Score-based and physical-abuse estimates blend two versions of this item; era-restricted replications will not match pooled values exactly.
5. Explore is crude; Compare is age-adjusted by default
Age sits on both sides of this dashboard. Reported ACEs fall with age (the mean ACE score is 2.12 among adults 18-24 and 1.06 among adults 65 and over), while most long-term conditions rise with it. Jurisdictions differ in age mix, and so do the survey years, so a crude gap between two states or two periods is partly a gap in who was young and who was old. Of two states with identical rates at every age, the older one reads lower on ACE measures and higher on long-term conditions.
Every number on Explore is crude: a weighted proportion or mean, not standardised to a common age distribution. That is the right figure for how common something is in a state, and the wrong one for ranking states. To compare states on Explore, filter to one age group or read the age breakdown; each holds age roughly constant.
Compare is age-adjusted by default (direct standardisation to the 2000 U.S. standard population; see the formulas), because there age moves both axes at once and both sides of the co-occurrence card: adults with a condition of older age are older on average than adults without it, and older adults report fewer ACEs, which narrows a crude gap for those conditions and widens it for behaviours of younger age. Its switch to crude shows the figures as surveyed. Three consequences to keep in mind:
The same state reads differently in the two views. Compare’s x axis and Explore’s map can show different values for one measure and one state, because one is adjusted and the other is not. Set Compare to crude to reproduce Explore’s figure.
Adjusted values depend on the standard. They are for comparing groups, not for counting people, and they are comparable only with other figures standardised the same way (same standard, same six age groups).
Adjustment removes the effect of a group’s age mix. It does not correct the age-specific rates themselves: the lower ACE reporting of older adults is partly a real difference between generations and partly recall and survival, and all of it remains.
The two published sources in the cross-checks above report crude estimates, and those rows read Explore’s crude scan, so they are like for like.
The register
What we do
Why
What a replication must do
Suppression at unweighted n < 30, applied after aggregation.
Small-cell estimates are unreliable; a single fixed rule is auditable.
Suppressed is not empty: the n is published so you can tell a withheld value (n > 0) from no data (n = 0).
Income eras are never unioned: 75k_plus exists only through 2020, the finer 75k+ buckets only from 2021.
INCOME2 code 8 and INCOME3 code 8 mean different things.
Filtering on 75k_plus implicitly selects 2009-2020 respondents; the fine buckets select 2021+.
2022 moved melanoma out of the skin-cancer item (CHCSCNC1) and into other cancer (CHCOCNC1), so the two items measure slightly different things either side of that year.
A CDC questionnaire change: from 2022 the skin item asks about skin cancer that is not melanoma, and the other-cancer item names melanoma explicitly.
Do not expect to find this by splitting the pooled series at 2022. Our post-2022 respondents are older, and because skin-cancer prevalence rises steeply with age that composition shift lifts the crude skin-cancer rate by roughly as much as the reclassification lowers it, leaving the crude series looking almost flat while the reclassification is plainly visible within age groups. Compare within age group, and within states present on both sides, or simply treat the two eras as different measures.
Heart disease for 2009-2012 is OUR derivation: CDC's calculated _MICHD exists only from 2019, so its own definition (MI or CHD) is applied to its two inputs, CVDINFR4 and CVDCRHD4.
The inputs exist in every year; the calculated variable does not.
The derivation agrees with _MICHD on all 553,160 rows where both exist (measured on the extract of 2026-09-22), but it is our computation and a replication must apply the same rule (either input yes = case; both no = non-case; else excluded).
2023-2024 have no yes/no insurance question; no-insurance is collapsed from the 13-value PRIMINS source-of-coverage item (codes 1-10 = insured, 88 = none).
CDC dropped the yes/no item after 2022.
The collapse reproduces CDC's own calculated _HLTHPLN exactly where both exist; replicate the collapse, not a yes/no read of PRIMINS.
The heavy-drinking and binge-drinking definitions changed thresholds across CDC's renames (_RFDRHV3 through _RFDRHV9; _RFBING4 through _RFBING6) even though the response codes match.
CDC revised the drink-count cutoffs behind the calculated flags over the years.
Treat these two series as definition-drifting across years; per-year comparisons are safe, pooled levels blend definitions.
Some outcomes were optional modules or absent in early years: depression is absent entirely in 2009 and optional in 2010 (a valid answer from 41% of that year's ACE respondents); arthritis was optional in 2010 (7% coverage); COPD, kidney disease, and both cancer items were not asked before 2011.
BRFSS module participation varied by state and year.
The coverage matrix above shows the exact percentages; low-coverage year cells change which states contribute to that outcome's pooled estimate.
Territories are included: District of Columbia, Puerto Rico, and Virgin Islands are in the data alongside the states (Guam is seeded but has no ACE data).
They fielded the module; excluding them would be a silent editorial choice.
Our national total is not a 50-state total. See coverage.csv for who contributed.
41 state-years come from a questionnaire-version or landline file instead of CDC's main annual file, including every year of California, Kansas, Maine, Maryland, Massachusetts, Nebraska, New York, Ohio, and Oklahoma. Those states asked the ACE module on only some versions of their questionnaire (a split sample), or in 2011 on the landline survey only, so their answers come from the respondents given those versions alone, each weighted by its own file's weight.
CDC's own procedure: it blanks a split-sample module's answers in the main file and publishes them per version, with a weight that projects the subsample onto the state's whole adult population. Reading main files only would drop those state-years, and whole states, for no reason but file layout.
Read each one from the file(s) the pipeline's source table names, with that file's weight, and never also from the main file (Oklahoma 2012's answers are in both). A subsample is a fraction of the state's respondents, so these state-years carry more sampling error than the state's size suggests. coverage.csv gives each state-year's source file, weight variable, and row count.
Every 2011 split-sample row (California, Maine, Nebraska, Nevada, and Oregon) is a landline respondent: CDC's 2011 questionnaire versions, like its LAND2011 file, cover the landline survey only.
That is how CDC published those states' 2011 module; their cell-phone samples were not asked it. CDC still weights the landline respondents to the whole state's adults, cell-only adults included.
These state-years differ in survey mode from every other 2011-onward state-year. A replication that wants a dual-frame basis throughout must drop them (coverage.csv source beginning “landline”).
California 2011 has no ACE-8 score and no incarceration indicator: neither of its questionnaire versions asked ACEPRISN.
A gap in the questionnaire, not nonresponse. Scoring is complete-case over all 11 items, so no California 2011 respondent can be scored, while its other six categories still count toward their own indicators.
Expect about 9,500 California 2011 respondents in the other indicator prevalences, and none in any score-based metric, in incarceration, or in the module-completion base (n_asked 0 in coverage.csv). About 1,600 more of its rows have no usable answer for any category, nearly all coded 9 on every item, apparently California's code for respondents the module never reached. The asked-population rule keeps them because 9 is not blank. With no valid ACE answer they enter no indicator, score or completion figure, but their core answers still count in California's Compare values for health outcomes, education and income under "Any ACE score", which divides by every row with a valid answer, and their weight is in the state-year's totals in coverage.csv. The same rule keeps smaller groups of whole-module refusers in other state-years.
Age uses the 6-band _AGE_G collapse.
The dashboard's demographic cards were designed around six bands.
Rebuild age groups from _AGE_G, not the 5-year bands, or the cells will not match.
ACEDIVRC code 8 (parents never married) is invalid, not a No.
Matches the legacy app and Swedo et al.'s published handling.
Treating 8 as No inflates the divorce denominator and deflates the whole-score missingness; copy our rule exactly.
Frequency items count code 2 (Once) as endorsed.
Standard BRFSS ACE practice; a threshold choice all the same.
Endorse at >= 2, not > 2.
Null demographics are dropped from breakdowns, never shown as an Unknown row.
The dashboard's cards enumerate known categories only.
Demographic rows do not sum to the total.
The two neglect indicators and the expanded 10-item score live on a restricted basis: the reverse-coded neglect items (ACEADSAF/ACEADNED) were fielded 2021-2024 by 29 of the 49 ACE jurisdictions. The pooled ACE-8 score never touches them.
BRFSS added the items in 2021 and only some states ran them; scoring them separately keeps ACE-8 comparable across all years.
Endorse neglect at codes 1-2 of the reverse 1-5 scale (CDC's cut; 3 is neither pole). Never compare an expanded-score or neglect number against a pooled metric. Note that the state and year restrictions are ONE restriction, not two: every jurisdiction that ran the ACE module at all in 2021-2024 also ran the neglect items, in every year it participated, so the 29 are exactly the jurisdictions present in those years. Restricting by state and restricting by year select the same rows.
The neglect items are held to a far stricter threshold than the other eleven, and the gap is not a finding about the population.
CDC's own coding, followed here. MMWR Suppl 2024;73(4) Table 1 codes neglect as exposed at 'never or rarely' while coding abuse as exposed at 'rarely' and above, in one table on one scale for one set of respondents, footnoting that the neglect cut aligns with previously published neglect measures.
The six frequency items endorse at "Once" (a single lifetime occurrence) and the five binary items at "ever", while the two neglect items endorse only at the bottom two of a five-point scale asking for how much of childhood a protective adult was present. So emotional/verbal abuse means sworn at once and emotional neglect means a near-total absence of a protective adult. Do not read the difference in magnitude as neglect being rarer than abuse. Equalising the basis does not close it either: on the shared 13-item base emotional neglect reads 6.6% against emotional abuse's 36.3%, both measured on that same base rather than on the 7.3% and 36.0% the bars publish. For scale, moving the neglect cut to codes 1-3 would give 13.4% and 7.2% and lift the ACE-10 mean from 1.907 to 1.993 (measured on 2026-11-09 from the extract of 2026-09-22), but no published analysis uses that cut.
The "Any ACE score" population applies no filter at all, so every indicator's denominator is wider than the scored base and the two score-group populations together cover fewer respondents than their own parent.
Restricting indicator prevalences to a complete 11-item score would be listwise deletion: refusing any one item nulls the score while leaving the other categories perfectly valid, and dropping those respondents biases every prevalence downward.
Roughly 76,000 respondents with no valid ace_score sit inside at least one indicator denominator (emotional abuse divides by about 732k against the scored base's 673k); about 9,500 of them are California 2011 respondents, who cannot be scored at all. Each indicator's denominator is its own valid responders, never ace_score IS NOT NULL. A score-group population, by contrast, IS complete-case, because its band excludes nulls either way.
Five outcomes carry a shorter series than the rest: depression starts in 2010, and COPD, kidney disease, skin cancer and other cancer start in 2011.
Those items were not fielded (or not under a resolvable variable) in the earliest ACE years; the pipeline nulls them rather than inventing a value.
Each divides by its own valid responders like every other measure, so the numbers are right, but they rest on 48 of the 49 jurisdictions rather than all 49. The missing one is Louisiana, which ran the ACE module in 2009 only: that is before any of the five begins, so its absence follows from the year ranges alone and needs no further explanation. Expect about 670k valid responses for each of the four that start in 2011, against about 757k for an outcome with a full series such as diabetes. Note separately that 2010 depression was optional and reaches 41% of that year's rows, so a short series is not always a complete one.
Refusers (codes 7/9) are in the asked base but excluded from every indicator's valid base.
Refusal is nonresponse, not a No; module completion exists to measure it.
Each indicator has its own denominator; never reuse one common denominator.
Seed drops rows only for unknown state FIPS or missing/non-positive weight.
Everything else nulls a field and keeps the row.
Reconcile row counts against the provenance banner's loaded/skipped figures.
Compare's no-outcome requires only one valid outcome response with none positive.
Requiring all 19 valid would empty the composite in sparse years.
A respondent with one valid No and 18 nulls counts as no outcome; composites mix each outcome's coverage window.