Charlson / Quan-Charlson Comorbidity Index (CCI)
A weighted count of 17 chronic conditions, each scored 1–6 by its association with 1-year mortality, summed into a single comorbidity-burden score used to risk-adjust or confounder-adjust RWE analyses; in claims, modern implementations should name the Deyo or Quan ICD code map and the weight set.
In plain language
The Charlson Comorbidity Index turns a patient's list of chronic illnesses into one number that stands for how sick they are. Each qualifying condition carries a point value — 1 for things like heart failure or COPD, up to 6 for metastatic cancer or AIDS — and you add up the points for the conditions a patient has. A common version also adds points for older age. In real-world data you do not read this off a chart; you let a published list of diagnosis codes decide which conditions count, looking back over a fixed window before the study start. Researchers use the score to make treatment groups fairer to compare, so that a difference in outcomes is not just because one group was sicker to begin with. The honest caveat: it only sees conditions that got coded, and it was built to predict death, so it is a blunt tool for anything else.
The Charlson Comorbidity Index (CCI) is the most widely used single-number summary of a patient's chronic-disease burden. Charlson et al. (1987) selected 19 conditions that independently predicted 1-year mortality in a hospital cohort and assigned each an integer weight of 1, 2, 3, or 6 proportional to its adjusted mortality hazard (e.g., myocardial infarction, CHF, COPD, uncomplicated diabetes = 1; any tumor, moderate/severe renal disease, diabetes with end-organ damage = 2; moderate/severe liver disease = 3; metastatic solid tumor and AIDS = 6). The patient's CCI is the sum of the weights of the conditions present. An age-adjusted CCI adds one point per decade of age over 40 (50–59 → +1, 60–69 → +2, ... ≥80 → +4). In RWE the index is almost never read off a chart — it is built from administrative data through a published ICD-to-condition crosswalk: Deyo (1992) mapped Charlson to ICD-9-CM for claims, and Quan (2005) extended the maps to ICD-10 and (2011) re-estimated condition weights in a modern multi-country discharge cohort. The CCI is fundamentally a covariate-construction method, not a study design or an outcome: its job is to make two exposure groups comparable on baseline sickness, either by entering the continuous score (or its categories 0 / 1–2 / 3–4 / ≥5) in an outcome model, or by feeding it into a propensity score.
Core conceptual distinctions
(1) Index vs algorithm vs weights: the index is the conceptual list of conditions; the code algorithm (Deyo, Quan) is the ICD crosswalk that decides whether a condition is "present" in claims; the weights (original Charlson 1987 vs updated Quan 2011) convert presence to a score. All three must be reported — "we used the Charlson index" is under-specified without naming the code map and weight set. (2) Lookback window: comorbidities are ascertained over a fixed baseline window before index date (commonly 6–12 months of continuous enrollment); a longer window finds more conditions and mechanically raises every patient's score, so the window must be equal across exposure groups. (3) Diagnosis rule: a single inpatient or outpatient diagnosis code is sensitive but noisy; a ≥1 inpatient OR ≥2 outpatient rule reduces rule-out/coding-artifact false positives, exactly as in phenotype algorithms. (4) CCI vs Elixhauser: CCI is a single mortality-weighted score (parsimonious, comparable across studies); Elixhauser is a broader 31-condition set usually entered as individual indicators or a van Walraven point score, trading parsimony for resolution.
Pros, cons, and trade-offs
(named against the alternatives). - vs the Elixhauser comorbidity measures: CCI is a compact, mortality-oriented single number that is directly comparable across the thousands of studies that report it; Elixhauser captures more conditions and typically predicts in-hospital outcomes better but costs degrees of freedom and cross-study comparability. Prefer CCI when you need one interpretable summary or a parsimonious confounder; switch to Elixhauser when condition-level resolution or maximal predictive performance matters. - vs a high-dimensional propensity score (hdPS): CCI is transparent, pre-specified, and clinically interpretable but captures only 17 named conditions; hdPS empirically screens hundreds of claims codes and can adjust for proxies CCI never sees, at the cost of interpretability and a data-driven (overfitting-prone) variable set. They are complementary — many analyses include CCI and hdPS. - vs individual comorbidity indicators: entering each condition as its own covariate is the most flexible (lets the outcome model learn condition-specific effects) but spends many degrees of freedom and can be unstable in sparse data; the CCI's fixed weights buy stability and parsimony at the price of assuming the 1-year-mortality weighting is appropriate for your outcome. - Original vs updated weights: the 1987 weights were fit to 1980s hospital mortality; the Quan 2011 weights reflect modern survival (several conditions now carry less weight). The choice shifts scores and must be pre-specified and reported.
When to use
As a baseline confounder for overall sickness in comparative cohort or case-control RWE; as a matching/propensity-score input; as a stratification or subgroup variable; as a case-mix adjuster in HCRU, cost, and mortality models; and as a transparent, reviewer-familiar summary of cohort comparability in a Table 1. It is the default comorbidity adjuster for claims-based regulatory and HTA submissions because its provenance and code maps are fully documented.
When NOT to use — and when it is actively misleading or dangerous
- As a proxy for the very outcome it was built to predict. The CCI's weights come from 1-year mortality; using it to adjust a mortality analysis risks adjusting away part of the effect (over-adjustment) or, if the comorbidity is on the causal pathway from exposure to death, introducing collider/mediator bias. Treat on-pathway comorbidities explicitly, not by burying them in a summary score. - When the lookback window or enrollment differs across groups. Score is mechanically a function of observable person-time; an exposure group with longer baseline enrollment will look sicker purely from more coding opportunity. Require equal continuous-enrollment windows before comparing scores. - In populations unlike its derivation cohort. The weights were estimated in general hospital inpatients; in pediatric, obstetric, or single-disease specialty cohorts the mortality weighting can be irrelevant or misleading. Validate or replace the index rather than importing it blindly. - As a measure of frailty or function. CCI counts diagnosed diseases; it does not capture functional dependency, falls, or frailty, which a claims-based frailty index targets directly. A robust older adult with treated hypertension and diabetes can outscore a frail one with few coded diagnoses. - Reporting a number without the code map and weight set. "CCI = 4" is uninterpretable and unreproducible unless the ICD algorithm (Deyo vs Quan), the weight set (1987 vs 2011), the lookback, and the diagnosis rule are stated.
Data-source operational depth
In claims, presence is decided by the Deyo/Quan ICD crosswalk over a fixed baseline window; use both inpatient and outpatient (and often physician) files, require a defensible diagnosis rule, and respect the diagnosis hierarchy (severe forms supersede mild — e.g., metastatic tumor overrides localized tumor, complicated overrides uncomplicated diabetes) so a condition is not double-weighted. In EHR, problem lists and encounter diagnoses give richer detail but capture only in-system care; conditions managed elsewhere are missed, so the score can understate true burden. Linked claims–EHR maximizes capture but inherits linkage selection. Across all sources the score is only as comparable as the underlying continuous enrollment and coding intensity, which differ systematically across payers (FFS vs Medicare Advantage vs commercial), so cross-payer score comparisons need explicit caution.
Interpreting the output
In the worked example, a patient carries CHF (weight 1), COPD (weight 1), complicated diabetes (weight 2), moderate renal disease (weight 2), and any malignancy (weight 2), giving CCI = 8; adding age-adjustment for age 72 yields CCI_age-adjusted = 11.
(1) Formal interpretation. CCI = 8 places this patient in a high-comorbidity stratum. The score is an additive index: each component condition contributes its pre-specified integer weight derived from Cox proportional-hazards coefficients fit to 1-year all-cause mortality in the 1987 Charlson index cohort. The age-adjusted score of 11 incorporates one additional point per decade above 40, reflecting the independent mortality contribution of age in the original regression. These weights do not update automatically to modern treatment effectiveness or the specific outcome in the current study. CCI is a summary confounder-control variable, not a causal severity measure — a score of 8 does not predict this patient's outcome, nor does CCI quantify modifiable disease burden.
(2) Practical interpretation. Use CCI as a covariate or stratification variable to adjust for baseline comorbidity differences between exposure groups, not as an absolute mortality prediction for modern patients. Confirm which ICD code-to-condition mapping and which weight set (Charlson original, Quan 2011, Romano) the algorithm applies, and specify the lookback window, because score values differ materially across implementations. When comparing CCI across data sources or payers, note that coding intensity and enrollment continuity differ — a score of 8 in Medicare FFS may reflect more complete comorbidity capture than the same score in a commercial claims file.
Index definitions
Source-backed definitions and variants for the index or checklist family.
| name | definition | source | use | notes |
|---|---|---|---|---|
| Original Charlson Comorbidity Index | Mortality-weighted comorbidity score derived from chronic conditions associated with 1-year mortality; modern catalog use treats the collapsed 17-condition score as a baseline risk-adjustment covariate. | Charlson et al. 1987 | Conceptual index and original integer weights. | Report the weight set separately from the code map, diagnosis rule, and lookback window. |
| Deyo Charlson ICD-9-CM adaptation | Administrative-claims coding algorithm that maps ICD-9-CM diagnosis codes to Charlson condition flags. | Deyo et al. 1992 | ICD-9-CM claims implementation and historical US administrative-data studies. | Still relevant when baseline windows include pre-ICD-10 diagnosis history. |
| Quan Charlson ICD-9/ICD-10 algorithms | ICD-9-CM and ICD-10 coding algorithms for Charlson comorbidities in administrative data, published alongside Elixhauser mappings. | Quan et al. 2005 | Crosswalk for modern coded claims/EHR administrative data. | This is the phrase many users mean by "Quan-Charlson comorbidity index"; the code map is distinct from the weight set. |
| Updated Quan Charlson weights | Re-estimated Charlson score weights using hospital discharge data from 6 countries to improve modern mortality risk adjustment. | Quan et al. 2011 | Updated mortality-calibrated score when modern weighting is desired. | Scores are not numerically comparable with the original Charlson weights unless the same weight set is applied. |
Worked example
Scenario
We want the age-adjusted Charlson score for one 72-year-old patient from a claims database. Over the 12-month baseline window the code algorithm flags four qualifying conditions; we look up each condition's Charlson weight, add them, then add the age points (one per decade over 40) to get the final score the analyst would carry into the outcome model.
Dataset
The conditions flagged for one patient over the 12-month lookback, with each condition's Charlson weight.
| condition | charlson_weight |
|---|---|
| congestive_heart_failure | 1 |
| chronic_pulmonary_disease | 1 |
| diabetes_with_complications | 2 |
| moderate_severe_renal_disease | 2 |
| any_malignancy | 2 |
Steps
List the qualifying conditions the code algorithm flagged over the equal baseline window, with each one's Charlson weight from the table.
Add the condition weights to get the base Charlson score: 1 + 1 + 2 + 2 + 2 = 8.
Compute the age points: the patient is 72, which is three full decades over 40 (50s, 60s, 70s), so age adds 3 points.
Add the age points to the base score for the age-adjusted CCI: 8 + 3 = 11.
Result
Base Charlson Comorbidity Index = 1 + 1 + 2 + 2 + 2 = 8; age-adjusted CCI = 8 + 3 = 11. This patient enters the model in the highest comorbidity stratum (≥5), and the same code algorithm, weight set, lookback, and diagnosis rule are applied identically to every patient in both exposure groups.
Runnable example
python implementation
Compute the (optionally age-adjusted) Charlson score from claims-style long diagnosis data. Inputs: diags : person_id, code (ICD string), dx_date (datetime) base : person_id, index_date (datetime), age (int) A {condition: (regex, weight)} map stands in for...
import re
import pandas as pd
LOOKBACK_DAYS = 365
# condition -> (ICD-prefix regex, Charlson weight). ILLUSTRATIVE subset; swap in full Deyo/Quan maps.
CHARLSON = {
"mi": (r"^(I21|I22|I252|410|412)", 1),
"chf": (r"^(I50|428)", 1),
"copd": (r"^(J4[0-7]|49[0-6])", 1),
"diabetes_uncx": (r"^(E1[0-4][0-1]?|250[0-3])", 1),
"diabetes_cx": (r"^(E1[0-4][2-8]|250[4-9])", 2),
"renal": (r"^(N1[789]|585|586)", 2),
"tumor": (r"^(C[0-7]|C8[0-5]|14[0-9]|1[5-9][0-9]|20[0-8])", 2),
"metastatic": (r"^(C7[7-9]|C80|19[6-9])", 6),
"aids": (r"^(B2[0-4]|04[2-4])", 6),
}
# milder form -> more severe form that supersedes it (severity hierarchy)
SUPERSEDES = {"diabetes_uncx": "diabetes_cx", "tumor": "metastatic"}
def charlson_score(diags: pd.DataFrame, base: pd.DataFrame, age_adjust: bool = True) -> pd.DataFrame:
df = diags.merge(base[["person_id", "index_date"]], on="person_id", how="inner")
win = df[(df["dx_date"] < df["index_date"]) &
(df["dx_date"] >= df["index_date"] - pd.Timedelta(days=LOOKBACK_DAYS))]
rows = []
for pid, g in win.groupby("person_id"):
codes = g["code"].astype(str)
present = {c: codes.str.match(rx).any() for c, (rx, _) in CHARLSON.items()}
for mild, severe in SUPERSEDES.items(): # don't double-count severity
if present.get(severe):
present[mild] = False
score = sum(w for c, (_, w) in CHARLSON.items() if present[c])
rows.append({"person_id": pid, "cci": score})
out = base[["person_id", "age"]].merge(pd.DataFrame(rows), on="person_id", how="left").fillna({"cci": 0})
out["cci"] = out["cci"].astype(int)
if age_adjust:
out["age_points"] = ((out["age"].clip(lower=40) - 40) // 10).clip(upper=4).astype(int)
out["cci_age_adj"] = out["cci"] + out["age_points"]
return outr implementation
R/data.table version. Inputs mirror the Python version: diags : person_id, code (character ICD), dx_date (Date) base : person_id, index_date (Date), age (integer) Replace the illustrative CHARLSON map with the full published Deyo/Quan code lists.
library(data.table)
LOOKBACK_DAYS <- 365L
charlson_map <- list( # condition = list(regex, weight) -- ILLUSTRATIVE subset
mi = list("^(I21|I22|I252|410|412)", 1L),
chf = list("^(I50|428)", 1L),
copd = list("^(J4[0-7]|49[0-6])", 1L),
diabetes_uncx = list("^(E1[0-4][0-1]?|250[0-3])", 1L),
diabetes_cx = list("^(E1[0-4][2-8]|250[4-9])", 2L),
renal = list("^(N1[789]|585|586)", 2L),
tumor = list("^(C[0-7]|C8[0-5]|14[0-9]|1[5-9][0-9]|20[0-8])", 2L),
metastatic = list("^(C7[7-9]|C80|19[6-9])", 6L),
aids = list("^(B2[0-4]|04[2-4])", 6L)
)
supersedes <- c(diabetes_uncx = "diabetes_cx", tumor = "metastatic")
charlson_score <- function(diags, base, age_adjust = TRUE) {
setDT(diags); setDT(base)
df <- merge(diags, base[, .(person_id, index_date)], by = "person_id")
win <- df[dx_date < index_date & dx_date >= index_date - LOOKBACK_DAYS]
score_one <- function(codes) {
present <- sapply(charlson_map, function(cw) any(grepl(cw[[1]], codes)))
for (mild in names(supersedes)) # severity hierarchy
if (isTRUE(present[[supersedes[[mild]]]])) present[[mild]] <- FALSE
sum(mapply(function(cw, p) if (p) cw[[2]] else 0L, charlson_map, present))
}
sc <- win[, .(cci = score_one(as.character(code))), by = person_id]
out <- merge(base[, .(person_id, age)], sc, by = "person_id", all.x = TRUE)
out[is.na(cci), cci := 0L]
if (age_adjust) {
out[, age_points := pmin(pmax(age - 40L, 0L) %/% 10L, 4L)]
out[, cci_age_adj := cci + age_points]
}
out[]
}