← Methods repository
concept

Cost-effectiveness Analysis (CEA)

A full economic evaluation that compares two or more interventions on both incremental cost and incremental health effect, summarized as an incremental cost-effectiveness ratio (cost per unit of effect, e.g. cost per life-year or QALY) and interpreted against a willingness-to-pay threshold.

Economic_Evaluationcost-effectivenessICERnet-monetary-benefithealth-economicscost-utilitycensored-costsCEACprobabilistic-sensitivity-analysis
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

Cost-effectiveness analysis asks a simple question: for every extra unit of health you buy with a new treatment, how much more does it cost compared to the current standard? You measure both the extra cost and the extra health benefit for the new treatment, divide one by the other, and get a single number — the incremental cost-effectiveness ratio, or ICER — in dollars per life-year or dollars per quality-adjusted life-year gained. Decision makers then compare that number to a ceiling price they are willing to pay per unit of health: if the ICER falls below that ceiling, the treatment is considered a good use of money; if it exceeds it, the treatment is too expensive for the benefit it delivers.

Cost-effectiveness analysis (CEA)

is a full economic evaluation: it compares at least two alternatives on both the resources they consume and the health they produce, and expresses the result as an incremental cost-effectiveness ratio (ICER) = (C_index - C_comparator) / (E_index - E_comparator), the extra cost to buy one additional unit of effect. When the effect unit is the QALY, the analysis is a cost-utility analysis (CUA); when it is a single natural unit (life-years, events avoided), it is CEA in the narrow sense. A result is judged against a willingness-to-pay (WTP) threshold λ: the intervention is cost-effective if ICER < λ, or equivalently if the net monetary benefit NMB = λ·ΔE - ΔC > 0. CEA does not tell you whether something is affordable — that is a budget-impact question — and it does not monetize health (that is cost-benefit analysis).

Core conceptual / estimand distinction

The estimand is a joint contrast of two correlated quantities — the difference in mean total cost and the difference in mean effect between strategies — under a stated perspective (payer vs health-system vs societal), time horizon, and discount rate. Three design forks govern everything downstream. (1) Trial/cohort-based ("within-study") CEA estimates ΔC and ΔE directly from observed patient-level data over the follow-up window; it carries low structural-assumption burden but is bounded by the data's horizon. (2) Decision-analytic (model-based) CEA — Markov cohort, partitioned-survival, or discrete-event simulation — extrapolates costs and effects to a lifetime horizon using transition probabilities and survival models, trading transparency for the ability to answer the decision-maker's actual (lifetime) question. (3) The ratio vs net-benefit parameterization: ICERs are non-monotone and undefined when ΔE crosses zero (a negative ICER can mean dominant or dominated), so modern RWE work targets NMB / net-benefit regression, a linear quantity that admits covariate adjustment and ordinary uncertainty propagation. The decision rule is the same; the statistics behave far better.

The censored-cost problem (the defining methodological hazard in claims-based CEA)

Naively averaging observed total cost over patients is biased downward under administrative censoring, because patients with shorter observed follow-up accrue less recorded cost even though their true lifetime cost is unknown — and standard survival methods do not fix this, since cost does not accrue at a constant rate over time (it spikes near initiation, relapse, and death). Two consistent estimators solve it: Lin's partitioned (interval) estimator (Lin & Feuer 1997), which partitions follow-up into intervals and weights mean within-interval cost by the Kaplan-Meier survival probability, and the inverse-probability-of-censoring-weighted (IPCW) estimator of Bang & Tsiatis (2000), which reweights fully-observed cost histories by the probability of remaining uncensored. Either is mandatory whenever a non-trivial fraction of person-time is administratively censored — i.e. essentially every claims study with a fixed data cut.

Pros, cons, and trade-offs (specific and comparative)

- vs cost-minimization analysis (CMA): CMA assumes equal effect and compares cost alone. Prefer CMA only when equivalence in effect is already established (e.g. a biosimilar with non-inferiority shown); using CMA when effects actually differ silently buries the health trade-off. CEA is the default when effects are uncertain or differ. - vs cost-benefit analysis (CBA): CBA monetizes health (WTP/QALY, friction-cost, human-capital), enabling cross-sector comparison but importing controversial valuation choices. CEA keeps health in natural/QALY units and defers the monetary trade-off to an explicit external threshold — usually preferred for HTA, where λ is set by the payer (e.g. NICE £20k-£30k/QALY). - vs budget-impact analysis (BIA): BIA answers "what will it cost the plan over 1-5 years," CEA answers "is it good value per unit of health." They are complements, not substitutes; an intervention can be cost-effective yet unaffordable. Report both for coverage decisions. - Within-study CEA vs decision-analytic model: within-study CEA (Ramsey/ISPOR) is transparent and assumption-light but truncated at the data horizon — fatal for chronic disease where most cost/QALY divergence happens after follow-up ends. Markov / partitioned-survival models extrapolate to lifetime but the answer can be dominated by extrapolation assumptions (parametric survival choice, transition probabilities) rather than the data. Prefer within-study when the horizon captures the relevant divergence (acute conditions, short-course therapy); prefer a model (or a hybrid: within-study costs/effects out to horizon, then extrapolation) for chronic disease.

