Add bundle_strep (Group A + Group B Strep) and the ABCs Strep sources - #189
Add bundle_strep (Group A + Group B Strep) and the ABCs Strep sources#189MadiBal wants to merge 9 commits into
Conversation
Updated per review feedback — force-pushed and retargeted to
|
|
(1) On Bundle files, we name the time variable as 'date'
|
Folds CDC's ABCs Group A (9y49-tura) and Group B (95m5-agj4) Streptococcus
datasets into the existing abcs source rather than adding separate folders, per
review feedback. ABCs publishes one dataset per pathogen and this source
already held the pneumococcal one, so all three now run from a single
ingest.R with each download tracked separately in process.json (raw_state,
raw_state_gas, raw_state_gbs). The pneumococcal outputs data.csv.gz and
uad.csv.gz are left structurally untouched.
Group A and Group B share a raw layout, so rates, counts and resistance are
merged into single files keyed by a `pathogen` column with organism-agnostic
measure names (abcs_rate_cases, not abcs_gas_rate_cases) - per-organism
prefixes would force half of every row to NA. Syndromes and typing are NOT
merged, because they are not the same measures: Group A syndromes are a rate
per 100,000 while Group B's are a percent of cases, and emm types and capsular
serotypes are different concepts. Eight strep files result, replacing eleven.
Fixes a real bug in the Group B counts. The previous output kept `onset` but
dropped `age`, so the infant early/late-onset rows and the all-ages row were
indistinguishable and invited summing. 1997 reads 16,600 all-ages cases
(= 1,600 deaths + 15,000 survivals) against 2,600 + 1,300 infant cases - a
different population. CDC's viewby fields conflate age with onset timing
("Infants, early-onset disease"), so both are now decomposed explicitly, the
same way the rates path already did. Duplicate index rows are asserted against
after every pivot.
population_group is gone. Its five levels conflated an age band with infant
onset timing, and collapsing them to age alone would map both infant levels
onto "<1" and create duplicate keys, so it decomposes to age + onset
throughout. Its values also contained commas, which forced CSV quoting.
Age labels are standardized on "Total" as the all-ages aggregate, matching the
pneumococcal output in this same folder, with clean bands (<1, 1, 2-4, 5-17,
18-34, 35-49, 50-64, 65+ for rates; the coarser <1, 18-64, 65+ for resistance
and typing). The typo "65+ years olds" is gone. Note this diverges from
CLAUDE.md's "Overall"; local consistency within the folder won.
Adds abcs_not_reported_flag_* columns marking values CDC never published, as
distinct from values suppressed for confidentiality - nothing is imputed, so a
flagged row must not be read as a zero. One flag covers each group of columns
that go missing together, so N_deaths and N_survivals share one as suggested.
The ingest now errors if any column contains NAs without a covering flag, so a
future change in what CDC publishes fails loudly rather than shipping
undocumented gaps.
Verified that the restructure changed arrangement only: for every one of the
eleven previous outputs, the melted (time, measure, value) triples are
identical old vs new - same count, same measures, same values. Also: emm
percents still sum to 100.00 +/- 0.02 in all 28 years and per-type counts still
reconcile to the typed total; 2015/2023 Group A resistance still reads
13.1/14.9/17 and 26.4/27.4/32.1; Group B serotypes and ALPH still sum to ~100
per group-year; every file parses with zero vroom problems; no duplicate index
rows; and two consecutive runs produce identical hashes.
measure_info.json now has 106 entries with dcf_check clean, up from 7 - which
also closes a pre-existing gap where the `pop` column was undocumented.
Two incidental fixes: dropped the unused library(cdlTools) call, which was
never invoked in this script (geography comes from the all_fips merge) but
blocked the ingest wherever that package is not installed. The pneumococcal
data.csv.gz is rewritten with last-digit float differences in pop/rate_IPD
only - N_IPD and pct_IPD are identical on all 176 differing lines - because the
committed copy was generated in a different environment.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Initialized with dcf::dcf_add_bundle, so process.json carries type: "bundle" and scripts: build.R. Ten long-format parquets in dist/, one per contributing source standard file - down from thirteen, now that the ABCs strep files are consolidated. Every parquet shares the same shape: geography (state name or "United States"), geography_fips (the FIPS code, kept for joining), time (ISO period end), measure, value, plus each source's dimension columns. Sources: epic_resp_infections (quarterly_gas.csv.gz, the standardized Epic Cosmos strep throat file), nnds (streptococcal toxic shock syndrome), and abcs (the strep_* / gas_* / gbs_* files). Carries the not-reported flags through as a `not_reported` column. In the wide standard files one flag can cover several measures at once, so it is 1 wherever ANY of them is absent; carrying that straight through would over-mark, since the serotype grouping flag is 1 on every row while only one of its three columns is missing per row. Once melted the per-row question is simply whether that measure was reported, so the column is derived from the value being absent - verified to equal is.na(value) in every parquet. build.R still asserts the source carried a covering flag for each sparse column, so the guarantee that every NA is a not-reported case survives upstream changes. `not_reported` and `suppressed` mean different things and are documented as such: the former is data CDC never published, the latter a small cell Epic withheld and the ingest imputed as 5. NNDSS publishes a cumulative year-to-date count that resets each MMWR year (national 2024 runs 5 -> 647 across weeks 1-52), so build.R de-accumulates it into stss_cases_weekly and keeps the published cumulative series alongside; the two are not additive. The negative weekly increments from NNDSS revising earlier weeks downward are left as reported rather than clamped, and logged. Also documents that STSS is the only Group A measure NNDSS carries - invasive Group A disease generally, and Group B disease, are not nationally notifiable - so it is the severe tip of Group A rather than Group A broadly. measure_info.json documents all 86 columns across the 10 parquets with 0 undocumented and 0 falling back to STANDARD_VARS defaults, and all 86 measure levels resolve against the contributing sources. Includes a _bundle block mapping each parquet to its source file. Open questions carried in the README and PR for Dan: whether age + onset is the right shape for the ABCs strep files, whether these parquets should be this tall, and what to do about the negative NNDSS increments. docs/ and resources/ regenerated on top of current main (14 bundles, 44 sources), so this does not revert the scheduled docs rebuild that landed meanwhile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… totals Six points from review, all verified against the repo first. 1. `date` not `time`. Confirmed the convention is standard files use `time` (38 of 40 sampled, none use `date`) and bundles rename it in build.R, as bundle_measles and bundle_respiratory do. Renamed in the dist files only, and added `year` alongside - plus `week` for the weekly NNDSS series, following bundle_measles which carries date/year/week together. 2. Consolidated the bundle. All eight ABCs topics now stack into one abcs_strep.parquet with a named column per stratification and "Total" wherever a row is not stratified on that dimension, so every column is populated and filterable without handling NA. Epic and NNDSS stay separate: they differ in geography grain, time resolution and measure. Ten parquets become three. Note "group A and group B have the same format" holds for the raw CDC files but only partly for the outputs, so the merge is not uniform: rates, counts and resistance share a schema, but Group A syndromes are a rate per 100,000 where Group B's are a percent of cases, and emm types and capsular serotypes are different concepts. Those keep their own measure levels and entity columns within the one file. 3. Long, but with tooltip information on one line. Following the pneumococcus_by_geography precedent of value + value_N, `n_isolates` (the denominator) and `n_type` (the emm numerator) ride on the same row as the percentage, so a tooltip reads "emm1: 22.5% (99 of 440 isolates)" from one line. Cases, deaths and survivals stay separate measure levels - they are three plottable series, not metadata for one value. 4. Negative values follow the measles convention. bundle_measles de-accumulates with value - lag(value, default = 0) exactly as this does and keeps the negatives (11 rows, min -21.44) rather than clamping, so this was already compliant. Documented explicitly, including that plots should cut the y axis at 0. 5. Total vs Overall. "Total" outnumbers "Overall" 40 to 3 across bundle dist files, so the aggregate label is now "Total" for sex, race_ethnicity and onset as well as age. CLAUDE.md documents "Overall" and is the outlier. 6. Onset totals - one place where the instruction could not be followed literally. Adding a Total row summing early + late onset is valid for counts and wrong for rates, because CDC labels both "Per 100,000 population" while using different denominators. For 1997 Group B the "<1" age band reads 115.7 (per 100,000 infants: 3,900 cases / ~3.9M births * 100,000 ~= 100) while early-onset + late-onset read 0.70 + 0.40 = 1.10 (per 100,000 general population: 3,900 / ~272M * 100,000 ~= 1.4). They differ in all 28 years, mean absolute difference 66.3, so summing would have put 1.10 beside CDC's published 115.7 as two contradictory infant rates. So: a derived age "<1" / onset "Total" row is added for counts (1997: 3,900, genuinely new since CDC publishes the parts but no combined figure), and the onset-specific rates moved to their own measure, abcs_rate_cases_by_onset, so the two denominators never share a column. This also fixes a latent defect - the previous strep_rates already mixed both denominators under one column. Verification. The consolidation changes arrangement only: every value multiset from the previous ten parquets is reproduced exactly - rates, counts, resistance, both syndrome sets, emm percents and per-type counts, serotypes, ALPH, and the isolate denominators - with the 28 derived count rows the only addition. Also asserted: the two rate measures never co-occur on a row and CDC's 115.7 is preserved; the derived infant count equals early + late in all 28 years and the all-ages row is untouched; no "Overall" survives; dimension columns have no NAs; no dist file has a `time` column; not_reported == is.na(value); no duplicate index rows; zero vroom parse problems; dcf_check clean on abcs (107 entries) and all 34 bundle columns documented with 17 measure levels resolving. Two fixes found while doing this. The ingest's own unflagged-NA guard caught that splitting the rate measure left both columns sparse, which is mutual exclusivity rather than a reporting gap, so the guard now takes an `exclusive` declaration and asserts it instead of demanding a misleading flag. And the bundle parquets were not byte-reproducible: vroom attaches `spec` and `problems` attributes that ride through dplyr into the parquet as R metadata, so identical data produced different bytes. Stripped before writing, and a forced re-download now reproduces all 13 output hashes exactly. docs/ and resources/ regenerated on top of current main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Updated for @DanWeinberger's review — force-pushed, rebased on current
|
| Parquet | Rows | Grain |
|---|---|---|
abcs_strep.parquet |
6,101 | national, annual — all eight ABCs topics stacked |
epic_gas.parquet |
39,312 | state + national, quarterly, by age |
nnds_stss.parquet |
24,856 | state + national, weekly (MMWR) |
1. date, not time ✅
Confirmed the split: standard files use time (38 of 40 sampled; none use date) and bundles rename it in build.R, exactly as bundle_measles and bundle_respiratory do. Renamed in the dist files only. Also added year, plus week for NNDSS, following bundle_measles which carries date/year/week together.
Worth flagging: your convention isn't uniform yet — 46 of 64 existing dist parquets still use time, only 18 use date. Might be worth a separate repo-wide sweep.
2. Consolidated ✅
All eight ABCs topics stack into one file with a named column per stratification and "Total" wherever a row isn't stratified on that dimension, so every column is populated and filterable without NA handling:
geography, geography_fips, date, year, pathogen,
age, sex, race_ethnicity, onset, <- stratifications
syndrome, antibiotic, emm_type, serotype, alph_type, <- which entity
measure, value, n_type, n_isolates, not_reported
Epic and NNDSS stay separate — different geography grain, time resolution and measure.
One nuance on "group A and group B have the same format": true of the raw CDC files, only partly true of the outputs. Rates, counts and resistance share a schema and merge cleanly. But Group A syndromes are a rate per 100,000 while Group B's are a percent of cases, and emm types and capsular serotypes are different concepts. So they share the file but keep distinct measure levels and entity columns rather than being forced into common columns.
3. Long, with tooltip info on one line ✅
Took the pneumococcus_by_geography.parquet value, value_N pattern as the model. n_isolates (denominator) and n_type (emm numerator) sit on the same row as the percentage, so a tooltip reads "emm1: 22.5% (99 of 440 isolates)" from one line.
Cases, deaths and survivals stayed as separate measure levels — by your rule they're three plottable series, not metadata for a single value.
4. Negative values — already matching measles ✅
Checked bundle_measles: it de-accumulates with value - lag(value, default = 0), exactly as this does, and keeps the negatives (11 rows, min −21.44) with no clamping. So this was already compliant; I've just documented it explicitly, including that plots should cut the y axis at 0. Nothing changed in the data.
5. Total vs Overall ✅
Total outnumbers Overall 40 to 3 across bundle dist files (only bundle_maternal_health uses Overall). Now Total for sex, race_ethnicity and onset as well as age.
Flagging that CLAUDE.md documents Overall — it's the outlier, and probably the reason this keeps recurring. Worth correcting the doc.
6. Onset totals — done for counts, deliberately not for rates ⚠️
This is the one I couldn't implement as written. Adding a Total row summing early + late is valid for counts and wrong for rates, because CDC labels both "Per 100,000 population" while using different denominators:
| 1997 Group B | Value | Denominator |
|---|---|---|
<1 age band |
115.7 | per 100,000 infants (3,900 cases ÷ ~3.9M births × 100,000 ≈ 100 ✓) |
| early-onset + late-onset | 0.70 + 0.40 = 1.10 | per 100,000 general population (3,900 ÷ ~272M × 100,000 ≈ 1.4 ✓) |
They differ in all 28 years, mean absolute difference 66.3. Summing would have put 1.10 next to CDC's published 115.7 as two contradictory infant rates.
So:
- Counts — derived
age = "<1"/onset = "Total"row added, = early + late (1997: 3,900). Genuinely new information, since CDC publishes the parts but no combined infant figure. Summing is valid because counts carry no denominator. - Rates — no derived total. The onset-specific rates moved to their own measure,
rate_cases_by_onset, so the two denominators never share a column. The two are mutually exclusive by row and the ingest asserts it.
This also fixes a latent defect: the previous strep_rates already mixed both denominators under one abcs_rate_cases column. Shout if you'd rather have it a different way — a rate_denominator column was the alternative.
Verification
The consolidation changes arrangement only. Every value multiset from the previous ten parquets is reproduced exactly — rates, counts, resistance, both syndrome sets, emm percents and per-type counts, serotypes, ALPH, and the isolate denominators — with the 28 derived count rows as the only addition.
Also asserted: the two rate measures never co-occur on a row and CDC's 115.7 survives; the derived infant count equals early + late in all 28 years with the all-ages row untouched; no Overall survives; dimension columns have zero NAs; no dist file has a time column; not_reported == is.na(value); no duplicate index rows; zero vroom parse problems; dcf_check clean on abcs (107 entries); all 34 bundle columns documented with 17 measure levels resolving.
Two fixes found along the way
- The ingest's own unflagged-NA guard caught that splitting the rate measure left both columns sparse. That's mutual exclusivity, not a reporting gap, so the guard now takes an
exclusivedeclaration and asserts it rather than demanding a flag that would misdescribe the gap. - The bundle parquets weren't byte-reproducible:
vroomattachesspecandproblemsattributes that ride through dplyr into the parquet as R metadata, so identical data produced different bytes on each build. Stripped before writing — a forced re-download now reproduces all 13 output hashes exactly.
Still open
antibioticis the column name for the resistance entity rather thanresistance, since it holds a drug name. Easy to rename.- Whether Epic and NNDSS should also fold into one file with a
sourcecolumn — I kept them separate given the grain differences, butbundle_measles/bundle_respiratorydo stack sources that way, so say the word.
🤖 Generated with Claude Code
|
A few small updates necessary in the standard data in the abcs data folder:
|
Standard-data feedback on #189: no NAs, zero-fill them, and give each variable its own flag so a 0 can be told apart from an unreported cell. Every strep standard file now has zero missing values. Each measure is zero-filled and paired with its own abcs_not_reported_flag_<measure>, the name derivable from the measure it describes - abcs_gbs_pct_serotype_ia pairs with abcs_not_reported_flag_gbs_pct_serotype_ia. 80 flags across the eight files, covering all 80 measures, including measures CDC always reports (whose flag is all zeros) so a consumer never has to work out whether a flag exists for the column it wants. The grouped flags this replaces could not do the job, exactly as the review said. In gbs_serotypes for 2000 late-onset, serotypes II, IV and VI are genuine zeros while the two VI-grouping columns were never reported, and a single serotype_vi_grouping flag over all of them said only "something here is missing". Now each column answers for itself. Files that previously carried NAs and no longer do: strep_rates (1400), strep_counts (168), strep_resistance (419), gas_syndromes (13), gbs_serotypes (280), gbs_alph (14), gas_emm (76). Also folds the split case-rate measure back together, per the preference stated in review. abcs_rate_cases_by_onset is gone; strep_rates instead carries a rate_denominator column with "Stratum population" (per 100,000 of the group the row describes) and "Population" (per 100,000 of the whole population regardless of stratum, which is how CDC reports the infant onset rates). The underlying hazard is unchanged - 1997 Group B reads 115.7 for the "<1" band against 0.70 + 0.40 = 1.10 for early plus late onset, differing in all 28 years with mean absolute difference 66.3 - so the distinction now rides on the index instead of on two measure names. "Population" rather than "Total population", so the level cannot be confused with the "Total" not-applicable fill. Bundle changes to match: not_reported is read off the source flag rather than inferred from a missing value, rate_denominator joins the stratification columns, and the mutually-exclusive-measure handling is gone since there is no longer a split measure. The n_isolates and n_type companions are blanked where their source flag is set rather than carried as zeros, because a denominator of 0 would read as broken in a tooltip. One caveat worth stating plainly: a zero-filled cell asserts a value CDC never published. "0% of Group B isolates resistant to tetracycline" is not true - tetracycline is not on the Group B panel - so any consumer that ignores the flag now gets a materially wrong number rather than a blank. The per-measure flags make that fully recoverable, and both READMEs and every flag's measure_info entry say so explicitly. Verification. All 80 measures across the eight files check out three ways: unflagged values are identical to the previous non-NA values, each flag count equals the previous NA count for that column, and every flagged cell reads 0. The folded rate measure reproduces the union of the two split columns exactly (952 values) and CDC's published 115.7 survives. Also asserted: zero NAs anywhere, 1:1 measure/flag pairing in every file, flags binary, no rate_denominator level a prefix of another, the derived infant count still equals early + late in all 28 years with the all-ages row untouched, dcf_check clean on abcs (185 entries), all 35 bundle columns documented with 16 measure levels resolving, and all 13 outputs reproducing identical hashes through a forced re-download. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Standard-data updates: no NAs, one flag per measureAll five points are in. Every strep standard file now has zero missing values, and each measure carries its own flag so a 0 can be told apart from an unreported cell.
Per-measure flags80 flags across the eight files, covering all 80 measures. The flag name is derivable from the measure — Your
Each column answers for itself instead of one The rate denominator — folded back together, as you preferred
The underlying hazard is unchanged: 1997 Group B reads 115.7 for the I used One caveat, stated plainlyA zero-filled cell asserts a value CDC never published. "0% of Group B isolates resistant to tetracycline" is not true — tetracycline isn't on the Group B panel. So a consumer that ignores the flag now gets a materially wrong number where before it got a blank, and the direction of the error is consistent: reading flagged rows as zeros understates resistance, syndrome rates and type shares. The per-measure flags make that fully recoverable, and it's called out in both READMEs and in every flag's Bundle changes to match
VerificationAll 80 measures check out three ways: unflagged values are identical to the previous non-NA values, each flag count equals the previous NA count for that column, and every flagged cell reads 0. The folded rate measure reproduces the union of the two split columns exactly (952 values) with CDC's published 115.7 intact. Also asserted: zero NAs anywhere; 1:1 measure/flag pairing in every file; flags binary; no 🤖 Generated with Claude Code |
|
Questions for @DanWeinberger
Questions for @MadiBal:
|
|
If a variable does not have missing values, you do not need a flag
if there is only 1 group, you don't need the variable. however, this might
cause issues when combining things in bundles if some of the datasets have
the variables and others don't. the place to deal with that would be on the
script creating the bundles
I think it is OK to have outcomes with different definitions on a bundle
file as long as there is a variable indicating what the outcome is
…---
Dan Weinberger
Professor of Epidemiology
Yale School of Public Health
On Tue, Aug 11, 2026 at 9:42 AM anshap32 ***@***.***> wrote:
*anshap32* left a comment (PopHIVE/Ingest#189)
<#189 (comment)>
Questions for @DanWeinberger <https://github.com/DanWeinberger>
- if a variable doesn't have any missing values, using gas_emm for
example, do we need a flag? Some of the variables had missing values and
some did not, but currently all have flags.
- if we only have one value for things like age group or onset, do we
need those variables? example is gas_syndromes, both age and onset are
total. gbs_syndromes doesn't have any missing values so I think it doesn't
need any flags?
- should we separate out the <1 ratesfrom the other counts into two
separate datasets? they have different denominators, currently indicated by
a denominator variable
Questions for @MadiBal <https://github.com/MadiBal>:
- do you know what the difference between clindamycin** and
clindamycin*** is? It looks like just age groups, but want to make sure we
can combine
- Did you confirm number of isolates in strep resistance is the
denominator? It is missing in a lot of years.
- Some of the measure info long descriptions are quite long. For
example, we don't need to say "Every measure in this source has its own
flag, including measures CDC always reports (whose flag is all zeros), so a
consumer never has to work out whether a flag exists for the column it
cares about - and a shared flag could not distinguish a genuine zero from
an unreported one where both occur on the same row" or "Reading a flagged 0
as a measured zero understates resistance, syndrome rates and type shares,
so filter on this column before aggregating."
- I would read through the measure info long descriptions to confirm
accuracy. For example, it says "Reasons a cell goes unreported are
structural: an antibiotic off that pathogen's susceptibility panel, an emm
type or serotype CDC pooled into "other" that year, a rate not broken out
for that stratification." From what I can see, there aren't 'other'
categories (except for race). Did you use the values in Topic == Other in
GAS? I didn't see them
—
Reply to this email directly, view it on GitHub
<#189?email_source=notifications&email_token=ANPQWPEFDYPDLEAKRFRJFWD5JMPFFA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMRVGM4TOMZSGQ22M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5253973245>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ANPQWPGKVKA3NEW2E6GUHMD5JMPFFAVCNFSNUABGKJSXA33TNF2G64TZHMYTAMJYGA3DSNZUG45US43TOVSTWNJQHA2DMMJVGI2DLILWAI>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/ANPQWPEGQADLUOBDO64GFR35JMPFFA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMRVGM4TOMZSGQ22M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJKTGN5XXIZLSL5UW64Y>
and Android
<https://github.com/notifications/mobile/android/ANPQWPBV4X257PVCQSOTDL35JMPFFA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMRVGM4TOMZSGQ22M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>.
Download it today!
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
|
More comments on bundle for @MadiBal
|
… measure_info Rename bundle_gas to bundle_strep now that the bundle covers Group B as well as Group A. Give each companion column a status (reported / not_reported / not_applicable) so a blank n_type or n_isolates says whether the measure has no such companion or CDC never published it. Rename not_reported to value_not_reported, since that is all it described, and assert in build.R that each companion is populated exactly when its status says so. Trim measure_info long descriptions from ~790 to ~270 characters by moving the repeated catchment and proportion-scaling text into _sources. Fix the "pooled into other" claim for Group B serotypes (there is no other bucket; CDC regrouped the rare serotypes), the Epic age description copy-pasted from ABCs, the N_cases onset level, and the n_isolates wording, which asserted a denominator CDC does not document. Record the meaning of the footnote asterisks on the clindamycin labels. Drop 12 orphaned flag entries and a dead variable left from the superseded shared-flag design.
|
Went through all six. Pushed as 9fd5112.
Title and description still need updating for the rename, will do that next. |
|
|
Sorry, I was wrong about replacing the NAs with 0s! You can either leave them as NAs or code them something like -999. |
Adds
bundle_gas, plus the two ABCs Strep sources it needs. Supersedes #187.@DanWeinberger — two shape questions for you in Open questions below, plus a data-quality oddity. @anshap32 — the ABCs findings in What checking Group A turned up are the substantive part.
What's here
bundle_gas— 13 long-format parquets indist/, one per contributing source standard file. Every parquet shares the same shape:geography"United States"for the national totalgeography_fips"00"national), kept so these join to FIPS-keyed datatimeYYYY-mm-ddperiod endmeasurevalueplus each source's dimension columns (
age,sex,race_ethnicity,onset,population_group) andsuppressedfor Epic.epic_gas.parquetepic_resp_infectionsnnds_stss.parquetnndsabcs_gas{,_counts,_syndromes,_resistance,_emm}.parquetabcs_gasabcs_gbs{,_counts,_syndromes,_resistance,_serotypes,_alph}.parquetabcs_gbsabcs_gbs(new) — ABCs Group B Streptococcus, CDC dataset95m5-agj4, national, annual 1997–2024. ABCs publishes one dataset per pathogen and only Group A was being read in. GBS is organized differently, so the ingest isn't a copy: infant onset timing is central (early-onset 0–6 days vs late-onset 7–89 days), apopulation_groupcolumn replaces sex, and serotypes plus ALPH typing stand in for emm types.abcs_gas— brought onto main-line development from the stalegasbranch and substantially rewritten (below). Notedata/abcsis pneumococcus, a different pathogen despite the similar name.What checking Group A turned up
Re-running the existing
abcs_gasingest against current CDC data does not just add a year. Three real problems:CDC restructured the 2026 release, changing meaning.
emm typeswas one topic whoseviewbyheld the type; now each type is its own topic (emm 1,emm 12, …),viewbyholds that type's isolate count, andvalueis a proportion despite units reading "Percent". The old parsing keyed on the vanished topic and produced an empty emm file. Separately, syndromes went from percent-of-cases (1997+) to rate per 100,000 (2001+), so those columns are nowabcs_gas_rate_syndrome_*— carrying rates under apct_name would mislead silently.Verified against the old release: 1997
emm 1arrives asviewby=99,value=0.225; per-type counts sum to 440; 99/440 = 0.225 = the 22.5 percent previously reported on 440 isolates.A 100× scale error. Every "Percent" value is now a proportion — 2023 clindamycin arrives as
0.264where the old release said26.4. Caught by diffing against the old committed file; resistance and emm now both reproduce the old values exactly.The
≥65age band was being silently dropped. Under the C localedcf_processruns in, that non-ASCII literal can't be translated for comparison, so all 28 rows vanished. Labels are now stripped to ASCII before matching.Each of these now fails loudly: the script
stop()s if "Percent" values exceed 1.5, if the emm or syndrome topics stop matching, or if no 65+ rows survive — rather than writing an empty or 100×-wrong file.Also picked up: GAS 2024 data (the repo's raw file was from May 2025), linezolid resistance, and a survivals count.
Counts are split into their own
data_counts.csv.gzin both sources. Folded in, they left columns ~85–93% NA — sparse enough thatvroom's type guessing inferslogicaland blanks out real values. This was observed, not theoretical: 140 real GBS values silently became NA before the split.Open questions
1. Parquet shape — @DanWeinberger. These are fully long (one row per measure). Looking at
bundle_childhood_immunizations/overall_rates_by_source.parquetandbundle_youth_wellbeing/yrbss_state_age_demographics.parquet, those are noticeably less tall, so it's unclear whether the Epic and NNDSS outputs should be long, semi-wide, or just a parquet copy of the standard file. Happy to reshape once there's a house rule.2. NNDSS long vs wide, and negative values — @DanWeinberger. NNDSS publishes STSS as a cumulative year-to-date count that resets each MMWR year (national 2024 runs 5 → 647 across weeks 1–52), so
build.Rde-accumulates it intostss_cases_weeklyand keepsstss_cases_cumulativealongside. Two questions: should those be twomeasurelevels or two columns, and what should happen to the 27 negative weekly increments? They come from NNDSS revising earlier weeks downward. Currently left as reported rather than clamped to zero, withbuild.Rlogging the count — clamping is a one-line change.3. 13 parquets is a lot. One per source standard file, following the earlier "keep the upstream split" decision. Reasonable place to push back if consolidation is preferred.
Verification
dcf::dcf_processclean on all three projects; bundle rebuild is deterministic (re-running produces no diff).dcf_check: 0 undocumented columns onabcs_gas(was 36 — thegas-branch measure_info used generic keys matching no actual column name),abcs_gbs, andepic_resp_infections.measure_info.jsondocuments all 78 columns across 13 parquets — 0 undocumented, 0 falling back toSTANDARD_VARSdefaults — and all 97 measure levels resolve against the contributing sources. Includes a_bundleblock mapping each parquet to its source file.geography↔geography_fipsis 1:1 in every parquet, national always"00".docs/andresources/regenerated (14 bundles, 46 sources); everybundle_gascolumn renders with a name and a description or value list.Caveats worth carrying forward
valuemixes units within a parquet; always filter or facet bymeasurefirst.🤖 Generated with Claude Code