
Elixhauser comorbidity profile (31 domains, van Walraven score)
Source:R/comorbidity.R
extract_elixhauser_comorbidities.RdFlags the 31 Elixhauser comorbidity domains (Quan et al. 2005 coding algorithms) in the look-back window
before each cohort row's index date and scores them with the van Walraven 2009 weights. It is the R
counterpart of Python omop_etl.extract_elixhauser_comorbidities(): both read the same curated code table,
system.file("extdata", "comorbidity", "elixhauser_codes.csv", package = "omopduckdb") (sources, weights,
hierarchy rules and known deviations are in PROVENANCE.md next to it), build the same SQL, and return the same
columns and values.
Usage
extract_elixhauser_comorbidities(
con,
cohort_table = "cohort",
cohort_id = NULL,
person_col = "subject_id",
index_date_col = "cohort_start_date",
lookback_days = 365,
source_and_standard = TRUE,
hierarchy_adjusted = TRUE,
exclude_index_diagnoses = TRUE,
fallback_on_unmapped = FALSE,
return_mapping_fidelity = FALSE
)Arguments
- con
Active DBI connection to DuckDB holding
condition_occurrenceand the cohort table (read-only is fine).concept_ancestoris looked up asconcept_ancestor(main, a TEMP view or the connection'ssearch_path, e.g. afteromop_connect()), then ascentral_vocab.main.concept_ancestor.- cohort_table
Table or view with one row per index event (default
"cohort"); may be schema-qualified.- cohort_id
Optional integer. Keep only rows with
cohort_definition_id = cohort_id. If the table has nocohort_definition_idcolumn a warning is raised and every row is profiled.- person_col
Person id column of
cohort_table, joined tocondition_occurrence.person_id. It must exist: columns are never guessed.- index_date_col
Index date column of
cohort_table; must exist. It is cast to DATE to place the window.- lookback_days
Window
[index - lookback_days, index):condition_start_datestrictly before the index date and no more thanlookback_daysdays earlier.NULLmeans all history before the index date.- source_and_standard
Also match
condition_concept_idagainst the domain's SNOMED ancestors and their descendants (defaultTRUE). Ifconcept_ancestorcannot be resolved on the connection (or is empty) a warning is raised and only the source side is used.FALSEuses the source side only, without a warning.- hierarchy_adjusted
Apply the hierarchy rules to the score and the condition count (default
TRUE).FALSEgives the plain weighted sum and the plain count of raw flags.- exclude_index_diagnoses
Bedside-scoring guard (train/serve skew, default
TRUE): diagnoses are coded by medical coders days to weeks after discharge, so a model trained on them cannot be scored at discharge. WhenTRUE, (a) condition records whosevisit_occurrence_idequals the cohort row'svisit_occurrence_idare never counted (when both tables have that column; cohorts fromdefine_study_cohort()do), and (b) anindex_date_colthat looks like a discharge / end date is an error, since a window ending at discharge contains the index stay. The window is always[index - lookback_days, index).- fallback_on_unmapped
Logical; if TRUE, falls back to source-value matching if standard concept ancestor is unavailable (default FALSE).
- return_mapping_fidelity
Logical; if TRUE, attaches mapping fidelity diagnostics attribute (default FALSE).
Value
A data.frame with the columns, in order: person_col, index_date_col, the 31 elix_<domain> flags
(integer 0/1: chf, arrhythmia, valvular, pulm_circ, pvd, htn_uncomp, htn_comp, paralysis, neuro_other, copd,
dm_uncomp, dm_comp, hypothyroid, renal_failure, liver_disease, pud, hiv, lymphoma, mets, solid_tumor, rheumatic,
coagulopathy, obesity, weight_loss, fluid_electrolyte, blood_loss_anemia, deficiency_anemia, alcohol_abuse,
drug_abuse, psychoses, depression), elix_van_walraven_score (integer; the weights run from -7 to 12, so it can
be negative) and elix_total_conditions (integer count of flagged domains).
Details
The result has one row per DISTINCT (person, index date) of the cohort table, ordered by person then index date. Persons without a qualifying condition still appear, with every flag 0 and a score of 0.
A condition record counts for a domain when its condition_source_value, upper-cased with . and whitespace
removed, STARTS WITH one of the domain's ICD-10-CM / ICD-9-CM prefixes (I50 matches I50.9 and i 50.9; 4280
matches 428.0), or (source_and_standard) its condition_concept_id is a SNOMED ancestor of the domain or a
descendant of one in concept_ancestor. Only records dated strictly before the index date are used
([index - lookback_days, index)), so nothing at or after the index leaks in.
The profile is one statement of CTEs: nothing is created, so it works on read-only connections and leaves no objects behind.
Hierarchy. With hierarchy_adjusted = TRUE, dm_comp supersedes dm_uncomp, mets supersedes solid_tumor
and htn_comp supersedes htn_uncomp in the score and in the condition count; the 0/1 domain columns always stay
the raw flags. Only the mets rule changes the score (12 instead of 16 together with a solid tumour); the diabetes and
hypertension rules change the count. The standard-concept side of dm_uncomp is the set of diabetes types, which
includes every complicated form, so its raw flag is only meaningful after the hierarchy is applied.
Prefixes are matched against any source value, whatever its coding system, so an ICD-9-CM V prefix can match an
ICD-10-CM transport-accident code and the ICD-10-CM prefixes E00-E03, E86, E87 and E890 can match ICD-9-CM
external-cause E codes (PROVENANCE.md lists them); profile data whose coding system you know.
Examples
con <- DBI::dbConnect(duckdb::duckdb())
#> duckdb keeps downloaded extensions and secrets in a temporary directory:
#> ℹ /tmp/RtmpFV3zps/duckdb
#> This is removed when the R session ends.
#> • Extensions are re-downloaded each session.
#> • Secrets are lost.
#> ℹ Run duckdb(shared_home = TRUE) (or create ~/.duckdb) to keep them (suitable for most users).
#> ℹ Run duckdb(shared_home = FALSE) to accept the temporary directory (and silence this message).
#> ℹ See ?duckdb_storage for details and alternatives.
DBI::dbExecute(con, "CREATE TABLE condition_occurrence AS
SELECT 1 AS person_id, 0 AS condition_concept_id, DATE '2021-05-01' AS condition_start_date,
'I50.9' AS condition_source_value")
#> [1] 1
DBI::dbExecute(con, "CREATE TABLE cohort AS
SELECT 1 AS subject_id, DATE '2021-06-01' AS cohort_start_date")
#> [1] 1
extract_elixhauser_comorbidities(con, source_and_standard = FALSE)[
, c("subject_id", "elix_chf", "elix_van_walraven_score")]
#> subject_id elix_chf elix_van_walraven_score
#> 1 1 1 7
DBI::dbDisconnect(con, shutdown = TRUE)