When to use

Comparing two or more interventions for the same indication where both cost and effect plausibly differ; HTA submissions and payer value dossiers; comparative-value evidence built on a defensible comparative-effect estimate (the ΔE should itself come from a sound design — e.g. active-comparator new-user with propensity adjustment — not a naive prevalent-user contrast).

When NOT to use / when it is actively misleading

- Chronic disease with short claims follow-up and no extrapolation. A 24-month within-study ICER for a therapy whose survival and cost curves separate over a decade is not "conservative" — it is wrong, and its direction is unpredictable. Either extrapolate explicitly or do not report an ICER. - Perspective mismatch. A payer-perspective CEA presented to a societal decision-maker (or vice versa) omits or includes productivity/caregiver costs that can flip the conclusion. State the perspective and match it to the decision-maker. - No utility data → crude QALY proxies. Claims rarely contain EQ-5D/utilities; mapping from diagnoses to utilities or borrowing trial utilities introduces error that the ICER hides. If utilities are weak, report cost per clinical event and flag the QALY as exploratory. - Reporting a raw ICER when ΔE may cross zero. With statistical noise around a small ΔE, the ICER is unstable and its sign is uninterpretable. Use NMB/CEAC instead; never quote a point ICER without joint uncertainty. - Immortal time / mis-timed cost accrual. Assigning costs to an exposure arm before exposure begins (procedure studies, post-index drug initiation) inflates one arm. Accrue cost only from a correctly aligned time zero, exactly as for the effect outcome.

Data-source operational depth

- Claims (FFS or commercial): the natural substrate for cost — paid/allowed amounts on medical + pharmacy claims give total cost directly. Use allowed (not billed) amounts; standardize to a price year and inflate with a medical-care index; decide all-cause vs disease-attributable cost up front. Failure modes: Medicare Advantage and capitated/bundled person-time lack itemized FFS claims, so cost is missing-not-zero — restrict to enrollees with full Parts A/B/D (or commercial medical+pharmacy) and exclude MA-only person-time. Differential administrative censoring by arm biases naive mean cost (use Lin/Bang-Tsiatis). Differential competing risk of death by exposure in elderly cohorts truncates cost accrual unequally — model cost over survival, not over raw person-time. - EHR: rich on effect/utility proxies (labs, vitals, notes for severity and outcomes) but weak on cost — chargemaster or RVU-based costing is a poor stand-in for paid amounts and out-of-network/external care is invisible. Link to claims for cost completeness; treat loss-to-follow-up (patient leaves the system) as informative censoring. - Registry: strong for adjudicated effect and severity, typically thin on resource use; link to claims for the cost side and to a death index to firm up the survival weights the cost estimators depend on. - Linked claims-EHR-vital-records: the ideal substrate (EHR severity/utility + claims cost completeness + reliable mortality for survival weighting), but linkage selection and order/fill/service-date discrepancies must be reconciled before aligning cost accrual to time zero.

Worked claims example (with the censoring correction made visible)

Question: incremental cost per relapse avoided, index biologic vs active comparator, over a 24-month health-system-perspective horizon in a commercial + Medicare FFS database (allowed amounts = plan-paid + patient cost-share; for a strict payer perspective sum paid amounts instead). (1) Cohort & time zero: active-comparator new-user design — first qualifying fill = `index_date`, arm from the dispensed NDC, 365-day continuous medical+pharmacy enrollment washout, MA-only person-time excluded. (2) Cost accrual: sum allowed amounts on all medical and pharmacy claims with `service_date` in (`index_date`, `index_date`+730], inflated to a common price year; accrue only from time zero. (3) Effect: time to first validated relapse from the same time zero, identical outcome definition in both arms. (4) The trap: with a fixed data cut, ~30% of patients are administratively censored before 24 months; their observed cost is artificially low, so a naive arm-mean cost is biased downward and, because censoring differs by arm, the bias does not cancel in ΔC. (5) Fix: estimate mean 24-month cost per arm with Lin's KM-weighted partitioned estimator (or Bang-Tsiatis IPCW), discount both cost and effect at 3% annually, and form ΔC and ΔE on the corrected means. (6) Decision & uncertainty: compute the ICER and NMB at λ; bootstrap the joint (ΔC, ΔE) at the patient level (resample with the censoring correction inside each replicate) to plot the cost-effectiveness plane and the cost-effectiveness acceptability curve (CEAC) across a range of λ; report the probability cost-effective, not a bare point ICER.

