Coarsened Exact Matching (CEM)
A matching-preprocessing method that temporarily bins baseline covariates into clinically meaningful categories, exactly matches treated and comparator patients within those coarsened strata, drops non-overlap strata, and analyzes the original uncoarsened data with CEM weights.
On this page
Coarsened exact matching first groups patients into clinically meaningful buckets, then keeps only buckets that contain both treated and comparator patients. It is a transparent way to say "we will only compare patients who were truly comparable on these key baseline features," but it changes the population if many patients have no match.
Coarsened exact matching (CEM)
is a design-stage matching method for observational causal inference. Analysts choose substantively meaningful bins for baseline covariates before looking at outcomes: age bands, calendar periods, disease severity categories, prior utilization bands, comorbidity score groups, region, sex, line of therapy, or baseline lab categories. Patients are temporarily coarsened into those bins. Treated and comparator patients are exactly matched within each coarsened stratum; strata containing only treated or only comparator patients are pruned as non-overlap. The analysis then returns to the original patient-level, uncoarsened covariates and outcomes, using CEM weights or matched-stratum adjustment. The method is attractive in RWE because it makes the common-support restriction visible and forces analysts to encode clinical comparability up front.
Core estimand distinction
CEM is preprocessing, not an outcome estimator. It creates a matched analytic sample and weights; the final estimand depends on which strata remain and how weights are normalized. In a typical pharmacoepi active-comparator new-user study, CEM targets the effect in the population with overlap under the chosen coarsening rules, often closest to the matchable treated/comparator region rather than the full source population. If many treated patients are pruned because no comparator exists in their exact coarsened strata, the estimand no longer describes all treated patients. That is not a flaw if stated; it is dangerous if hidden.
Pros, cons, and trade-offs
- vs propensity-score matching: CEM bounds imbalance by design on the coarsened variables and makes non-overlap explicit before fitting any treatment model. Cost: it can discard large amounts of data when many variables or narrow bins are used, and it does not balance covariates that were omitted or poorly coarsened.
- vs exact matching on raw covariates: Exact matching on raw continuous variables is usually impossible in claims/EHR cohorts. CEM makes exact matching feasible by grouping clinically equivalent values. Cost: bin choices matter and should be pre-specified or justified clinically.
- vs high-dimensional PS: hdPS scales to thousands of proxy codes; CEM is more transparent but cannot exact-match on hundreds of sparse codes without destroying overlap. A common pattern is CEM on a small set of design-critical variables followed by PS or outcome adjustment within the matched sample.
- vs entropy balancing / overlap weighting: Weighting methods can preserve more patients and target smooth overlap populations. CEM gives a simpler audit trail and hard common-support pruning, but may be less efficient when overlap is moderate and many strata are sparse.
When NOT to use -- and when it is actively misleading or dangerous
- Too many variables or too fine bins. Exact matching on many coarsened variables creates sparse strata, prunes most of the cohort, and can produce a small, idiosyncratic estimand with poor precision.
- Bins are tuned after seeing outcomes. Coarsening is a design choice. Outcome-driven bin adjustment is analysis fishing and invalidates the design-stage claim.
- Clinical ordering is ignored. Binning eGFR, age, stage, or comorbidity in ways that mix clinically distinct risk groups creates false comparability. Coarsening should follow clinical thresholds or pre-specified quantiles justified without outcomes.
- Unmeasured confounding dominates. CEM balances only selected measured variables and their coarsened interactions. It does not fix missing smoking, ECOG, disease activity, over-the-counter use, or surveillance intensity unless those factors are measured or proxied by included variables.
- Pruned patients are silently omitted. Dropping unmatched strata changes the target population. Always profile pruned vs retained patients and avoid claiming the result applies to patients outside common support.
Data-source operational depth
- Claims: CEM works well for exact coarsened overlap on payer, calendar quarter/year, age band, sex, prior event history, comorbidity/frailty score, baseline utilization, and therapy line proxies. Claims failure mode: Medicare Advantage vs FFS differences can make "no prior code" incomparable across payer segments; coarsen payer/completeness explicitly or restrict to complete capture.
- EHR: Use clinically meaningful bins for labs, vitals, severity scores, site, and encounter intensity. EHR failure mode: missing lab values are informative; either treat missingness as its own coarsened level or impute before CEM using a pre-specified missing-data strategy.
- Registry: Stage, grade, biomarker, performance status, and line-of-therapy variables are natural exact/coarsened matching dimensions. Registry failure mode: strict CEM on rare biomarker-stage combinations can leave only tertiary-center patients; describe the retained population.
- Linked data: Coarsen on data-source availability or linkage status when linked clinical detail is used. Otherwise, CEM may match a richly observed patient to a poorly observed patient with the same diagnosis codes but different measurement opportunity.
Worked RWE example
A comparative oncology study evaluates Drug A vs Drug B after first-line progression. Investigators insist that patients be comparable on cancer type, metastatic stage, biomarker status, ECOG band, line of therapy, index quarter, age band, and prior hospitalization band. CEM creates exact strata from those variables and drops strata without both treatment arms. The raw cohort has 6,400 Drug A and 9,800 Drug B initiators; CEM retains 4,900 and 6,100. Retained patients have overlap in stage/biomarker/ECOG combinations; pruned Drug A patients are younger and biomarker-positive with no real comparator. The final model uses CEM weights plus continuous age and baseline utilization adjustment inside retained strata. The result applies to patients whose baseline clinical profile existed in both treatment arms, not to the entire Drug A launch population.
Decision diagram
flowchart TD
Cohort[Outcome-blind cohort<br/>treated and comparator initiators] --> Bins[Choose clinical bins<br/>age, stage, ECOG, calendar, payer]
Bins --> Strata[Create coarsened exact strata]
Strata --> Support{Both arms in stratum?}
Support -->|yes| Keep[Retain patients<br/>compute CEM weights]
Support -->|no| Drop[Prune non-overlap stratum]
Keep --> Analyze[Analyze original uncoarsened data<br/>within retained support]Worked example
Scenario
An oncology RWE study compares Drug A and Drug B after first-line therapy. Investigators require exact coarsened overlap on disease stage, biomarker status, ECOG band, line of therapy, age band, and calendar quarter before outcome analysis.
Dataset
Example CEM strata after coarsening
| stratum | drug_a | drug_b | keep | reason |
|---|---|---|---|---|
| Stage IV, biomarker positive, ECOG 0-1, second line, age 65-74 | 84 | 91 | Yes | Both arms represented |
| Stage IV, biomarker positive, ECOG 2+, second line, age 75+ | 21 | 0 | No | No comparator support |
| Stage III, biomarker negative, ECOG 0-1, second line, age 55-64 | 43 | 68 | Yes | Both arms represented |
Steps
Result
The analysis estimates the effect among patients with cross-arm clinical overlap. It does not generalize to pruned biomarker-positive, ECOG 2+ patients who had no Drug B comparator in the data.
Trade-offs
Runnable example
CEM preprocessing for a binary treatment. Required input: df : one row per patient with treatment and baseline covariates bin_specs : dict mapping covariate names to either category labels already present or numeric cut points Returns retained rows with CEM strata and simple ATT-style weights: treated weight 1,...
import numpy as np
import pandas as pd
def cem_preprocess(df, treatment_col, bin_specs):
out = df.copy()
parts = []
for col, spec in bin_specs.items():
if pd.api.types.is_numeric_dtype(out[col]) and isinstance(spec, (list, tuple)):
binned = pd.cut(out[col], bins=spec, include_lowest=True, duplicates="drop")
else:
binned = out[col].astype("string").fillna("MISSING")
cname = f"cem_{col}"
out[cname] = binned.astype("string").fillna("MISSING")
parts.append(cname)
out["cem_stratum"] = out[parts].agg("|".join, axis=1)
counts = out.groupby(["cem_stratum", treatment_col]).size().unstack(fill_value=0)
keep_strata = counts[(counts.get(0, 0) > 0) & (counts.get(1, 0) > 0)].index
kept = out[out["cem_stratum"].isin(keep_strata)].copy()
sc = kept.groupby(["cem_stratum", treatment_col]).size().unstack(fill_value=0)
ratio = (sc[1] / sc[0]).replace([np.inf, -np.inf], np.nan)
kept["cem_weight"] = np.where(
kept[treatment_col] == 1,
1.0,
kept["cem_stratum"].map(ratio).astype(float)
)
return keptR CEM preprocessing using base cut() and dplyr. The output contains retained patients, CEM strata, and ATT-style weights. Use uncoarsened covariates in the final outcome model if residual within-bin imbalance remains.
library(dplyr)
cem_preprocess <- function(df, treatment, bin_specs) {
out <- df
cem_cols <- c()
for (nm in names(bin_specs)) {
cn <- paste0("cem_", nm)
if (is.numeric(out[[nm]]) && is.numeric(bin_specs[[nm]])) {
out[[cn]] <- cut(out[[nm]], breaks = bin_specs[[nm]], include.lowest = TRUE)
} else {
out[[cn]] <- ifelse(is.na(out[[nm]]), "MISSING", as.character(out[[nm]]))
}
cem_cols <- c(cem_cols, cn)
}
out$cem_stratum <- do.call(paste, c(out[cem_cols], sep = "|"))
tab <- out %>% count(cem_stratum, .data[[treatment]]) %>%
tidyr::pivot_wider(names_from = .data[[treatment]], values_from = n, values_fill = 0)
keep <- tab %>% filter(`0` > 0, `1` > 0) %>% select(cem_stratum)
kept <- inner_join(out, keep, by = "cem_stratum")
sc <- kept %>% count(cem_stratum, .data[[treatment]]) %>%
tidyr::pivot_wider(names_from = .data[[treatment]], values_from = n, values_fill = 0) %>%
mutate(comp_weight = `1` / `0`) %>%
select(cem_stratum, comp_weight)
kept <- left_join(kept, sc, by = "cem_stratum")
kept$cem_weight <- ifelse(kept[[treatment]] == 1, 1, kept$comp_weight)
kept
}SAS CEM preprocessing outline. Create clinically meaningful bins, form a stratum key, keep strata with both arms, and compute ATT-style comparator weights. The weighted outcome model should use original, uncoarsened covariates as needed.
data cem0;
set work.analytic;
length age_bin $8 util_bin $8 cem_stratum $200;
if age < 55 then age_bin = '<55';
else if age < 65 then age_bin = '55-64';
else if age < 75 then age_bin = '65-74';
else age_bin = '75+';
if prior_hosp = 0 then util_bin = '0';
else if prior_hosp = 1 then util_bin = '1';
else util_bin = '2+';
cem_stratum = catx('|', sex, stage, biomarker, ecog_band, age_bin, util_bin, index_qtr);
run;
proc sql;
create table stratum_counts as
select cem_stratum,
sum(treated=1) as n_treated,
sum(treated=0) as n_comp
from cem0
group by cem_stratum;
create table work.cem_matched as
select a.*, b.n_treated, b.n_comp,
case when a.treated=1 then 1
else b.n_treated / b.n_comp end as cem_weight
from cem0 as a inner join stratum_counts as b
on a.cem_stratum=b.cem_stratum
where b.n_treated > 0 and b.n_comp > 0;
quit;Citations
- [1]Iacus SM, King G, Porro G. Causal inference without balance checking: coarsened exact matching. Political Analysis. 2012;20(1):1-24.
- [2]Iacus SM, King G, Porro G. Multivariate matching methods that are monotonic imbalance bounding. Journal of the American Statistical Association. 2011;106(493):345-361.