Cost-of-Illness (COI) Study
An economic-burden study that estimates the total cost a disease imposes on a defined population over a defined period — typically direct medical, direct non-medical, and indirect (productivity) costs — without comparing competing interventions.
In plain language
A cost-of-illness study adds up everything one disease costs over a set period for a defined group of people, then reports that total as a dollar figure. It counts direct medical costs (hospital stays, drugs, outpatient visits) and indirect costs (the wages a patient loses when illness keeps them out of work), but it does not compare two treatments and cannot tell you whether a drug is worth its price. Think of it as measuring the size of the problem, not the value of any fix. One honest caveat: it only captures the costs the data can see, so anything paid out of pocket or handled outside the records is missing.
A cost-of-illness (COI) study measures the aggregate economic burden of a disease, condition, or risk factor in a defined population and period. It answers "how much does this disease cost society (or a payer)?" rather than "is treatment A worth more than treatment B?". The total is conventionally decomposed into direct medical costs (inpatient, outpatient, pharmacy, procedures, devices), direct non-medical costs (transportation, paid caregiving, home modification), and indirect costs (lost productivity from morbidity, premature mortality, and informal caregiving, valued by the human-capital or friction-cost method). COI is a descriptive economic-evaluation method: it has no incremental contrast and no QALY, so it cannot establish value for money. Its outputs justify research prioritization, size a market or unmet need, and supply the background burden figure for downstream cost-effectiveness, budget-impact, and HTA submissions.
Core conceptual distinction
. Three independent design axes define a COI study, and conflating them is the most common error. (1) Prevalence-based vs incidence-based: a prevalence-based COI sums all costs incurred by everyone with the disease during a calendar window (the standard for annual national-burden figures and payer planning); an incidence-based COI follows new cases from onset and sums lifetime costs, discounted to present value (the right input for prevention/screening value, but it requires long longitudinal follow-up). (2) Gross (all-cause) vs net (attributable/excess): gross costing counts every dollar a patient spends; net/attributable costing subtracts the cost those patients would have incurred without the disease, usually via a matched non-diseased comparator — this is the methodologically defensible burden but demands a causal-comparison design. (3) Top-down vs bottom-up: top-down apportions national aggregate spending to the disease using attributable fractions; bottom-up builds the estimate from patient-level resource use × unit costs (the natural approach in claims/EHR). The estimand must be stated explicitly: COI does not estimate a treatment effect, and its "attributable cost" is an association unless an active-comparator/new-user or adjusted design is layered on.
Pros, cons, and trade-offs
(specific & comparative, naming the alternatives). - vs cost-effectiveness / cost-utility analysis: COI quantifies burden, not value; it has no comparator intervention, no incremental cost-effectiveness ratio, and no health-outcome denominator. Cheaper and faster to produce and useful for advocacy/prioritization, but it can never answer "should we pay for this drug?". Prefer COI only to size the problem; switch to CEA/CUA the moment a decision involves choosing between interventions. - vs budget-impact analysis (BIA): BIA is the affordability cousin — it projects net spending changes to a specific budget holder over a short horizon under a new intervention's uptake. COI is intervention-agnostic and societal/payer-wide. A COI total is often the denominator a BIA quotes against. - Gross vs attributable costing: all-cause/gross costs are trivial to compute from claims but systematically overstate disease burden by including unrelated care; attributable (excess) costing via a matched comparator is far more defensible but introduces confounding, matching, and competing-risk problems. - Human-capital vs friction-cost for indirect costs: human-capital values all foregone lifetime productivity (large numbers, favored in the US literature); friction-cost values only the period until a worker is replaced (smaller, favored in some European guidance). The choice can move the indirect-cost total several-fold, so it must be pre-specified and reported transparently.
When to use
. Establishing the magnitude and distribution of disease burden for a payer, manufacturer, or policymaker; generating the background-burden and natural-history cost inputs that feed a CEA/CUA or BIA; supporting research-prioritization or market-sizing; quantifying the burden of an unmet need in a value dossier. Bottom-up, prevalence-based, payer-perspective COI is the workhorse for US claims-based burden studies.
When NOT to use — and when it is actively misleading or dangerous
. - As evidence of an intervention's value. A large COI total is routinely (mis)used to argue a therapy is worth its price. It is not — burden says nothing about how much of that cost a treatment can avert at what incremental cost. Presenting COI as cost-effectiveness is the field's signature abuse. - Gross all-cause cost reported as "the cost of the disease." Without an attributable/excess design, the figure includes care patients would have needed anyway and overstates burden, sometimes by multiples. - Double counting. Summing prevalence-based annual costs across multiple years, or adding indirect mortality costs (already a lifetime value) to multi-year direct costs, inflates totals. Mixing top-down attributable fractions with bottom-up patient costs double-counts the same care. - Cross-study addition. COI totals from studies with different perspectives, cost years (no inflation adjustment), populations, or costing methods are not additive and must not be summed to a "total national burden." - Immortal-time / survivorship artifacts in lifetime (incidence-based) COI. Requiring survival to a landmark to accrue costs, or annualizing costs only over observed (survivor) person-time, biases per-patient burden downward in fatal diseases — costs that cluster near death are differentially censored.
Data-source operational depth
. - Administrative claims (FFS, commercial, Medicaid): the standard substrate for bottom-up burden. Cost = adjudicated/allowed/paid amount, not charges; standardize across payers and inflate to a common cost year (CPI medical-care component or GDP deflator). Require continuous medical + pharmacy enrollment across the measurement window so per-patient-per-month (PPPM) and per-patient-per-year (PPPY) denominators reflect true observable person-time, not enrollment gaps. Failure modes: Medicare Advantage encounter records lack reliable paid amounts and MA-only person-time has no FFS claims — restrict national-burden estimates to FFS Parts A/B/D with complete capture and never pool MA encounter costs with FFS paid amounts. Capitated/bundled arrangements zero-out component costs. Differential competing risk of death by disease severity truncates cost accrual in the sickest patients, biasing prevalence-based annual costs; annualize over observed person-time (PPPM × 12) rather than forcing a full-year denominator. Carve-out behavioral/pharmacy benefits and out-of-network care are invisible. Indirect (productivity) costs are absent from claims entirely and must be modeled externally. - EHR: captures clinical detail and severity for attribution but not the full cost of care delivered elsewhere (referrals, other systems); chargemaster prices are not economic costs — apply cost-to-charge ratios or a standardized fee schedule. Visit-driven capture means sicker, in-system patients are over-represented. - Registry: strong for incident-case identification, severity, and outcomes (ideal denominator for incidence-based lifetime COI) but weak for complete cost; link to claims for the full resource-use ledger and to a death index for mortality-cost valuation. - Linked claims–EHR–vital-records or claims–survey: the ideal substrate — claims completeness + EHR severity for attribution + survey/administrative wage data for indirect costs — but linkage selection (only the linkable subset) and date reconciliation across sources must be handled before person-time and cost windows are fixed.
Worked claims example (attributable, prevalence-based, payer perspective)
Question: the 1-year direct medical burden of heart failure (HF) attributable to the disease among commercially insured + Medicare FFS adults. (1) Cases: adults with ≥1 inpatient or ≥2 outpatient HF diagnoses (ICD-10 I50.x) ≥30 days apart in 2023; index_date = first qualifying claim. (2) Continuous enrollment: require medical + pharmacy enrollment for the full 2023 observation window (and a 365-day baseline for matching), excluding MA-only person-time so paid amounts are observed. (3) Cost build: sum allowed/paid amounts across inpatient, outpatient, professional, and pharmacy claims by person_id within the window; convert each person to PPPM = total_cost / observed_member_months and annualize to PPPY = PPPM × 12 so patients who die or disenroll mid-year are not penalized by a forced 12-month denominator. (4) Attribution: match each HF case 1:1 to a non-HF control on age, sex, region, index calendar month, and a comorbidity score using only baseline-window data; attributable cost = case PPPY − matched-control PPPY, which nets out the care these patients would have incurred regardless. (5) Aggregation: population burden = mean attributable PPPY × number of prevalent HF enrollees, with patient-level bootstrap CIs to reflect the right-skewed cost distribution; inflate all 2023 dollars to the reporting cost year. Report perspective (payer), cost components, the gross-vs-attributable split, and that productivity/informal-care costs are excluded.
Interpreting the output
A COI study for a single heart-failure patient from a societal perspective reports a per-patient total of $25,000: $19,750 in direct medical costs (79%) — comprising $14,200 inpatient, $3,150 pharmacy, and $2,400 outpatient — and $5,250 in indirect costs from lost productivity (21%).
(1) Formal interpretation. The $25,000 per-patient total is an accounting aggregate, not a causal counterfactual. Unless this figure was derived by subtracting matched-control costs, it includes spending that would have occurred regardless of heart failure (background comorbidity care, routine preventive visits). The 79% direct / 21% indirect split is perspective-dependent: the societal view includes lost wages via the human-capital method; a payer-perspective analysis would exclude the $5,250 indirect component entirely, yielding $19,750. Each cost component carries its own measurement limitations — inpatient costs from claims reflect allowed amounts, not societal resource consumption, and lost productivity requires wage-rate assumptions. The total is reported as a point estimate; the right-skewed cost distribution means the median patient cost is substantially lower.
(2) Practical interpretation. The $25,000 COI figure sizes the problem for a disease-awareness or formulary argument. For an HTA submission, a reviewer will ask what the comparable cost is for a matched non-HF patient — if that figure is not reported, the $25,000 cannot be attributed to heart failure. Use this estimate to frame unmet need and to calibrate cost-offset claims, not as a standalone causal burden statement.
Worked example
Scenario
We want the one-year cost of illness for a single patient with heart failure, seen from a societal view so both their medical bills and their lost wages count. An analyst pulls the dollars the patient generated in each spending bucket over the calendar year, labels each bucket as either a direct medical cost or an indirect cost, then adds them up and reports the total along with how much of it is medical care versus lost work.
Dataset
The per-patient annual dollars an analyst would assemble, one row per cost bucket.
| cost_component | type | annual_cost |
|---|---|---|
| inpatient_hospital | direct | 14200 |
| prescription_drugs | direct | 3150 |
| outpatient_visits | direct | 2400 |
| lost_productivity | indirect | 5250 |
Steps
List every cost bucket the patient generated in the year, with its dollar amount, exactly as the table shows.
Tag each bucket as direct (care the patient received) or indirect (wages lost to illness): the three care buckets are direct, lost productivity is indirect.
Add the three direct buckets: 14,200 + 3,150 + 2,400 = 19,750 in direct medical costs.
The single indirect bucket stands alone: 5,250 in lost productivity.
Add the direct and indirect subtotals to get the full cost of illness: 19,750 + 5,250 = 25,000.
Result
Total cost of illness = $25,000 per patient for the year. The direct/indirect split is $19,750 direct (79%) and $5,250 indirect (21%); the three direct buckets are $14,200 inpatient + $3,150 drugs + $2,400 outpatient = $19,750.
Runnable example
python implementation
Bottom-up, prevalence-based ATTRIBUTABLE cost from claims-style inputs. Required inputs (cleaned, de-duplicated): claims : person_id, paid_amount (float), service_date (datetime), claim_type {'IP','OP','PROF','RX'} cases : person_id, index_date (datetime),...
import pandas as pd
import numpy as np
WINDOW_START = pd.Timestamp("2023-01-01")
WINDOW_END = pd.Timestamp("2023-12-31")
def patient_cost(claims: pd.DataFrame, enroll: pd.DataFrame) -> pd.DataFrame:
# Keep only paid claims inside the measurement window; sum to person-level total cost.
c = claims[(claims["service_date"] >= WINDOW_START) &
(claims["service_date"] <= WINDOW_END)]
total = c.groupby("person_id")["paid_amount"].sum().rename("total_cost")
# PPPM over OBSERVED member-months avoids penalizing mid-year death/disenrollment; annualize to PPPY.
out = enroll.set_index("person_id").join(total).fillna({"total_cost": 0.0})
out = out[out["member_months_observed"] > 0].copy()
out["pppm"] = out["total_cost"] / out["member_months_observed"]
out["pppy"] = out["pppm"] * 12.0
return out.reset_index()[["person_id", "total_cost", "member_months_observed", "pppm", "pppy"]]
def attributable_pppy(cases: pd.DataFrame, costs: pd.DataFrame,
n_boot: int = 1000, seed: int = 1) -> dict:
# Attributable cost = case PPPY minus matched-control PPPY, paired within match_id.
df = cases.merge(costs[["person_id", "pppy"]], on="person_id", how="inner")
wide = (df.pivot_table(index="match_id", columns="is_case", values="pppy")
.rename(columns={1: "case_pppy", 0: "ctrl_pppy"})
.dropna())
diff = (wide["case_pppy"] - wide["ctrl_pppy"]).to_numpy()
# Patient-level (cluster) bootstrap over matched pairs to respect the right-skewed cost distribution.
rng = np.random.default_rng(seed)
n = len(diff)
boot = np.array([diff[rng.integers(0, n, n)].mean() for _ in range(n_boot)])
return {
"mean_attributable_pppy": float(diff.mean()),
"ci95_low": float(np.percentile(boot, 2.5)),
"ci95_high": float(np.percentile(boot, 97.5)),
"n_pairs": int(n),
}r implementation
R version with data.table. Inputs mirror the Python version: claims : person_id, paid_amount, service_date (Date), claim_type cases : person_id, index_date (Date), is_case (1/0), match_id enroll : person_id, member_months_observed (integer, FFS-observable,...
library(data.table)
WINDOW_START <- as.Date("2023-01-01")
WINDOW_END <- as.Date("2023-12-31")
patient_cost <- function(claims, enroll) {
setDT(claims); setDT(enroll)
c <- claims[service_date >= WINDOW_START & service_date <= WINDOW_END]
total <- c[, .(total_cost = sum(paid_amount)), by = person_id]
out <- merge(enroll, total, by = "person_id", all.x = TRUE)
out[is.na(total_cost), total_cost := 0]
out <- out[member_months_observed > 0]
out[, pppm := total_cost / member_months_observed] # observed-time denominator
out[, pppy := pppm * 12] # annualize
out[, .(person_id, total_cost, member_months_observed, pppm, pppy)]
}
attributable_pppy <- function(cases, costs, n_boot = 1000L, seed = 1L) {
setDT(cases); setDT(costs)
df <- merge(cases, costs[, .(person_id, pppy)], by = "person_id")
# One case PPPY and one control PPPY per matched pair.
wide <- dcast(df, match_id ~ is_case, value.var = "pppy")
setnames(wide, c("0", "1"), c("ctrl_pppy", "case_pppy"))
wide <- wide[!is.na(case_pppy) & !is.na(ctrl_pppy)]
diff <- wide$case_pppy - wide$ctrl_pppy
set.seed(seed)
n <- length(diff)
boot <- replicate(n_boot, mean(diff[sample.int(n, n, replace = TRUE)]))
list(mean_attributable_pppy = mean(diff),
ci95_low = unname(quantile(boot, 0.025)),
ci95_high = unname(quantile(boot, 0.975)),
n_pairs = n)
}