QALY Utility Mapping (Crosswalking to Health-State Utilities)
A statistical crosswalk that predicts preference-based health-state utility values (e.g., EQ-5D) from non-preference-based clinical or patient-reported measures collected in real-world data, so that quality-adjusted life-years can be estimated for a cost-utility analysis.
In plain language
QALY utility mapping is a technique for putting a number on how good or bad a patient's health feels — on a 0-to-1 scale where 1 means perfect health and 0 means dead — when the study never directly asked patients the right question to get that number. Instead, analysts take a disease-specific symptom questionnaire the patients did fill out, apply a published translation formula (called a crosswalk or mapping), and convert those symptom scores into the 0-to-1 health-state values needed for economic analysis. Those converted values are then multiplied by the amount of time spent in each health state to calculate quality-adjusted life-years (QALYs), the standard currency health agencies worldwide use to decide whether a treatment is worth its cost.
QALY utility mapping
(also called crosswalking) is the estimation of a regression-type function that predicts a preference-based health-state utility — most often the EQ-5D index, anchored at 1 = full health and 0 = dead, with values below 0 allowed for states worse than death — from a non-preference-based source measure that was actually collected, such as a disease-specific PRO (FACT-G, EORTC QLQ-C30, HAQ-DI), a generic profile (SF-36/SF-12), or clinical variables (ECOG, tumour response, lab values). The fitted function is then applied to the real-world cohort to assign a utility at each measurement time, and those utilities are integrated over survival time to produce the quality-adjusted life-years (QALYs) that feed the incremental cost-utility ratio. Mapping is the standard fallback when a trial or RWE study did not field a preference-based instrument but a reimbursement dossier (NICE, CADTH, PBAC, IQWiG) requires utilities on a recognised value set.
Core conceptual distinction
Mapping is prediction of a value-set-anchored quantity, not measurement of utility and not a causal contrast. Three quantities must be kept separate. (1) The source measure (a symptom or function score) lives on its own scale and carries no preference weighting. (2) The target utility is a cardinal index on a population value set (UK EQ-5D-3L tariff, US EQ-5D-5L, etc.) — mapping is value-set-specific, and a function fitted to the UK tariff must not be reused to produce US-tariff QALYs. (3) The QALY is the area under the utility-versus-time curve, U(t) integrated from time zero to death or horizon, summed across the cohort and discounted. The estimand of a cost-utility analysis built on mapped utilities is incremental QALYs (and the ICER/net monetary benefit), conditional on the mapping model being a correct predictor in this population. Direct (response) mapping predicts the EQ-5D dimension responses and then applies the tariff; indirect (regression) mapping predicts the index value directly. The index is bounded above at 1, has a large spike of observations at 1.0 (full health) and a gap below it, so ordinary least squares is structurally wrong at the boundary even when its mean R² looks acceptable.
Pros, cons, and trade-offs
- vs collecting EQ-5D / a preference-based instrument directly: Direct elicitation needs no mapping model and no extrapolation assumptions, and is always the HTA-preferred option. Mapping's only advantage is that it rescues a dataset that never fielded a preference-based measure — at the cost of added prediction uncertainty, value-set dependence, and a known tendency to compress the utility distribution (overpredicting the worst states and underpredicting the best), which biases incremental QALYs toward the null. Prefer direct elicitation whenever the study can still field EQ-5D; map only when the preference-based data genuinely do not exist. - vs OLS index mapping: OLS is transparent and fast but violates the bounded, multimodal, ceiling-spiked structure of EQ-5D and produces impossible predictions above 1. The adjusted limited dependent variable mixture model (ALDVMM) and beta-regression / two-part / tobit approaches respect the bounds and the full-health spike and are now the methods recommended by the ISPOR task force and NICE DSU. Prefer a mixture/limited-dependent model over OLS for any submission; keep OLS only as a sensitivity comparison. - vs Markov / partitioned-survival modelling with externally sourced utilities: Pulling utilities from a published catalogue (Tufts CEA Registry, prior HTAs) avoids fitting a model but imports values from a different population and value set, often with poor face validity for your cohort. Mapping uses your own patients' source scores. Prefer mapping when you have a rich source PRO measured longitudinally in the actual cohort; prefer catalogue utilities when the source measure is too sparse to fit a stable model.
When to use
A cost-utility analysis or HTA submission requires utilities on a recognised value set; the RWE study (or its feeder trial) measured a non-preference-based PRO or clinical state at multiple time points but never an EQ-5D-type instrument; a published, externally validated mapping algorithm exists for that exact source measure → target value set pairing (or you can fit and validate one in a separate estimation sample); and you can attach utilities to time-indexed observations so they can be integrated over survival.
When NOT to use — and when it is actively misleading or dangerous
- No conceptual overlap between source and target. Mapping requires that the source measure capture the same health construct the value set prices (mobility, self-care, usual activities, pain, anxiety/depression). Mapping a pure biomarker (HbA1c, tumour size) onto EQ-5D fabricates a utility that has no behavioural basis; the function will fit noise and the predicted QALY gain is an artefact. - Extrapolating outside the estimation range. A model fitted in moderate disease applied to a real-world cohort with severe states (or to states-worse-than-death) predicts utilities the data never supported. The compression bias means incremental QALYs are systematically attenuated — the worse arm looks better than it is, which can flip an ICER. - Re-using a mapping across value sets or country tariffs. A UK-3L mapping applied to generate US-5L QALYs is simply the wrong quantity; the cardinal scale differs and the result is uninterpretable. - Propagating only point predictions. Reporting mapped QALYs without carrying the mapping model's prediction uncertainty into the probabilistic sensitivity analysis understates decision uncertainty and overstates the precision of the ICER — a substantive error in a reimbursement context. - Differential measurement timing by arm. If the source PRO is captured more often (or at sicker visits) in one arm, the area-under-the-curve integration is biased before any mapping question arises; this is a data-capture failure that mapping cannot fix.
Data-source operational depth
Mapping in RWE depends on whether the source PRO/clinical measure and the survival/cost spine come from the same linked record. - Claims (administrative): Pure claims carry no PRO or functional measure, so an index utility cannot be mapped from claims alone — there is nothing to map from. Claims contribute the survival/censoring spine (the time axis of the QALY) and resource costs, but utilities must be borrowed from a linked PRO source or a catalogue. A specific trap: in Medicare Advantage (MA) person-time the encounter and death capture differ from fee-for-service (FFS), so the QALY time axis itself is incomplete for MA-only members; restrict the integration window to observable FFS person-time or a linked vital-records death date rather than letting MA gaps masquerade as survival. - EHR: May contain structured PROMIS/disease-specific instruments and clinical states (ECOG, NYHA class) that are mappable, but capture is visit-driven and sicker patients are measured more often — so the observed utility trajectory is informatively sampled. Carry forward the last observation only over clinically plausible windows, model the missingness, and never let a long gap with no measurement be silently filled with the last (healthier) value. - Registry: Disease registries (oncology, rheumatology) often field the strongest source PROs and adjudicated clinical states, making them the best substrate for fitting and applying a mapping; their weakness is incomplete cost and sometimes death capture, so link to claims and a death index to complete the QALY denominator. - Linked claims–EHR–registry–vital records: The ideal substrate — registry/EHR supplies the mappable source measure, claims supply cost and resource use, vital records firm up the death date that closes the QALY integral. Linkage selection (only the linkable subset) and date discrepancies between the PRO visit and the service/death date must be reconciled before utilities are aligned to the time axis.
Worked example (claims-linked registry, oncology cost-utility)
Question: lifetime QALYs for two first-line regimens in metastatic disease, from a tumour registry that fielded EORTC QLQ-C30 at each cycle, linked to commercial + Medicare FFS claims for cost and to the NDI/state death file for mortality. (1) Eligibility / time axis: index date = first qualifying regimen administration; require continuous medical enrolment from index so person-time is observable; assign the regimen arm from the administered product. (2) Source measure: each QLQ-C30 assessment (physical-functioning, role, emotional, fatigue, pain, dyspnoea subscales) at a known `assess_date`. (3) Mapping: apply a pre-specified, published, externally validated QLQ-C30→EQ-5D-3L (UK tariff) algorithm — an ALDVMM, not OLS — to each assessment to get U at each `assess_date`, bounding predictions at 1 and allowing values <0. (4) Utility trajectory: within each person, order utilities by date and carry the value forward only to the next assessment (or a maximum 90-day plausibility window), setting U=0 at the linked death date so the curve closes at "dead". (5) QALY: integrate U(t) by the trapezoidal rule over [index, death or data end] per person — QALY_i = Σ over consecutive measurements of (U_j + U_{j+1})/2 × (t_{j+1} − t_j)/365.25 — then discount future increments at the jurisdiction rate (e.g., 3.5%/yr for NICE) and compare mean QALYs by arm. (6) Cost-utility: combine with mean discounted costs from the linked claims to form the ICER (ΔCost/ΔQALY) and net monetary benefit at the willingness-to-pay threshold. (7) Uncertainty: in the probabilistic sensitivity analysis, draw the mapping model coefficients from their estimated covariance (or bootstrap the whole map-then-integrate pipeline) so the prediction uncertainty of the crosswalk propagates into the ICER, and run scenario analyses on the carry-forward window and an alternative value set. Sensitivity checks: compare mapped against any subset with directly measured EQ-5D, and confirm the mapped distribution reproduces the ceiling spike at U=1 rather than smearing it.
Interpreting the output
Patient 1001 accumulated 0.3275 QALYs over 6 months, starting with a mapped utility of 0.730 at baseline (Physical Function = 80, Pain = 20, Fatigue = 30) and declining to 0.655 and then 0.580 as symptoms worsened.
(1) Formal interpretation. The mapped utility of 0.730 is the predicted EQ-5D index value for a patient with those symptom scores, estimated from a regression model fitted in a separate study population where both the clinical instrument and EQ-5D were measured. It is not a directly elicited preference; it inherits two layers of uncertainty: sampling uncertainty in the mapping model coefficients and model-specification uncertainty (choice of regression form, covariates, and target value set). The QALY accumulation applies the trapezoidal rule across intervals — the first 3-month segment contributes 0.1731 QALYs and the second 0.1544, reflecting the lower utility in the latter period. These mapped QALYs are appropriate inputs to a cost-utility ICER when direct EQ-5D data are unavailable, provided the mapping model is validated and its uncertainty is propagated into the PSA.
(2) Practical interpretation. The patient spent 6 months in health states ranging from 0.730 to 0.580 on the 0–1 utility scale. Mapping uncertainty means this figure could be noticeably higher or lower depending on which published algorithm is applied; a sensitivity analysis comparing at least two published maps for the same instrument is mandatory in HTA submissions. The mapping estimate should never be presented as equivalent in precision to a directly measured EQ-5D utility — decision-makers should understand it is an approximation.
Worked example
Scenario
A rheumatology registry collected EORTC-style symptom scores — physical functioning (PF), pain, and fatigue, each on a 0-to-100 scale — from patient 1001 at three visits over six months, but never administered the EQ-5D. An analyst needs QALYs for a cost-utility analysis. Using the YAML source file's illustrative mapping formula, the analyst converts each visit's symptom scores into a utility, then uses the trapezoidal rule to compute QALYs over the two 3-month intervals.
Dataset
Raw symptom assessment records for one patient — three visits over six months. Physical functioning (PF) is higher when the patient functions better; pain and fatigue are higher when symptoms are worse (all on a 0-to-100 scale).
| person_id | assess_date | pf_score | pain_score | fatigue_score |
|---|---|---|---|---|
| 1001 | 2024-01-01 | 80 | 20 | 30 |
| 1001 | 2024-04-01 | 70 | 30 | 40 |
| 1001 | 2024-07-01 | 60 | 40 | 50 |
Steps
Apply the mapping formula to each visit: mapped_utility = 0.90 - 0.0030 x (100 - PF) - 0.0025 x pain - 0.0020 x fatigue.
Visit 1 (2024-01-01): utility = 0.90 - 0.0030 x (100 - 80) - 0.0025 x 20 - 0.0020 x 30 = 0.90 - 0.060 - 0.050 - 0.060 = 0.730.
Visit 2 (2024-04-01): utility = 0.90 - 0.0030 x (100 - 70) - 0.0025 x 30 - 0.0020 x 40 = 0.90 - 0.090 - 0.075 - 0.080 = 0.655.
Visit 3 (2024-07-01): utility = 0.90 - 0.0030 x (100 - 60) - 0.0025 x 40 - 0.0020 x 50 = 0.90 - 0.120 - 0.100 - 0.100 = 0.580.
Each interval between visits is 91 days, treated here as 0.25 years for clean arithmetic (a standard 3-month approximation).
Segment 1 QALY (Jan to Apr): average utility = (0.730 + 0.655) / 2 = 0.6925; QALYs = 0.6925 x 0.25 = 0.1731.
Segment 2 QALY (Apr to Jul): average utility = (0.655 + 0.580) / 2 = 0.6175; QALYs = 0.6175 x 0.25 = 0.1544.
Total QALYs over 6 months = 0.1731 + 0.1544 = 0.3275.
Result
Patient 1001 accumulated 0.3275 QALYs over the 6-month observation window. Because their symptom scores worsened across visits (PF declined from 80 to 60, pain rose from 20 to 40, fatigue from 30 to 50), each mapped utility fell accordingly (0.730 to 0.655 to 0.580), and the second segment contributed fewer QALYs (0.1544) than the first (0.1731). A full cost-utility analysis would compare mean QALYs across treatment arms and pair them with costs to form an incremental cost-effectiveness ratio.
Runnable example
python implementation
Apply a pre-specified utility-mapping function to a longitudinal source-PRO table, then integrate utility over time into discounted QALYs. Required inputs (already cleaned, one row per person-assessment): pro : person_id, arm, assess_date (datetime), and...
import numpy as np
import pandas as pd
DISCOUNT_RATE = 0.035 # NICE reference-case annual rate for effects
CARRY_MAX_DAYS = 90 # max plausibility window for last-observation-carried-forward
def map_utility(pro: pd.DataFrame) -> pd.Series:
# PLACEHOLDER for a published, externally validated mapping (e.g., ALDVMM coefficients).
# Replace with the real linear predictor / mixture prediction; keep the boundary handling.
lp = (0.90
- 0.0030 * (100 - pro["physical_functioning"])
- 0.0025 * pro["pain"]
- 0.0020 * pro["fatigue"])
return np.clip(lp, a_min=None, a_max=1.0) # cap at full health; values <0 allowed
def qalys_by_person(pro: pd.DataFrame, death: pd.DataFrame) -> pd.DataFrame:
pro = pro.sort_values(["person_id", "assess_date"]).copy()
pro["utility"] = map_utility(pro)
out = []
for pid, g in pro.groupby("person_id"):
d = death.loc[death["person_id"] == pid].iloc[0]
end = d["death_date"] if pd.notna(d["death_date"]) else d["obs_end"]
# Knot points: each assessment, plus a terminal knot (U=0 at death, else last utility at obs_end).
t = list(g["assess_date"]); u = list(g["utility"])
t.append(end); u.append(0.0 if pd.notna(d["death_date"]) else u[-1])
qaly = 0.0
for j in range(len(t) - 1):
seg_days = (t[j + 1] - t[j]).days
if seg_days <= 0:
continue
# Carry-forward plausibility: long gaps with no measurement are not credited full utility.
eff_days = min(seg_days, CARRY_MAX_DAYS) if seg_days > CARRY_MAX_DAYS else seg_days
yrs_from_index = (t[j] - d["index_date"]).days / 365.25
disc = (1.0 + DISCOUNT_RATE) ** (-max(yrs_from_index, 0.0))
qaly += (u[j] + u[j + 1]) / 2.0 * (eff_days / 365.25) * disc # discounted trapezoid
out.append({"person_id": pid, "arm": g["arm"].iloc[0], "qaly": qaly})
return pd.DataFrame(out)
# qalys = qalys_by_person(pro, death)
# qalys.groupby("arm")["qaly"].mean() # mean discounted QALYs per arm -> feeds the ICERr implementation
Same map-then-integrate pipeline in R. Inputs mirror the Python version: pro : person_id, arm, assess_date (Date), source-measure subscale columns death : person_id, death_date (Date or NA), index_date (Date), obs_end (Date) map_utility() is a placeholder...
library(dplyr)
DISCOUNT_RATE <- 0.035
CARRY_MAX_DAYS <- 90
map_utility <- function(pro) {
# PLACEHOLDER for a published, externally validated mapping (e.g., ALDVMM).
lp <- 0.90 -
0.0030 * (100 - pro$physical_functioning) -
0.0025 * pro$pain -
0.0020 * pro$fatigue
pmin(lp, 1.0) # cap at full health; values < 0 allowed (worse than death)
}
qalys_by_person <- function(pro, death) {
pro <- pro %>% arrange(person_id, assess_date) %>% mutate(utility = map_utility(.))
split(pro, pro$person_id) |>
lapply(function(g) {
d <- death[death$person_id == g$person_id[1], ][1, ]
end <- if (!is.na(d$death_date)) d$death_date else d$obs_end
t <- c(g$assess_date, end)
u <- c(g$utility, if (!is.na(d$death_date)) 0.0 else tail(g$utility, 1))
qaly <- 0.0
for (j in seq_len(length(t) - 1L)) {
seg <- as.numeric(t[j + 1] - t[j])
if (seg <= 0) next
eff <- if (seg > CARRY_MAX_DAYS) CARRY_MAX_DAYS else seg
yrs <- as.numeric(t[j] - d$index_date) / 365.25
disc <- (1 + DISCOUNT_RATE) ^ (-max(yrs, 0))
qaly <- qaly + (u[j] + u[j + 1]) / 2 * (eff / 365.25) * disc
}
data.frame(person_id = g$person_id[1], arm = g$arm[1], qaly = qaly)
}) |>
bind_rows()
}
# qalys <- qalys_by_person(pro, death)
# aggregate(qaly ~ arm, data = qalys, FUN = mean) # mean discounted QALYs per arm