Introduction

This report analyzes CMS’s Outcome of Care Measures file: 30-day risk-adjusted mortality and readmission rates for three high-stakes conditions — heart attack (AMI), heart failure (HF), and pneumonia (PN) — reported for 4,706 U.S. hospitals. For each condition and each outcome, CMS provides the hospital’s rate, a confidence interval, the patient count behind it, and a categorical comparison to the national rate (Better, No Different, Worse, or Number of Cases Too Small).

This is a quality/outcomes file, distinct from the structural hospital directory analyzed previously — it says how hospitals performed on these three measures, not what type of hospital they are or who owns them.

raw <- read_csv("outcome-of-care-measures.csv", col_types = cols(.default = "c"))

# Six rate measures, each with a matching comparison-to-US-rate column
measures <- tibble::tribble(
  ~key,      ~condition,      ~outcome,       ~rate_col,
  "ha_mort", "Heart Attack",  "Mortality",    "Hospital 30-Day Death (Mortality) Rates from Heart Attack",
  "hf_mort", "Heart Failure", "Mortality",    "Hospital 30-Day Death (Mortality) Rates from Heart Failure",
  "pn_mort", "Pneumonia",     "Mortality",    "Hospital 30-Day Death (Mortality) Rates from Pneumonia",
  "ha_read", "Heart Attack",  "Readmission",  "Hospital 30-Day Readmission Rates from Heart Attack",
  "hf_read", "Heart Failure", "Readmission",  "Hospital 30-Day Readmission Rates from Heart Failure",
  "pn_read", "Pneumonia",     "Readmission",  "Hospital 30-Day Readmission Rates from Pneumonia"
) %>%
  mutate(comp_col = str_replace(rate_col, "^Hospital", "Comparison to U.S. Rate - Hospital"))

hospitals <- raw %>%
  mutate(across(all_of(measures$rate_col), ~ as.numeric(na_if(., "Not Available"))))

About the data

tibble(
  Metric = c("Total hospitals", "States / territories represented", "Distinct counties",
             "Conditions covered", "Outcome types covered"),
  Value = c(
    comma(nrow(hospitals)),
    n_distinct(hospitals$State),
    comma(n_distinct(hospitals$`County Name`)),
    "Heart Attack, Heart Failure, Pneumonia",
    "30-Day Mortality, 30-Day Readmission"
  )
) %>%
  kable(caption = "Dataset snapshot") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Dataset snapshot
Metric Value
Total hospitals 4,706
States / territories represented 54
Distinct counties 1,498
Conditions covered Heart Attack, Heart Failure, Pneumonia
Outcome types covered 30-Day Mortality, 30-Day Readmission

Unlike a facility directory, most of the analytic value here is in the rate columns — and most of those cells are legitimately blank. CMS suppresses a hospital’s rate whenever it has too few qualifying cases to report a statistically reliable number, so “missing” here mostly means small volume, not bad data.


Data completeness by measure

completeness <- hospitals %>%
  summarise(across(all_of(measures$rate_col), ~ sum(!is.na(.)))) %>%
  pivot_longer(everything(), names_to = "rate_col", values_to = "n_reported") %>%
  left_join(measures, by = "rate_col") %>%
  mutate(
    pct_reported = n_reported / nrow(hospitals),
    label = paste(condition, outcome)
  )

ggplot(completeness, aes(x = fct_reorder(label, n_reported), y = n_reported, fill = outcome)) +
  geom_col() +
  geom_text(aes(label = paste0(comma(n_reported), " (", percent(pct_reported, accuracy = 1), ")")),
            hjust = -0.05, size = 3.3) +
  coord_flip(clip = "off") +
  scale_y_continuous(labels = comma, expand = expansion(mult = c(0, 0.25))) +
  scale_fill_manual(values = c(Mortality = "#e34a33", Readmission = "#2c7fb8")) +
  labs(title = "Hospitals with a reportable rate, by measure",
       x = NULL, y = "Hospitals with a non-suppressed rate", fill = NULL) +
  theme_minimal(base_size = 12)

Heart attack measures have by far the most suppression — only about 58% of hospitals have a reportable heart-attack mortality rate, and just 50% have a reportable heart-attack readmission rate, because AMI is treated at a comparatively small number of hospitals in high enough volume to meet CMS’s reliability threshold. Pneumonia is the most completely reported measure (90% mortality, 90% readmission), since pneumonia is common and treated at nearly every acute care hospital. Any comparison across conditions should keep this in mind: the heart-attack numbers describe a narrower, larger-volume subset of hospitals than the pneumonia numbers do.