Interpreting the output

The worked example returns an ICER of $50,000 per QALY gained: Drug B costs $25,000 more per patient and produces 0.50 additional QALYs relative to Drug A, so $25,000 / 0.50 = $50,000/QALY.

(1) Formal interpretation. The ICER is the ratio of incremental costs to incremental effects — it is not an average cost per QALY for Drug B in isolation. An ICER of $50,000/QALY means every additional QALY purchased by switching from Drug A to Drug B costs the payer $50,000. Because $50,000 is below the stated willingness-to-pay threshold of $100,000/QALY, Drug B meets the cost-effectiveness criterion. The ICER is plotted on the cost-effectiveness plane (x-axis = ΔE, y-axis = ΔC); a northeast-quadrant result with ICER below the threshold line is the standard cost-effective finding. Dominance occurs when ΔC ≤ 0 and ΔE ≥ 0 simultaneously — the new intervention is both cheaper and at least as effective, making ICER computation unnecessary.

(2) Practical interpretation. In plain language: the plan gains one full year of perfect health for each $50,000 spent upgrading from Drug A to Drug B — well within what the plan has decided is worth paying. Two caveats are essential. First, the ICER says nothing about affordability: if 50,000 patients are eligible, the budget impact is $1.25 billion, a separate budget-impact question. Second, the ICER is an incremental quantity; quoting Drug B's average cost-per-QALY (total cost divided by total QALYs, ignoring what the comparator produces) is a common error that conflates average and marginal efficiency.

Worked example

Scenario

A health plan is deciding whether to cover Drug B, a newer therapy for a chronic condition, instead of Drug A, the current standard of care. An analyst pulls two-year totals from a clinical study: average cost per patient and average QALYs per patient for each treatment arm. The goal is to calculate the ICER and determine whether Drug B is cost-effective at the plan's willingness-to-pay threshold of $100,000 per QALY.

Dataset

Summary results per patient over a two-year follow-up — the two numbers an analyst would extract from a trial or observational cohort before computing the ICER.

treatmentmean_total_cost_usdmean_qalys
Drug A (comparator)270000.4
Drug B (new treatment)420000.7

Steps

  • Compute incremental cost: $42,000 − $27,000 = $15,000. Drug B costs $15,000 more per patient over two years.

  • Compute incremental effect: 0.70 − 0.40 = 0.30 QALYs. Drug B produces 0.30 additional QALYs per patient.

  • Compute the ICER: $15,000 ÷ 0.30 QALYs = $50,000 per QALY gained.

  • Compare to the willingness-to-pay threshold: $50,000 per QALY < $100,000 per QALY threshold.

Result

ICER = $50,000 per QALY gained. Because $50,000 is below the plan's $100,000 per QALY ceiling, Drug B is cost-effective — the plan gets an additional QALY for less than the maximum it is willing to pay.

Runnable example

python implementation

Patient-level within-study CEA from a claims-style analytic table, with censored-cost correction, ICER/NMB, joint bootstrap, and a CEAC. Required input (one row per patient, post data-management; cost already accrued from time zero and discounted): df:...

import numpy as np
import pandas as pd

def km_censoring_survival(obs_time, uncensored):
    # KM estimate of G(t)=P(C>t), treating *censoring* as the event of interest.
    # cens_event = 1 when the observation ends due to censoring (i.e. uncensored == 0).
    cens_event = 1 - np.asarray(uncensored, dtype=float)
    t = np.asarray(obs_time, dtype=float)
    order = np.argsort(t)
    t_sorted, d_sorted = t[order], cens_event[order]
    n = len(t_sorted)
    at_risk = n
    G, surv = {}, 1.0
    for i, (ti, di) in enumerate(zip(t_sorted, d_sorted)):
        if di == 1:                      # a censoring "event" drops survival
            surv *= (1.0 - 1.0 / at_risk)
        G[ti] = surv
        at_risk -= 1
    # step function lookup: G at the largest event time <= queried time
    times = np.array(sorted(G))
    vals = np.array([G[x] for x in times])
    def G_at(q):
        idx = np.searchsorted(times, q, side="right") - 1
        return vals[idx] if idx >= 0 else 1.0
    return np.vectorize(G_at)

def ipcw_mean_cost(arm_df):
    # Bang-Tsiatis: average fully-observed costs reweighted by 1/G(event time).
    G_at = km_censoring_survival(arm_df["obs_time"], arm_df["uncensored"])
    obs = arm_df[arm_df["uncensored"] == 1]
    w = 1.0 / np.clip(G_at(obs["obs_time"].to_numpy()), 1e-6, None)
    return float(np.sum(w * obs["cost"].to_numpy()) / len(arm_df))

