Changelog
Source:NEWS.md
narcan 0.5.4
Data corrections affecting earlier releases
These change values that earlier releases returned without error. If you have published or cached county-level, racer40, or 2003 racer2 results built on narcan v0.3.0 through v0.5.3 – every release that has ever existed – re-derive them.
-
add_county_fips()returned the wrong county for pre-2003 data in every prior release. Before 2003 the last 3 digits ofcountyrs/countyocare the county’s alphabetical rank within its state, not its FIPS code. The old code translated the state prefix and pasted the rank back untouched, so NCHS05019(Los Angeles) came back as06019– Fresno. The result was a real, valid-looking FIPS code, with no error, no warning and noNA.It was wrong for about 97% of pre-2003 records. FIPS county codes step by two (001, 003, 005 …) while an alphabetical rank steps by one, so the two coincide only at rank 001 – the first county alphabetically in each state. Measured on the restricted 1999 file, the old path returned the correct county for 2.8% of records; 0.5.4 returns it for 99.9%. (An earlier draft of this entry said “roughly half”, conflating this with the separate and much weaker question of how often the wrong code happened to name some real county, which was 49.9%.)
Affected data years for the county half are 1979-2002. Two smaller changes reach other years and are listed under Bug fixes below: the
"00"/"999"resolution affects public 2003-2004 output, and passing a FIPS column directly was mis-decoded for any pre-2003 year.The fix sources pre-2003 geography from the record’s own FIPS block –
fipsctyr(residence) andfipsctyo(occurrence) – whichimport_mcod_fwf()has always read and which every 1982-2002 import already carried unused. Those fields exist for 1982-2002 and are used by default across that whole span. Data years 1979-1981 are not fixed and cannot be: they carry no FIPS county at all, so there is nothing to source from. They still return the rank – but now say so rather than doing it silently. Tracked as issue #25. Sourcing needs a data year. A row without one inherits the frame’s era only when every year-bearing row agrees on one and that era is the sourced 1982-2002 span; otherwise it is reported rather than guessed, since a numeric state half cannot by itself distinguish a pre-2003 NCHS code from a frame already converted to FIPS.Measured on the full restricted 1999-2002 files, the correction is large and consistent across all four years. Taking 1999 as the example: the share of returned county codes that are odd-numbered rises from 52.2% to 98.4% (real FIPS county codes are odd by construction – 001, 003, 005 – while an alphabetical rank is a dense sequence and so roughly half odd), and the share of distinct returned codes that name a county on a current roster rises from 49.9% (1,564 of 3,136) to 99.5% (3,123 of 3,140). In 1999 the 17 codes that still do not match are all real 1990s geography that has since been renumbered: eight pre-2022 Connecticut counties, five Alaska boroughs, Dade before Miami-Dade, Shannon SD before Oglala Lakota, Bedford City, and Clifton Forge. (The set shifts slightly by year – 2002 has 17 as well, but a different one of them.) County of occurrence behaves the same (odd-share 98.0%).
racer40returned state codes, not race, for restricted data years 2003-2007. narcan declared Race Recode 40 at bytes 489-490 from 2003 and documented those years as “documented-but-empty”. They are not empty: for 2003-2007 those bytes carry the expanded state of occurrence in legacy NCHS numeric coding.racer40is now declared with no position for 2003-2007 and imports as all-NA; 2008 onward are unchanged. Discard anyracer40values you have for 2003-2007.
Dictionary fixes (2003 and 2004)
- Added
certifier(col 110),tobacco_use(col 142) andpregnancy_status(col 143) to the restricted 2003 and 2004 layouts. All three are present and in-domain in the raw records and are documented in the 2003 layout doc; narcan simply never declared them. The restricted column set is now uniform at 138 columns from 2003 through 2019. They are blank in the public tier for those years and are marked suppressed there. -
Breaking column rename, and a third silent wrong-values defect. Any code that selected
racer2for restricted 2003 was reading Race Recode 5 values under a name that means a 2-category recode in every other year, in every release through v0.5.3. Recheck any 2003 analysis that usedracer2. Restricted 2003 col 450 was labelledracer2; the 2003 layout doc and the observed distribution both say it is Race Recode 5. It is nowracer5, matching 2004 onward. Code selectingracer2for 2003 must be updated. - Dropped the phantom
rnifla_20(col 440) from 2003. It sat insiderecord_20(cols 439-442) and was always blank. - Documented that
certifierandtobacco_useare 100% blank in the restricted NCHS 2013 file – a one-year upstream dropout, not a narcan bug and not a confidentiality mask (2012 and 2014 are populated normally, andpregnancy_statusis populated in 2013). narcan declares the real positions for 2013 and returnsNA. This is distinct from the public tier, where both fields are blanked in every year as a confidentiality mask, which is why the public dictionary marks them suppressed throughout – 2013 excepted, since there the blankness is upstream rather than a mask.
New features
-
add_county_fips()gainsuse_fips_cols, controlling whether pre-2003 geography is sourced fromfipsctyr/fipsctyo:"auto"(default; do it wherever they are present and the year is in 1982-2002),"yes"(same span, but error rather than fall back when a row cannot be resolved) or"no"(reproduce the pre-0.5.4 behavior).schemeanduse_fips_colsare independent –schemeselects how the state half is decoded,use_fips_colswhere the county half comes from. -
add_county_fips()reports which path it took – geography sourced from the FIPS block, or a county half left as an NCHS rank – so a wrong county is not returned without saying so. Each notice fires once per session, so in a loop over data years only the first offending call reports. UsesuppressMessages()to quiet it.
Bug fixes
-
add_county_fips(): ayearvector whose length is neither 1 nornrow(df)is now an error. It was previously used as a frame-level set, which silently decoded the state under the wrong scheme. -
add_county_fips(): the foreign/unknown state"00"and the public-file masked county"999"now resolve toNAon every path, not only when geography was sourced from the FIPS block. 18% of public 2004 residence records carry a999suffix and were being returned as codes like"48999". Thestate_substr/county_substrcolumns keep the literal code, so a mask stays distinguishable from a blank field. -
add_county_fips(): row order, row count, class, row names and grouping are now preserved for every input shape, including grouped and zero-row frames. -
add_county_fips(): passing a FIPS column (fipsctyr,fipsctyo) directly ascounty_vectorwith a pre-2003 year used to send it down the NCHS path, so06037(Los Angeles) came back as08037(Colorado) – wrong state and wrong county, silently. Such a column is now recognized as already-FIPS regardless of the data year, and an explicitscheme=that contradicts it warns rather than being obeyed. -
add_county_fips(): a character county code that is not 5 characters wide (or 2, for the state-onlyfipsstr/fipssto) is now refused with a warning and returnsNA. A 4-digit code has usually lost its leading zero, andsubstr()read it as a different state –"1001"(Alabama 01001) parsed as state"10", Delaware.
narcan 0.5.3
New vignettes and documentation
- Added a “Getting started” overview vignette and two new worked vignettes: “End-to-end on real public-use data (2004)” (the whole pipeline to an age-standardized opioid death rate, with real 2004 numbers) and “Demographic recodes across coding eras”. Applied a clarity/accuracy pass across the existing vignettes and reordered the pkgdown Articles.
-
flag_drug_deaths(): documented that the ICD-10 rule requires a poisoning underlying cause and a drug T-code, making it ~0.1% stricter than the CDC WONDER underlying-cause-only “drug overdose” count (it drops poisoning-UCOD deaths that carry no drug T-code). Resolves the drug-death definition question. - Corrected the ISW7 attribution in the vignettes to the Safe States Alliance (the report’s publisher; CSTE/CDC were among the participating organizations).
Bug fixes
-
calc_stdrate_var(): fixed a variance error. The standardized rate was written into the input rate column’s name, so the variance expression then read that scalar instead of the age-specific rate vector and dropped the wrong strata; it now drops the same strata as the rate. Apop == 0/deaths > 0(Inf) stratum is now dropped (with a warning) rather than coercing the whole group toInf. The standardcalc_asrate_var()->calc_stdrate_var()pipeline is unchanged (verified byte-identical). -
flag_opioid_contributed(): for ICD-9-era data the flag is undefined – the ICD-9 any-mention rule makes every opioid-in-a-contributory-field death an opioid death, so there is no “contributed but not the underlying opioid death” subset. It now warns and returnsNAfor ICD-9 instead of a redundant, always-1 value. The ICD-10 branch is unchanged. -
remap_age(): 2003+ detail-age codes outside the documented domain (the undocumented unit 3; a years quantity above 135) now map toNAwith a warning instead of a plausible-looking wrong age. -
categorize_hspanicr(): now warns on out-of-domain / era-mismatched codes (was silent), matchingcategorize_hispanic_origin(). -
st_fips_map/add_county_fips()/state_abbrev_to_fips(): dropped territories (narcan is US-only: 50 states + DC). This removes an American Samoa / Northern Mariana Islands NCHS-code collision; territory codes now resolve toNAwith a warning. -
std_pops: fixed a corrupted factor label on the WHO 2000-2025 standard population (it now reads “Std Million” like the others); thepop_stdvalues are unchanged.
Documentation
-
flag_od_intent(): documented that for ICD-9 a drug death flagged via a contributory nature-of-injury code, whose underlying cause is a determinate non-drug mechanism, is labeledundeterminedby design (narcan derives overdose intent only from a drug-poisoning underlying cause). -
import_restricted_data(): corrected a misleading note – restricted files carry sub-state geography for all years; the public files stop at 2004.
narcan 0.5.2
New features – Hispanic-origin death-side join
-
Two new exported helpers add a binary Hispanic-origin column to a death frame.
categorize_hispanic_origin(hspanicr_column, year)maps the NCHShspanicrrecode to"hispanic"/"non_hispanic"/"unknown"(orNAbefore 1989, in 2021, or out of range), andadd_hispanic_origin(df)adds it as ahispanic_origincolumn, read per row fromyear(or two-digitdatayear) so a multi-yearbind_rows()frame is labeled correctly.yearis required (no silent-scheme default): the 9-category (1989-2020) and 14-category (2022+)hspanicrschemes are not comparable, but the binary origin axis is. -
add_pop_counts()now joins Hispanic-stratified denominators. Add ahispanic_origincolumn toby_vars(values"hispanic"/"non_hispanic") underrace_scheme = "single"(2000+) or"bridged"(1990+) to get origin-specific population counts; the all-origin denominator is unchanged whenhispanic_originis absent or"all".get_pop_state()/get_pop_county()already expose the same axis via theirhispanic_origin=filter argument.
Breaking changes
-
The
add_pop_counts(hispanic=)argument is removed. It only ever accepted its no-op"all"default; Hispanic-stratified joins now use thehispanic_origincolumn inby_varsinstead. A call passinghispanic=now errors (unused argument). -
race_scheme = "legacy"now hard-errors on a Hispanic-origin or sub-national geography column. The bundledpop_estis national and all-origin, so a death frame carrying a non-"all"hispanic_origin, or astate_fips/county_fips/st_fipscolumn, previously attached the wrong (all-origin, or national) denominator silently. It now fails loud, pointing torace_scheme = "single"/"bridged"(which resolve origin and geography).
New guards
-
Mixed-era coherence stop. A single frame that mixes
"all"with stratified ("hispanic"/"non_hispanic") origins is rejected:"all"already sums the strata, so mixing them double-counts. Combine eras with separate calls andrbind()(seevignette("hispanic-origin")). -
Unknown/
NAorigin is non-denominable. In a stratified join, an origin of"unknown"orNAhard-errors (there is no matching denominator); exclude those deaths from stratified rates or use"all". -
Population-slice validation.
add_pop_counts()now validates the resolved denominator slice before summing it, so a corrupt or hand-supplied parquet cannot silently mis-count: a stored"all"origin marginal beside stratified cells (per-year), a storedrace="total"/sex="both"marginal beside finest cells, an off-canonical label, or a finest cell missing an origin stratum each hard-error. Shipped assets are unaffected. -
get_pop_state()/get_pop_county()refuse a bridged pre-1990 stratified request (e.g.years = 1985, hispanic_origin = "hispanic") instead of silently returning zero rows – SEER resolves Hispanic origin only from 1990, matching the death-join guard. - The accessor year-coverage guard is scoped to the requested geography. A year present for some state/county but missing for the one requested now errors, instead of returning a silent short slice. An entirely-absent geography (a nonexistent FIPS) still returns zero rows, not an error.
Other changes
-
add_pop_counts()accepts a two-digitdatayear. It coalescesdatayear(1979-1995 files) into a canonicalyearper row, so theadd_hispanic_origin()->add_pop_counts()pipeline works for pre-1996 frames; an NA join-year now fails with a clear message rather than a misleading “no population” error. -
Strict-scheme
add_pop_counts()output gains apop_schemecolumn ("single"/"bridged") so results from different schemes are not silently chainable (their origin labels are identical). The"legacy"output is unchanged. -
calc_stdrate_var()warns on any omitted demographic stratifier (sex/race/hispanic_origin/geography), not justyear– a forgotten stratifier silently averages the strata into one blended rate.
Bug fixes
-
categorize_hspanicr()now reads a factor-valuedhspanicrby value, not by factor-level position. A factor whose present levels did not line up position-to-value (e.g. the 2022+ codes 10-14, or a filtered 9-category frame) was silently mislabeled. Non-factor (integer/character) input is unchanged. The pre-fix behavior is reproducible from thev0.5.1tag.
Caveats for Hispanic-stratified rates
-
Numerator/denominator origin misclassification. Death-certificate Hispanic origin (numerator) and Census/SEER origin (denominator) are separately measured and differentially misclassified; see
?add_pop_countsandvignette("hispanic-origin"). -
Incomplete early reporting. Hispanic origin was phased onto state death certificates through ~1997, so 1990-1996 origin-stratified bridged rates undercount Hispanic deaths (biased low).
add_pop_counts()emits a once-per-session message when a bridged join touches that span.
narcan 0.5.1
New features – single-race backfill to 2000 + SEER-uniform bridged denominators
-
Single-race denominators extended back to 2000.
pop_singlerace_full(national, bundled) now spans 2000-2024, backfilling the 0.5.0 tables (which covered 2020-2024). State and county backfills ship as Release assets. The 0.5.0pop_singlerace/pop_singlerace_state(2020-2024) are frozen byte-for-byte; the backfill is additive and its 2020-2024 slice is identical to the frozen tables. -
SEER-uniform bridged-race denominators, 1969-2024. New
pop_bridged(national, bundled) plus state/county Release assets give a single, internally consistent bridged-race series from the SEER U.S. Population Data. Unlike the legacypop_est(pieced-together vintages, stops at 2020), this one series covers 1969-2024 at national/state/county. It is era-ragged: SEER resolves AIAN/API and Hispanic origin only from 1990 (pre-1990 is white/black/other only), so the bridged scheme requiresyearand validates the race set per row’s era. -
add_pop_counts()gainsrace_scheme = "bridged"alongside"legacy"(default) and"single". Both strict schemes guarantee no silentNAdenominator; a request that reaches a year outside a scheme’s coverage is a hard error, not a silent empty result. Single-race requests routed to a pre-2020 year automatically use the backfill; 2020-2024-only requests stay on the dependency-free bundled tables. -
get_pop_state()/get_pop_county()gain the bridged scheme and honor the backfill (pass pre-2020yearsto reach 2000-2024);get_pop_state()gains aparquet=argument.download_pop_data(scheme = "bridged")fetches the bridged parquets;pop_sources()lists every single-race and bridged dataset. -
New vignettes:
classifying-overdose-deaths(ISW7 drug/opioid flagging),age-standardized-rates(an end-to-end age-standardized synthetic-opioid rate by sex, US residents),population-denominators(choosing among the three schemes; supersedessingle-race-rates),geography-fips(FIPS harmonization, public geography 1999-2004), andunspecified-drug-deaths(the specificity problem over time).
Bug fixes (surfaced by the pre-delivery review)
A standing pre-delivery review (seven Opus + Sonnet-5 pairs, including passes over older, less-vetted code) found two pre-existing correctness bugs, now fixed. The pre-fix behavior is reproducible from the v0.5.0 tag.
-
unite_records()auto-cleans raw ICD-9 data, so theflag_*pipeline is correct on ICD-9-era records (data years 1979-1998) without a manualclean_icd9_data()step. For the ICD-9 era it now runs the idempotentclean_icd9_data()internally before collapsing the record columns. Previously, calling the documented pipeline (unite_records()->flag_drug_deaths()/flag_opioid_deaths()/flag_opioid_types()) on a raw ICD-9 frame silently returned all-zero drug/opioid flags (mis-formatted E-codes and nature-of-injury codes missed the ICD-9 regex) or errored on therniflag_-named nature-of-injury columns (1991-1995 files). Blast radius: pre-1999 analyses that did not already callclean_icd9_data()first – their flag counts change from wrong to correct. Already-cleaned pipelines are unchanged (the cleaner is idempotent). -
add_coded_occupation()returns type-stable, zero-padded codes.occ_coded/ind_codedare now zero-padded character in both eras. Previously the 1982-1999 Census (3-digit) scheme returned numeric codes with leading zeros dropped (e.g.7for occupation"007"), which broke 3-digit crosswalks and made abind_rows()of a pre-2000 result with a 2020+ NIOSH (character) result error on the type clash.
Guards
- The strict-scheme join now asserts the population slice is unique on its finest cell before aggregating, so a corrupted or duplicated denominator asset can no longer silently double a rate’s denominator (the many-to-one join could not catch this on its own). Year handling in the accessors is factor-safe.
Notes
- The bundled population manifest now points the single-race county asset at the 2000-2024 backfill (superseding the 0.5.0 2020-2024 asset); the 0.5.0 asset remains available at the
v0.5.0tag for reproducing 0.5.0 rates. - Legacy bridged-race (
"legacy"), SEER bridged ("bridged"), and single-race ("single") denominators are not comparable and must not be chained into one trend. The bridged Hispanic-origin dimension ships now, but the death-side Hispanic join is still deferred to a later release. -
download_pop_data()dropped itsyearsargument, which was reserved and never functional (no code path consumed it); the source/asset pull is otherwise unchanged. This is the only non-additive signature change in 0.5.1. - Apart from that non-functional
yearsargument, nothing was renamed or removed; all new datasets and arguments are additive and the frozenpop_est/pop_singlerace/pop_singlerace_stateare unchanged.
narcan 0.5.0
New features – single-race population denominators (2020-2024)
-
Single-race denominators for 2022+ deaths. From data year 2022 NCHS codes race with the single-race (OMB 1997) scheme;
categorize_race()labels those deathswhite_only/black_only/american_indian_only/asian_only/nhopi_only/multiracial(codes 101-106). This release adds matching Census Population Estimates Program (Vintage 2024) denominators for 2020-2024 so those deaths get correct rates. Bundled:pop_singlerace(national) andpop_singlerace_state(state). County estimates are too large to bundle and are fetched on demand (see below). Each dataset also carries ahispanic_origindimension. -
add_pop_counts()gainsrace_scheme.race_scheme = "legacy"(the default) joins the frozen bridged-racepop_estand reproduces existing bridged-race rates byte-for-byte.race_scheme = "single"joins the new single-race denominators. The single scheme guarantees no silentNAdenominator: out-of-domainage/sex/racevalues and unmatched keys are hard errors, and passing single-race labels under the default scheme errors with a pointer torace_scheme = "single". Geography is routed byby_varsmembership – addstate_fips/county_fipsfor sub-national denominators. The"total"(race),"both"(sex), and"all"(Hispanic origin) aggregate tokens are synthesized on demand. -
New accessors
get_pop_state()andget_pop_county()return population rows for descriptive use, with the Hispanic-origin dimension exposed. -
download_pop_data()fetches data too large to bundle. By default it fetches the analysis-ready county parquet from the tagged GitHub release and verifies its checksum;raw = TRUEfetches the original Census source files verbatim (the same pull the package’s own build uses), so the processed data can be reproduced from scratch.pop_sources()prints the provenance manifest (source, vintage, coverage, delivery) for every dataset. -
New vignette
single-race-rateswalks through anasian_onlyage-standardized rate end to end.
Bug fixes
-
calc_stdrate_var()age-standardized variance corrected for cells with a missing age-specific rate. When a stratum’s rate wasNaN(a legitimatepop == 0cell) or its weight wasNA, the standardized rate renormalized over the surviving strata but the variance did not, so the reported variance (and confidence interval) was too small. The rate and variance now drop the same strata and renormalize identically. Complete-data rates are unchanged (byte-for-byte); complete-data variances are numerically unchanged (they differ by at most a few ULP from reordered floating-point operations). Only estimates with an empty/dropped stratum change materially (their variance was previously understated). AnNAweight now drops that stratum from the rate too (previously it made the whole rateNA). -
add_county_fips()no longer silently misassigns state FIPS. A numericcounty_vector(leading zeros already lost) is now refused with a clear error instead of parsing e.g. Alabama county 01001 as state 10. A frame whose per-rowyearstraddles the 2002/2003 NCHS->FIPS boundary now resolves the coding scheme separately per era, so a mixed-era frame no longer decodes the minority era with the wrong scheme. -
clean_icd9_data()is now idempotent. Cleaning an already-cleaned frame no longer NAs external-cause (E-code) UCODs; the record prefixer likewise never drops an in-range record toNAon a missing nature-of-injury flag.
Minor changes
-
calc_asrate_var()now emits a warning when any cell haspop == 0(its rate is undefined). This is a diagnostic only – the numeric output is unchanged, so existing rate values are unaffected. Zero-population cells are common in fine single-race county strata. -
add_std_pop()warns when the chosen standard’s age granularity does not match the data (e.g. a single-year standard joined to 5-year bins), which would otherwise misweight standardized rates silently. -
remap_race()andremap_age()now warn when an input code falls outside the known set for its era (previously silentlyNA), matching the loud unmatched- code warnings inadd_county_fips(). - Documentation corrected:
std_pops(1015 rows; five SEER standards s13-s17 are unlabeled) andst_fips_map(fips/nchsare numeric).download_natality_ ascii()now fetches over HTTPS (the CDCftp://host was decommissioned).
Notes
- Bridged-race (
"legacy", 2020 and earlier) and single-race ("single", 2022+) denominators are not comparable and must not be chained into one trend. The frozenpop_estis unchanged; all new datasets and arguments are additive (nothing was renamed or removed). - The Hispanic-origin dimension ships in the new denominator data now, but the death-side Hispanic join is deferred to a later release; the death-side join is currently pinned to all-origin denominators.
- narcan’s existing by-race rates for 2020 and earlier pair bridged-race death counts with single-race population estimates, which understates the denominator (most for smaller groups), so those rates run slightly high. A future release will add a bridged-race denominator series consistent across 1969-2024; for coherent single-race by-race rates from 2020 on, use the new single-race path.
narcan 0.4.2
Bug fixes
-
pop_estno longer contains the spuriousyear == 420block. The bundled data carried an extra 216-row year labeled420– an alternate-vintage copy of the 2020 estimates whose year field was mangled during the original build (every cell is within ~1.2% of the real 2020 block; the legitimate 2020 data are unchanged). The block is removed;pop_estnow spans exactly 1979-2020 (9072 rows). No legitimate year or cell changed value. The data builder now asserts the year range so a future rebuild cannot reship the artifact. -
pop_estdocumentation corrected. The help page said 1979-2015 / 7992 rows; it now reads 1979-2020 / 9072 rows.
narcan 0.4.1
New features (0.4-P4)
-
flag_opioid_types()and the six opioid-subtype flags gainopioid_deaths_only(defaultTRUE). The default is unchanged – a type is flagged only for opioid deaths. WithFALSE, an opioid type is flagged wherever its code appears in the contributory causes, even when the death is not an opioid death under the ISW7 combined rule; the caller is then expected tofilter(opioid_death == 1)themselves (an opioid in a contributory cause does not make the death an opioid death). Resolves issue #2.num_opioids/unspecified_op_presentstay coherent underFALSE(the residual keys off an “any opioid present” indicator, so it never fires on a non-opioid row). -
categorize_sex()andcategorize_female()– era-aware sex recodes. NCHS codes sex numerically (1/2) through 2002 and as characters ("M"/"F") from 2003; these map either scheme to"male"/"female"/NA(matching thesexlabels inpop_est) and to1/0/NA, respectively. Resolves issue #11. -
remap_age()– converts the raw unit-coded NCHS detail-age field (age) to age in completed years in a newage_yearscolumn, dispatching on the 2003 encoding change; sub-year ages (months/weeks/days/hours/minutes) collapse to0and not-stated ages toNA. Resolves issue #15. -
flag_all_deaths()– a convenience wrapper that runs the canonical pipeline (unite_records->flag_drug_deaths->flag_opioid_deaths->flag_opioid_types->flag_od_intent) in one call, resolving the data year once. Optionaltypes/intent/clean_icd9toggles.
Second-pass correctness fixes (0.4-P2b)
A second, exhaustive multi-agent review of the 0.4.0 code (weighted toward the areas the first pass rated “clean,” and toward the 0.4.0 fixes themselves) found several correctness issues the first pass missed – one of them introduced by a 0.4.0 fix. Point estimates on canonical national data are essentially unchanged; the behavior changes below are narrow. The 0.4.0 behavior is reproducible from the v0.4.0 tag.
Breaking changes
-
add_county_fips()gainsyearandschemearguments and resolves the state-coding scheme deterministically. NCHS mortality files code state as NCHS numeric codes through 2002 and as 2-letter postal abbreviations from 2003; the numeric NCHS codes overlap FIPS but mean different states (NCHS"06"is Colorado, FIPS California). The function now picks the scheme fromyear(a scalar, or ayearcolumn on the data). When no year is available it guesses from the codes and warns loudly on an ambiguous numeric code instead of silently guessing FIPS (the 0.4.0 subset-detection fix could resolve an isolated ambiguous code to the wrong state). Passscheme=to force a scheme. -
add_county_fips()maps the ambiguous NCHS code 62 toNA(with a warning) instead of aborting. NCHS 62 is both American Samoa and the Northern Mariana Islands; the 0.4.0relationship = "many-to-one"join errored on the whole batch if any 62 record was present. Those rows now becomeNAstate FIPS and the rest of the batch proceeds. -
flag_od_intent()gates every intent flag ondrug_death == 1. A poisoning UCOD with no contributory T-code (not a drug death under the combined rule) now yields all-zero intents ("not_overdose"afterlabel_od_intent()), matching theflag_drug_deaths()definition. Previously intent was assigned from the UCOD alone. Real-data effect is negligible (a poisoning UCOD essentially always co-occurs with a qualifying T-code). -
A two-digit
datayearis normalized to its four-digit year..extract_year()(used byremap_race(), theflag_*family, andunite_records()) maps a 1979-1995datayearsuch as85to1985. As a result theflag_*family now correctly dispatches adatayear-coded ICD-9 file to the ICD-9 branch, rather than erroring on the two-digit value (superseding the 0.4.0 behavior). An explicit two-digityearargument still errors. -
add_coded_occupation()recognizes the 3-digit occupation/industry scheme from data year 1982 (was 1985). The byte-verified dictionary carries real, non-suppressedoccup/industryfrom 1982, so 1982-1984 records were being silently dropped toocc_available = FALSE.
Fixed
-
calc_asrate_var()returns variance0(notNaN) for a zero-death cell. The variance is nowdeaths * (1e5 / pop)^2rather than the algebraically identicalrate^2 / deaths, which was0/0whendeaths == 0. Age-specific rate CIs for zero-count strata (ubiquitous in stratified data) wereNaN; standardized rates were already shielded byna.rm. -
add_county_fips()returnsNAfor a missing county code instead of the literal string"NANA", and raises a clean error when every state code is missing (previously it silently produced"NANA"). -
remap_race()errors on an impossible data year instead of emitting a bare"Invalid year"warning and passing raw, unmapped race codes through. -
state_abbrev_to_fips()maps an unrecognized or wrong-case abbreviation toNAwith a warning, instead of returning it unchanged to fail a downstream join silently. -
unite_records()strips a leading"NA"token (not only interior/trailing ones) when collapsing the record columns.
Guards / warnings
-
calc_stdrate_var()warns when a multi-year frame is passed withoutyearin the grouping (which would collapse all years into one rate) and when standardization weights areNAor sum to zero; the...documentation now states that grouping is not added automatically. -
summarize_binary_columns()warns when a non-binary column would be summed as a flag, and sums withna.rm = TRUE(warning when a flag column hasNAs so a flag total may differ fromdeaths).
narcan 0.4.0
Verified-review correctness fixes (0.4-P2)
Fixes correctness issues surfaced by a systematic, primary-source-anchored review of the ISW7 flag logic and the geography/rate machinery. Point estimates for canonical NCHS data are essentially unchanged; the behavior changes below are narrow. The pre-fix state is tagged v0.3.0 for reproducibility.
Breaking changes
-
add_county_fips()errors on an ambiguous NCHS state code instead of silently duplicating rows. NCHS code 62 maps to both American Samoa and the Northern Mariana Islands inst_fips_map, so an NCHS-coded “62” record previously fanned into two output rows. The NCHS join now usesrelationship = "many-to-one". Real-world impact is nil (code 62 does not appear in national MCOD files; public-MCOD-anchor estimate). Now requiresdplyr (>= 1.1.0). -
calc_stdrate_var()renormalizes the age-standardized variance by the weights actually present. Previouslysum(w^2 * var)was correct only when the weights summed to 1; if age bins were dropped upstream the reported variance was under-stated (the point rate, already renormalized byweighted.mean(), is unchanged). Reported variances/CIs change ONLY for analyses passing incomplete age bins (e.g. county-level sparse-bin standardized rates); complete-bin analyses are unaffected. -
The
flag_*family errors on a 2-digitdatayearor a 4-digit year before 1979 (shared.dispatch_era()guard) instead of silently routing the record into the ICD-10 branch. Valid 4-digit years (1979+) are unaffected. -
.regex_drug_icd10()no longer matches T51-T59 (non-medicinal toxic effects); the drug T-code range is now T36.0-T50.9 as intended. Effect ondrug_deathcounts is negligible: 8-26 records per year (<= 0.07%) across public data years 2005-2023 (public-MCOD-anchor estimate). -
.regex_drug_icd9()no longer matches the unassigned E859; the accidental E-code range is now E850-E858. No effect on real data: E859 is unassigned in WHO ICD-9 (zero E859 records in 1998 public data; public-MCOD-anchor estimate).
Fixed
-
add_county_fips()detects the state-coding scheme (postal / NCHS / FIPS) by subset membership, so it works on a single state or filtered batch instead of requiring the entire national code space; an unrecognized scheme raises an informative error. -
add_pop_counts()warns when join keys have no match inpop_est(previously a silent NA population) and errors early on a pre-existingpopcolumn;add_std_pop()errors early on a pre-existingpop_std/unit_wcolumn. -
flag_od_intent()intent patterns and the opioid-subtype flags (flag_heroin_present()etc.) use anchored code patterns consistent with the death-flag definitions, removing latent substring over-match.
Internal
- Added
.dispatch_era()as the single source of truth for ICD-9/ICD-10 era selection, shared byunite_records()and theflag_*family. - Expanded tests: an ICD-oracle golden test (codes -> expected flags, primary- source-cited), direct unit tests for the ICD-9 munging helpers, era-dispatch boundary tests, and regression tests for each fix above.
narcan 0.3.0
Modernized for current R and tidyverse
This release brings a long-dormant package up to date with modern R and tidyverse conventions and fixes several correctness issues, while preserving the existing analysis behavior. R CMD check is clean (0 errors, 0 warnings).
Changed (behavior)
-
Minimum R is now 4.2 – the package uses the native
|>pipe internally. -
The
flag_suicide_*family gains ayearargument and now warns on pre-1999 (ICD-9) data instead of silently returning all zeros. ICD-9 suicide coding is not yet implemented; the warning fires only when a year is determinable and< 1999. -
state_abbrev_to_fips()now zero-pads two-digit FIPS codes (e.g."06"for California, not"6"), matchingadd_county_fips()and the NCHS convention. -
unite_records()now errors on a year outside 1979-1998 />= 1999(for example a two-digitdatayearsuch as93) instead of silently returning an unrelated object. Pass an explicit four-digityear.
New
- The main data-consuming functions (
unite_records(),flag_drug_deaths(),flag_opioid_deaths(),flag_opioid_types(),flag_od_intent(),calc_asrate_var(),calc_stdrate_var(),add_pop_counts(),add_std_pop()) now emit a plain warning when the first argument is not a data frame or is missing a required column, so a mistake surfaces by name rather than as a cryptic downstream error. These checks never abort.
Internal
- Dependency calls deprecated or defunct in current dplyr were updated with no change in output:
funs()->across(),mutate_at(vars())->mutate(),one_of()->any_of(), andgroup_by(add = TRUE)->group_by(.add = TRUE). - Converted internal code to the native
|>pipe, modernized the rate/summary helpers’ non-standard evaluation to{{ }}embracing, and replacedtidyr::gather()withtidyr::pivot_longer(). - Removed the unused
purrrdependency.
narcan 0.2.1
Coding-aware race/Hispanic recode functions
The recode/label helpers previously hardcoded a single coding scheme and applied it to every year, silently mislabeling data for years where NCHS coding changed. They are now year-aware. Values verified against the NCHS public-use mortality file documentation.
Changed (behavior)
-
categorize_hspanicr(hspanicr_column, year = NULL)gains ayearargument (a scalar or a vector aligned to the data column). It applies the 9-category scheme through 2020, returnsNAfor the reserved 2021 field, and applies the expanded 14-category (single-race, 1997 OMB) scheme from 2022. Values before 1989 (not recorded) areNA. Whenyearis omitted the pre-2022 9-category scheme is assumed with a warning – existing calls keep working but should passyearto label 2022+ data correctly. -
remap_race(icd_df, year)now dispatches three ways: bridged detailedracethrough 2020 (unchanged),NAfor the 2021 transition gap (bridged race dropped, single-race recodes not yet populated), and the single-race Race Recode 6 (racer5) from 2022 mapped to a non-colliding code space (101-106). The internal.remap_race_1992_2015helper is renamed.remap_race_1992_2020. -
categorize_race()labels the single-race codes 101-106 (white_only,black_only,american_indian_only,asian_only,nhopi_only,multiracial) in addition to the bridged codes; the factor levels adapt to the scheme(s) present, so pre-2021 output is unchanged. - Bridged (<=2020) and single-race (2022+) race/Hispanic categories are not comparable;
remap_race()andcategorize_race()warn when the single-race path is used, and the two must not be chained into a single trend.
New
-
import_mcod_fwf()now guarantees a canonicalyearcolumn (from itsyearargument), so downstream year dispatch works on every era including stacked multi-year data. 1979-1995 files retain their originaldatayearcolumn as well. -
.extract_year()falls back todatayear(used by 1979-1995 files) when noyearcolumn is present.
Notes
- Rates for 2021+ single-race deaths need single-race population denominators;
add_pop_counts()joins bridged-racepop_estonly (discontinued by NCHS after Vintage 2020). See?add_pop_counts. Single-race denominators are future work. - New
testthatcoverage for the recode functions is built from a small random sample of real public MCOD data spanning every coding era.
narcan 0.2.0
Byte-verified fixed-width layouts (1979-2024)
The MCOD fixed-width dictionaries were re-verified against the raw NCHS bytes for every year (public 1979-2024 and restricted 1989-2024), not just the NBER Stata dictionaries. narcan’s existing positions were found to be highly accurate; this release makes a small set of corrections and adds public-use support.
New
-
mcod_public_fwf_dicts– a public-use tier dictionary, andimport_mcod_fwf(file, year, tier = c("restricted", "public")), a single exported importer for both tiers. The public importer keeps restricted-only / suppressed columns (sub-state geography and record type from 2005, certifier, tobacco, pregnancy) as all-NAso public and restricted output are column-compatible. Public effective record length by year: 440 (1979-2002), 488 (2003-2012), 490 (2013-2019), 817 (2020-2024). -
add_coded_occupation(df, year)– harmonizes coded occupation/industry across the two non-comparable schemes (3-digit Census 1985-1999; 4-digit NCHS+NIOSH 2020+) into standardocc_coded/ind_coded/occ_recode/ind_recodecolumns plusocc_schemeand anocc_availableflag, so researchers need not know byte positions, era, or tier. The 4-digit codes reach the public file in 2020 but the restricted file only in 2021 (identical from 2021). - Coverage extended to data years 2023 and 2024 (both tiers).
-
cdc_dictextended through the latest public-use year (2024);.download_mcod_fwf()now uses the CDC HTTPS endpoint (the oldftp://host was decommissioned). - The dictionaries are now built from reviewed source CSVs (
data-raw/fwf_layouts/*.csv) by a network-free assembly script that writes the exported and internal copies in sync.
Corrections (restricted tier)
-
hspanicrwidened from 1 byte (@488) to 2 bytes (@487-488) for 2022+. This is the only change that alters parsed values: reading a single byte truncated the 14-category Hispanic-origin/race recode to ~10 units-digit values (e.g. Mexican01collided with non-Hispanic Asian11). If you parsed 2022+ restricted data with narcan < 0.2.0, re-import to recover the full recode. -
racer40(@489-490) is now declared for restricted 2003-2011 as a documented-but-empty field, making the declared record length faithful to the true 490-byte record. This adds an all-NAracer40column to those years’ output; no other values change.
Notes (no code change; positions were already correct)
- narcan already carried
racer40from data year 2012 with record length 490 – NCHS documentation that dates it to 2018 is wrong for the data. -
read_fwf()’s newline-delimited read already handles 2005+ public geography suppression and the 1980 variable-length public file correctly. - Some columns keep their NBER names but change meaning across an era boundary – see
?mcod_fwf_dicts:ucr130is all-ages in 1999-2001 (infant-only from 2002);racer5@450 is bridged Race Recode 5 through 2020 and single-race Race Recode 6 from 2022.
Internal
- Removed the stale
data-raw/making_restricted_dicts.R(it wrote unrelated objects toR/sysdata.rdaand would have clobbered the dictionaries if re-run). - Added a
testthat(edition 3) suite: dictionary integrity (types, monotonic positions,max(end)equals the expected record length), public/restricted column parity, a pre-2022 restricted snapshot, and importer round-trip / parity on synthetic fixtures.