Rate distributions

rate_long <- hospitals %>%
  select(`Provider Number`, State, all_of(measures$rate_col)) %>%
  pivot_longer(all_of(measures$rate_col), names_to = "rate_col", values_to = "rate") %>%
  left_join(measures, by = "rate_col") %>%
  filter(!is.na(rate))

rate_long %>%
  group_by(condition, outcome) %>%
  summarise(
    n = n(),
    mean = mean(rate), median = median(rate),
    sd = sd(rate), min = min(rate), max = max(rate),
    .groups = "drop"
  ) %>%
  mutate(across(c(mean, median, sd, min, max), ~ round(., 1))) %>%
  kable(caption = "Summary statistics for each 30-day outcome measure (%)") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Summary statistics for each 30-day outcome measure (%)
condition outcome n mean median sd min max
Heart Attack Mortality 2720 15.4 15.4 1.5 10.1 21.9
Heart Attack Readmission 2372 19.7 19.6 1.5 15.1 27.4
Heart Failure Mortality 3947 11.6 11.6 1.5 6.7 18.1
Heart Failure Readmission 4025 24.8 24.6 1.9 19.0 33.6
Pneumonia Mortality 4233 12.1 11.9 1.8 6.8 21.2
Pneumonia Readmission 4247 18.5 18.4 1.6 14.1 25.8
ggplot(rate_long, aes(x = rate, fill = outcome)) +
  geom_density(alpha = 0.6, color = NA) +
  facet_wrap(~condition, ncol = 1, scales = "free_y") +
  scale_fill_manual(values = c(Mortality = "#e34a33", Readmission = "#2c7fb8")) +
  labs(title = "Distribution of 30-day rates, by condition and outcome",
       x = "Rate (%)", y = "Density", fill = NULL) +
  theme_minimal(base_size = 12)

A few patterns stand out:

  • Readmission rates run consistently higher than mortality rates for every condition — patients are far more likely to be readmitted within 30 days than to die, which is expected but worth stating plainly.
  • Heart failure has the highest readmission rate of the three conditions (mean ≈ 24.8%, roughly one in four HF patients readmitted within a month), consistent with heart failure’s well-known pattern of repeat hospitalizations.
  • Heart attack has the highest mortality rate (mean ≈ 15.5%) but the lowest readmission rate (mean ≈ 19.7%) of the three — patients who survive an AMI hospitalization appear comparatively less likely to bounce back within 30 days than heart-failure or pneumonia patients.
  • All three conditions show tight, near-normal distributions across hospitals, which is a direct consequence of CMS’s risk-adjustment methodology: hospitals are being compared on a standardized scale, so extreme outliers are rare by design.

How hospitals compare to the U.S. national rate

comp_long <- hospitals %>%
  select(`Provider Number`, all_of(measures$comp_col)) %>%
  pivot_longer(all_of(measures$comp_col), names_to = "comp_col", values_to = "comparison") %>%
  left_join(measures, by = "comp_col")

comp_summary <- comp_long %>%
  filter(!is.na(comparison)) %>%
  count(condition, outcome, comparison) %>%
  group_by(condition, outcome) %>%
  mutate(share = n / sum(n)) %>%
  ungroup()

comp_summary %>%
  mutate(label = paste(condition, "-", outcome)) %>%
  mutate(comparison = factor(comparison, levels = c(
    "Better than U.S. National Rate", "No Different than U.S. National Rate",
    "Worse than U.S. National Rate", "Number of Cases Too Small", "Not Available"
  ))) %>%
  ggplot(aes(x = fct_rev(label), y = share, fill = comparison)) +
  geom_col(position = "stack") +
  coord_flip() +
  scale_y_continuous(labels = percent, expand = c(0, 0)) +
  scale_fill_manual(values = c(
    "Better than U.S. National Rate" = "#2c7fb8",
    "No Different than U.S. National Rate" = "#c7e9c0",
    "Worse than U.S. National Rate" = "#e34a33",
    "Number of Cases Too Small" = "#bdbdbd",
    "Not Available" = "#f0f0f0"
  )) +
  labs(title = "How each hospital compares to the U.S. national rate, by measure",
       x = NULL, y = "Share of hospitals", fill = NULL) +
  theme_minimal(base_size = 11) +
  theme(legend.position = "bottom")