def cea_point(df):
    c_i = ipcw_mean_cost(df[df.arm == "index"])
    c_c = ipcw_mean_cost(df[df.arm == "comparator"])
    e_i = df.loc[df.arm == "index", "effect"].mean()
    e_c = df.loc[df.arm == "comparator", "effect"].mean()
    d_cost, d_eff = c_i - c_c, e_i - e_c
    icer = d_cost / d_eff if d_eff != 0 else np.nan  # undefined / unstable when d_eff ~ 0
    return d_cost, d_eff, icer

def cea_bootstrap(df, wtp_grid, n_boot=2000, seed=7):
    # Resample patients WITHIN arm so the (delta_cost, delta_effect) pair stays jointly correlated.
    rng = np.random.default_rng(seed)
    idx = df[df.arm == "index"].reset_index(drop=True)
    cmp_ = df[df.arm == "comparator"].reset_index(drop=True)
    dC, dE = np.empty(n_boot), np.empty(n_boot)
    for b in range(n_boot):
        bi = idx.iloc[rng.integers(0, len(idx), len(idx))]
        bc = cmp_.iloc[rng.integers(0, len(cmp_), len(cmp_))]
        dC[b] = ipcw_mean_cost(bi) - ipcw_mean_cost(bc)
        dE[b] = bi["effect"].mean() - bc["effect"].mean()
    # CEAC: P(intervention cost-effective) = P(NMB > 0) at each willingness-to-pay lambda.
    ceac = {lam: float(np.mean(lam * dE - dC > 0)) for lam in wtp_grid}
    return dC, dE, ceac

if __name__ == "__main__":
    d_cost, d_eff, icer = cea_point(df)
    dC, dE, ceac = cea_bootstrap(df, wtp_grid=[50_000, 100_000, 150_000])
    print({"delta_cost": d_cost, "delta_effect": d_eff, "icer": icer,
           "nmb_at_100k": 100_000 * d_eff - d_cost, "ceac": ceac})
r implementation

Within-study CEA in R using survival::survfit for the IPCW censoring weights and a patient-level bootstrap for the joint (delta_cost, delta_effect) distribution and CEAC. Input mirrors the Python version: df: data.frame(person_id, arm in...

library(survival)

# Bang-Tsiatis IPCW mean cost: KM on the censoring indicator, then reweight observed costs.
ipcw_mean_cost <- function(d) {
  # event = censoring, so use (1 - uncensored) as the survfit event.
  fit <- survfit(Surv(obs_time, 1 - uncensored) ~ 1, data = d)
  G_at <- function(q) {
    i <- findInterval(q, fit$time)
    ifelse(i == 0, 1.0, fit$surv[i])
  }
  obs <- d[d$uncensored == 1, ]
  w <- 1 / pmax(G_at(obs$obs_time), 1e-6)
  sum(w * obs$cost) / nrow(d)
}

cea_point <- function(df) {
  ci <- ipcw_mean_cost(df[df$arm == "index", ])
  cc <- ipcw_mean_cost(df[df$arm == "comparator", ])
  ei <- mean(df$effect[df$arm == "index"])
  ec <- mean(df$effect[df$arm == "comparator"])
  d_cost <- ci - cc; d_eff <- ei - ec
  list(delta_cost = d_cost, delta_effect = d_eff,
       icer = if (d_eff != 0) d_cost / d_eff else NA_real_)  # unstable near d_eff = 0
}

cea_bootstrap <- function(df, wtp_grid, n_boot = 2000) {
  idx <- df[df$arm == "index", ]; cmp <- df[df$arm == "comparator", ]
  dC <- dE <- numeric(n_boot)
  for (b in seq_len(n_boot)) {
    bi <- idx[sample(nrow(idx), replace = TRUE), ]   # resample within arm to keep the pair correlated
    bc <- cmp[sample(nrow(cmp), replace = TRUE), ]
    dC[b] <- ipcw_mean_cost(bi) - ipcw_mean_cost(bc)
    dE[b] <- mean(bi$effect) - mean(bc$effect)
  }
  # CEAC: probability NMB > 0 across willingness-to-pay thresholds.
  ceac <- sapply(wtp_grid, function(lam) mean(lam * dE - dC > 0))
  names(ceac) <- wtp_grid
  list(dC = dC, dE = dE, ceac = ceac)
}

# Net-benefit regression (Hoch et al.): adjust the incremental NMB for covariates at a fixed lambda.
nb_regression <- function(df, lambda, covariates = c("age", "sex")) {
  df$nb <- lambda * df$effect - df$cost
  f <- reformulate(c("arm", covariates), response = "nb")
  summary(lm(f, data = df))$coefficients  # 'arm' coefficient = adjusted incremental NMB
}