← Methods repository
concept

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.

Causal_Inference_Methodcoarsened-exact-matchingcemmatchingcommon-supportpreprocessingcovariate-balanceexact-matchingmonotonic-imbalance-bounding
Methods reference only. Use primary source citations and local policy before applying this in a study protocol, regulatory submission, payer dossier, or clinical decision.

In plain language

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 to use

Use CEM when a few variables are non-negotiable for clinical exchangeability: calendar era, line of therapy, cancer stage, sex, age band, prior outcome history, frailty/comorbidity band, site, region, payer type, baseline severity, or index-setting. It is especially useful as an outcome-blind design gate before PS modeling: first require exact coarsened overlap on the variables reviewers would not accept as merely "adjusted," then estimate a PS, hdPS, or outcome model inside the CEM-retained sample. Report the coarsening cutpoints, the number of strata, pruned counts by treatment arm, balance before and after CEM, and the final estimand population.

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.

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

stratumdrug_adrug_bkeepreason
Stage IV, biomarker positive, ECOG 0-1, second line, age 65-748491YesBoth arms represented
Stage IV, biomarker positive, ECOG 2+, second line, age 75+21NoNo comparator support
Stage III, biomarker negative, ECOG 0-1, second line, age 55-644368YesBoth arms represented

Steps

  • Choose bins from clinical thresholds and the protocol, before looking at outcomes.

  • Create a CEM stratum as the cross-classification of all coarsened covariates.

  • Drop strata with only one treatment arm and compute CEM weights in retained strata.

  • Compare retained vs pruned patients so the target population is transparent.

  • Fit the outcome model on uncoarsened patient data using CEM weights and, if needed, residual covariate adjustment.

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.

Runnable example

python implementation

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...

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 kept
r implementation

R 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
}