← Methods repository
concept

Major Adverse Cardiovascular Events (MACE)

A composite cardiovascular endpoint, commonly defined as cardiovascular death, nonfatal myocardial infarction, and nonfatal stroke, with variants that add hospitalization, revascularization, heart failure, or unstable angina components.

Outcome_Measuremacecardiovascular-outcomescomposite-endpointmyocardial-infarctionstrokecardiovascular-deathendpoint-definition
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

MACE is a shorthand for a serious cardiovascular-event composite. The common version is cardiovascular death, heart attack, or stroke. But different studies use different component lists, so the word MACE is not enough by itself.

MACE is not one universal endpoint. It is a family of cardiovascular composite endpoints whose interpretation depends on the component list, event ascertainment rules, and whether the analysis counts first event only or recurrent components. The most common "3-point MACE" definition combines cardiovascular death, nonfatal myocardial infarction, and nonfatal stroke. Other studies use 4-point, 5-point, or study-specific MACE definitions that add hospitalization for unstable angina, urgent revascularization, heart failure hospitalization, or all-cause death.

In RWE, MACE is an outcome-algorithm problem plus a composite-endpoint problem. Nonfatal MI and stroke are usually identified through inpatient diagnosis algorithms and validated code lists. Cardiovascular death requires cause-of-death linkage; all-cause death is easier but changes the estimand. The composite date is usually the earliest qualifying component date, but the component that fires must be retained because a treatment effect dominated by one component is not a general cardiovascular benefit.

Cross-study comparison is especially risky. "MACE" in a diabetes CV outcomes trial, a device registry, and a claims-based safety study may have different death definitions, claim-position requirements, washout periods, and adjudication standards. RWE reports should spell out the component list in the title, table shell, and methods, not only in the appendix.

Pros, cons, and trade-offs

MACE is attractive because it increases event yield and gives clinicians a familiar serious cardiovascular endpoint. The trade-off is interpretability. Composite precision is not useful if a softer or more frequent component drives the result while cardiovascular death, myocardial infarction, and stroke move differently. Claims-based MACE adds another trade-off: inpatient MI and stroke algorithms may be reproducible, but cause-specific death and out-of-network events require linkage that many datasets do not have.

When to use

Use MACE when the components are clinically coherent, event capture is fit for purpose, and the estimand is time to the first serious cardiovascular event. Keep the first firing component, run component-specific estimates, and state whether the endpoint is 3-point MACE, expanded MACE, MACCE, or a study-specific variant.

When NOT to use - and when it is actively misleading

Do not write "MACE" when the study only observes nonfatal claims events or all-cause death. Do not combine revascularization, unstable angina, heart failure, and death without showing component results. It is actively misleading to compare a nonfatal claims-only composite with a trial-adjudicated 3-point MACE endpoint as if the endpoints were the same.

Index definitions

Source-backed definitions and variants for the index or checklist family.

namedefinitionsourceusenotes
3-point MACECardiovascular death, nonfatal myocardial infarction, or nonfatal stroke; event date is the first qualifying component date.Cardiovascular outcomes trial convention and FDA endpoint guidanceStandard cardiovascular safety/effectiveness composite when cause-specific death is observable.Requires reliable death and cause-of-death linkage for the fatal component.
Nonfatal MACENonfatal myocardial infarction or nonfatal stroke without the fatal cardiovascular-death component.Claims-based RWE adaptationSensitivity or restricted endpoint when cause-specific mortality is unavailable.Should not be labeled full 3-point MACE.
Expanded MACE3-point MACE plus protocol-specific components such as unstable angina hospitalization, revascularization, or heart failure hospitalization.Study-specific cardiovascular endpoint definitionsHigher event yield or broader net cardiovascular burden.Report every component and avoid comparing directly with 3-point MACE.

Worked example

Scenario

A claims study of a diabetes drug defines 3-point MACE as cardiovascular death, nonfatal inpatient MI, or nonfatal inpatient ischemic stroke. One patient has an inpatient MI on day 143, later revascularization on day 151, and death without cause information on day 220.

Dataset

Candidate cardiovascular events for one patient.

daycandidate_eventsourceendpoint_component
143inpatient myocardial infarctionprincipal inpatient ICD-10-CM diagnosisnonfatal MI
151coronary revascularizationCPT/ICD-10-PCS procedurenot in 3-point MACE
220death, cause unknowndeath-date linkage onlynot cardiovascular death unless cause source supports it

Steps

  • Apply the MI algorithm and de-duplication window to the inpatient claim.

  • Exclude revascularization because the protocol specified 3-point MACE, not expanded MACE.

  • Do not classify death as cardiovascular death without cause-of-death evidence.

  • Assign first MACE date to day 143 and retain winning component = nonfatal MI.

Result

The time-to-first 3-point MACE endpoint fires on day 143. The later revascularization and cause-unknown death are reported separately or handled in sensitivity analyses.

Runnable example

python implementation

Construct first 3-point MACE from already validated component tables. Inputs: index : person_id, index_date events : person_id, event_date, component, source Components must already satisfy the study's event algorithms. Keep the winning component for...

import pandas as pd

MACE3 = {"cardiovascular_death", "nonfatal_mi", "nonfatal_stroke"}

def first_mace(index, events, followup_end=None):
    candidates = events[events["component"].isin(MACE3)].copy()
    candidates = candidates.merge(index[["person_id", "index_date"]], on="person_id", how="inner")
    candidates = candidates[candidates["event_date"] >= candidates["index_date"]]
    if followup_end is not None:
        candidates = candidates.merge(followup_end[["person_id", "end_date"]], on="person_id", how="left")
        candidates = candidates[candidates["end_date"].isna() | (candidates["event_date"] <= candidates["end_date"])]
    candidates = candidates.sort_values(["person_id", "event_date", "component"])
    out = candidates.groupby("person_id", as_index=False).first()
    out["mace3"] = 1
    out["time_to_mace_days"] = (out["event_date"] - out["index_date"]).dt.days
    return index.merge(
        out[["person_id", "mace3", "event_date", "component", "source", "time_to_mace_days"]],
        on="person_id",
        how="left",
    ).fillna({"mace3": 0})
r implementation

R/data.table version for first 3-point MACE. Component rows are assumed to be validated upstream.

library(data.table)

first_mace <- function(index, events, followup_end = NULL) {
  setDT(index); setDT(events)
  mace3 <- c("cardiovascular_death", "nonfatal_mi", "nonfatal_stroke")
  cand <- events[component %in% mace3]
  cand <- merge(cand, index[, .(person_id, index_date)], by = "person_id")
  cand <- cand[event_date >= index_date]
  if (!is.null(followup_end)) {
    setDT(followup_end)
    cand <- merge(cand, followup_end[, .(person_id, end_date)], by = "person_id", all.x = TRUE)
    cand <- cand[is.na(end_date) | event_date <= end_date]
  }
  setorder(cand, person_id, event_date, component)
  first <- cand[, .SD[1], by = person_id]
  first[, `:=`(mace3 = 1L, time_to_mace_days = as.integer(event_date - index_date))]
  out <- merge(index, first[, .(person_id, mace3, event_date, component, source, time_to_mace_days)],
               by = "person_id", all.x = TRUE)
  out[is.na(mace3), mace3 := 0L]
  out[]
}