The overwhelming majority of hospitals are statistically indistinguishable from the national average on every measure — CMS’s methodology is deliberately conservative, flagging a hospital as Better or Worse only when the difference clears a confidence threshold. Because of this:

  • Pneumonia mortality has the most hospitals flagged as Worse than the U.S. rate (212 hospitals, ~4.5%) — more than any other measure — while also having 187 flagged as Better, making it the most differentiated measure in the file.
  • Heart failure mortality shows a similar but smaller split (195 Better vs. 116 Worse), suggesting more hospitals distinguish themselves positively on this measure than negatively.
  • “Number of Cases Too Small” dominates the heart-attack columns (over a third of all hospitals for both AMI measures), reinforcing the completeness finding above — AMI comparisons are simply less available across the full hospital landscape.
  • Readmission measures rarely produce a Better rating — for pneumonia readmission, only 33 hospitals nationwide (0.7%) beat the national rate, versus 123 flagged as worse, suggesting readmission is a harder outcome for hospitals to meaningfully outperform on than mortality.

Geographic pattern: where “worse than U.S. rate” pneumonia mortality clusters

Pneumonia mortality is the best-populated measure in the file, so it’s the most reliable one for a state-level look.

pn_worse_col <- measures %>% filter(key == "pn_mort") %>% pull(comp_col)

worse_by_state <- hospitals %>%
  filter(.data[[pn_worse_col]] == "Worse than U.S. National Rate") %>%
  count(State, sort = TRUE) %>%
  slice_max(n, n = 10)

ggplot(worse_by_state, aes(x = fct_reorder(State, n), y = n)) +
  geom_col(fill = "#e34a33") +
  geom_text(aes(label = n), hjust = -0.3, size = 3.5) +
  coord_flip(clip = "off") +
  scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
  labs(title = "States with the most hospitals rated 'Worse than U.S. rate'\nfor 30-day pneumonia mortality",
       x = NULL, y = "Number of hospitals") +
  theme_minimal(base_size = 12)

California, Louisiana, and Texas have the largest raw counts of hospitals flagged as worse-than-average on pneumonia mortality — though this table is a count of hospitals, not a rate, and larger states naturally contribute more hospitals to any count-based ranking. A fair state-to-state comparison would need to divide by each state’s total number of reporting hospitals, which the appendix table below provides.

hospitals %>%
  mutate(pn_flag = .data[[pn_worse_col]]) %>%
  filter(!is.na(pn_flag)) %>%
  group_by(State) %>%
  summarise(
    n_reporting = n(),
    n_worse = sum(pn_flag == "Worse than U.S. National Rate"),
    share_worse = percent(n_worse / n_reporting, accuracy = 0.1)
  ) %>%
  filter(n_reporting >= 20) %>%
  arrange(desc(n_worse)) %>%
  slice_head(n = 10) %>%
  kable(col.names = c("State", "Hospitals Reporting", "Rated Worse", "Share Rated Worse"),
        caption = "States (≥20 reporting hospitals) with the highest share of pneumonia-mortality hospitals rated worse than the U.S. rate") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
States (≥20 reporting hospitals) with the highest share of pneumonia-mortality hospitals rated worse than the U.S. rate
State Hospitals Reporting Rated Worse Share Rated Worse
CA 341 17 5.0%
LA 114 13 11.4%
TX 370 13 3.5%
GA 132 11 8.3%
KY 96 11 11.5%
NC 112 11 9.8%
IL 179 10 5.6%
TN 116 9 7.8%
PA 175 8 4.6%
VA 87 8 9.2%

Does mortality track readmission?

A natural question is whether hospitals with higher mortality on a given condition also tend to have higher readmission rates for it — i.e., whether one outcome predicts the other.

mort_read_pairs <- list(
  "Heart Attack"  = c("ha_mort", "ha_read"),
  "Heart Failure" = c("hf_mort", "hf_read"),
  "Pneumonia"     = c("pn_mort", "pn_read")
)

pair_data <- purrr::imap_dfr(mort_read_pairs, function(keys, cond) {
  mort_col <- measures %>% filter(key == keys[1]) %>% pull(rate_col)
  read_col <- measures %>% filter(key == keys[2]) %>% pull(rate_col)
  hospitals %>%
    select(mortality = all_of(mort_col), readmission = all_of(read_col)) %>%
    filter(!is.na(mortality), !is.na(readmission)) %>%
    mutate(condition = cond)
})

cor_labels <- pair_data %>%
  group_by(condition) %>%
  summarise(r = cor(mortality, readmission)) %>%
  mutate(label = paste0("r = ", round(r, 2)))

ggplot(pair_data, aes(x = mortality, y = readmission)) +
  geom_point(alpha = 0.15, size = 0.8, color = "#2c7fb8") +
  geom_smooth(method = "lm", color = "#e34a33", se = FALSE, linewidth = 0.8) +
  geom_text(data = cor_labels, aes(x = -Inf, y = Inf, label = label),
            hjust = -0.15, vjust = 1.5, size = 4, inherit.aes = FALSE) +
  facet_wrap(~condition, scales = "free") +
  labs(title = "30-day mortality rate vs. 30-day readmission rate, by hospital",
       x = "Mortality rate (%)", y = "Readmission rate (%)") +
  theme_minimal(base_size = 12)

The correlation between a hospital’s mortality rate and its readmission rate is essentially zero for every condition (r ≈ 0.02–0.03). This is a genuinely useful finding: a hospital that does well (or poorly) on one 30-day outcome tells you almost nothing about how it will do on the other. Mortality and readmission appear to be measuring largely independent aspects of care quality, which is part of why CMS reports and rewards them as separate measures rather than folding them into a single score.


Explore the data

hospitals %>%
  select(`Hospital Name`, City, State, `County Name`,
         `Hospital 30-Day Death (Mortality) Rates from Heart Attack`,
         `Hospital 30-Day Death (Mortality) Rates from Heart Failure`,
         `Hospital 30-Day Death (Mortality) Rates from Pneumonia`,
         `Hospital 30-Day Readmission Rates from Heart Attack`,
         `Hospital 30-Day Readmission Rates from Heart Failure`,
         `Hospital 30-Day Readmission Rates from Pneumonia`) %>%
  rename(
    "AMI Mortality %" = `Hospital 30-Day Death (Mortality) Rates from Heart Attack`,
    "HF Mortality %" = `Hospital 30-Day Death (Mortality) Rates from Heart Failure`,
    "PN Mortality %" = `Hospital 30-Day Death (Mortality) Rates from Pneumonia`,
    "AMI Readmit %" = `Hospital 30-Day Readmission Rates from Heart Attack`,
    "HF Readmit %" = `Hospital 30-Day Readmission Rates from Heart Failure`,
    "PN Readmit %" = `Hospital 30-Day Readmission Rates from Pneumonia`
  ) %>%
  datatable(options = list(pageLength = 10, scrollX = TRUE), rownames = FALSE, filter = "top")

Key takeaways

  • Heart attack outcomes are the least completely reported measure (only ~50–58% of hospitals have a reliable rate), because AMI care concentrates in fewer, higher-volume hospitals; pneumonia is reported for ~90% of hospitals.
  • Readmission consistently exceeds mortality across all three conditions — most notably for heart failure, where nearly 1 in 4 patients is readmitted within 30 days.
  • Heart attack has the highest mortality but the lowest readmission of the three conditions, an asymmetry worth noting for anyone building a composite quality score.
  • Most hospitals are statistically “average” on every measure — CMS’s conservative flagging means only a small minority are labeled Better or Worse than the national rate, and pneumonia mortality is the measure with the most hospitals distinguished in either direction.
  • Mortality and readmission are essentially uncorrelated (r ≈ 0.02–0.03) for every condition — a hospital’s performance on one outcome doesn’t predict its performance on the other.
  • State-level “worse than average” counts are driven partly by hospital volume, not just performance — always normalize by the number of reporting hospitals before comparing states.

Limitations

This file reports process-adjacent outcome measures, not raw clinical data — rates are already risk-adjusted by CMS, and the underlying patient-level records aren’t available here. It also doesn’t include hospital type, ownership, or bed count, so outcome patterns can’t be directly tied to those structural factors without joining against a separate facility file (such as the hospital directory analyzed previously). Suppressed (“Not Available” / “Number of Cases Too Small”) rates should not be treated as zero or excluded silently from claims about overall U.S. performance — they simply reflect insufficient volume for a reliable estimate.


Report generated in R Markdown. To publish: open this file in RStudio with outcome-of-care-measures.csv in the same folder, click Knit, then use the Publish button (top right of the preview pane) to push directly to RPubs.