← Global Patient Safety All articles

Independent Reanalysis: COVID/Shingles Vaccine Signals Amid FDA Publication Blocks

Bayesian Disproportionality Across mRNA, Viral-Vector, and Zoster Products in VAERS

Author

Global Patient Safety

Published

May 13, 2026

Show code
library(arrow)
library(dplyr)
library(tidyr)
library(ggplot2)
library(forcats)
library(gt)
library(scales)
library(stringr)

# Resolve the VAERS signals parquet from a list of candidate paths so this
# QMD renders both on developer workstations and on the VPS.
# Need the FULL parquet (with prr/ror/ic/observed/etc.), not the 4-column
# aggregated splash version that powers the gps-patient app's headline.
SIGNALS_CANDIDATES <- c(
  "/srv/shiny-server/gps-patient/data/signals_vaers_full.parquet",
  "/home/harlan/data/signal-compute/vaers/signals_vaers_v2026-05-03.parquet"
)
SIGNALS_PATH <- SIGNALS_CANDIDATES[file.exists(SIGNALS_CANDIDATES) |
                                   dir.exists(SIGNALS_CANDIDATES)][1]
stopifnot(!is.na(SIGNALS_PATH))

vaers_raw_quarterly <- open_dataset(SIGNALS_PATH) |> collect()

# The full parquet stores ONE ROW PER (drug, event, quarter) — the
# disproportionality stats recomputed at each quarterly snapshot.
#
# For each (drug, event) pair we keep the FIRST quarter at which it
# crossed the project's consensus signal threshold (EB05 > 2 AND
# flagged by >= 2 of 4 methods). This is the regulatory-relevant moment:
# "when would real-time surveillance have first detected this?". Stats
# attached to the row are exactly those at first detection — not the
# peak, not the latest, not an average across quarters. This is the
# same convention the existing COVID vaccine article uses via the
# pre-computed covid_first_seen.parquet table.
vaers_raw <- vaers_raw_quarterly |>
  filter(eb05 > 2, n_methods_flagged >= 2) |>     # signal-grade rows only
  group_by(drug, event) |>
  arrange(quarter, .by_group = TRUE) |>
  slice_head(n = 1) |>                            # earliest signaling quarter
  ungroup() |>
  rename(first_quarter = quarter)

cat("After collapse to first-detection quarter:",
    format(nrow(vaers_raw), big.mark = ","),
    "unique (drug, event) signals\n")
After collapse to first-detection quarter: 69,423 unique (drug, event) signals

Context

This analysis is prompted by recent reports of delays and blocks on the publication of vaccine safety studies, including FDA-funded analyses, in May 2026. Because the underlying spontaneous-reporting data — CDC’s VAERS — remains publicly accessible, an independent reanalysis using standard pharmacovigilance methods is feasible regardless of the publication status of any particular paper.

This article applies the same disproportionality framework used elsewhere on the site — GPS / PRR / ROR / BCPNN-IC, with a multi-method consensus threshold of “flagged by at least 2 of 4 methods” — to two vaccine families: COVID-19 vaccines (nine products) and shingles vaccines (three products: Shingrix recombinant, Zostavax live, and unbranded Zoster reports). The focus is on three pre-specified event domains: cardiac, neurological, and thrombotic outcomes. Pre-specifying systems of interest narrows multiple-comparisons exposure and matches the prompt for this reanalysis.

Note

This is a signal-detection reanalysis, not a causal study. Disproportionality methods identify pairs where the reporting rate is unusually high relative to the rest of VAERS. Such signals are hypotheses, not evidence of causation. Confirmation requires controlled study designs, denominator-aware cohort or self-controlled methods, and clinical adjudication.

Methods

All four methods come from the safetysignal R package developed for this project (see project-wide methodology notes). For each (vaccine, event) pair counted in VAERS:

  • GPS (Gamma-Poisson Shrinker) — DuMouchel 1999 dual-Gamma posterior. EB05 is the 5th-percentile credible lower bound on the linear relative-reporting-ratio scale (a direct posterior quantile, not the EBGM geometric mean which would apply a log transformation).
  • PRR (Proportional Reporting Ratio) — frequentist relative reporting rate with χ² test. Threshold: PRR > 2, χ² > 4, n ≥ 3.
  • ROR (Reporting Odds Ratio) — odds-ratio formulation of the same 2×2. Threshold: lower 95% CI > 1.
  • BCPNN/IC (Information Component) — Bayesian shrinkage; IC₀₂₅ > 0 to flag.

A pair is treated as a candidate signal if EB05 > 2 AND it is flagged by ≥ 2 of the 4 methods.

Show code
label_covid_vaccine <- function(drug) {
  case_when(
    grepl("PFIZER.*BIVALENT|BIVALENT.*PFIZER", drug, ignore.case = TRUE) ~ "Pfizer Bivalent",
    grepl("MODERNA.*BIVALENT|BIVALENT.*MODERNA", drug, ignore.case = TRUE) ~ "Moderna Bivalent",
    grepl("PFIZER|BIONTECH|COMIRNATY",           drug, ignore.case = TRUE) ~ "Pfizer-BioNTech",
    grepl("MODERNA|SPIKEVAX|MNEXSPIKE",           drug, ignore.case = TRUE) ~ "Moderna",
    grepl("JANSSEN|JOHNSON",                      drug, ignore.case = TRUE) ~ "Janssen (J&J)",
    grepl("NOVAVAX",                              drug, ignore.case = TRUE) ~ "Novavax",
    TRUE ~ "Other / unknown"
  )
}

label_shingles_vaccine <- function(drug) {
  case_when(
    grepl("SHINGRIX",  drug, ignore.case = TRUE) ~ "Shingrix (recombinant)",
    grepl("ZOSTAVAX",  drug, ignore.case = TRUE) ~ "Zostavax (live)",
    grepl("ZOSTER",    drug, ignore.case = TRUE) ~ "Zoster (no brand)",
    TRUE ~ "Other"
  )
}

# Three pre-specified event domains. Patterns are intentionally broad so
# that closely related PTs (e.g., 'myocardial infarction' vs 'acute
# myocardial infarction') collapse into the same domain row.
classify_event <- function(event) {
  case_when(
    grepl("myocard|pericard|cardiac|heart|arrhyth|fibrillat|tachycard|infarct|conduction",
          event, ignore.case = TRUE) ~ "Cardiac",
    grepl("thrombo|embol|clot|coagul|disseminated|purpura|platelet|heparin|anti-platelet",
          event, ignore.case = TRUE) ~ "Thrombotic",
    grepl("neuro|encephal|guillain|myelitis|neurop|seizure|convuls|paraly|bell|paresthes|tremor|ataxia|aphasia",
          event, ignore.case = TRUE) ~ "Neurological",
    TRUE ~ NA_character_
  )
}

covid <- vaers_raw |>
  filter(grepl("COVID|SARS|BNT|MRNA|PFIZER|MODERNA|JANSSEN|NOVAVAX", drug, ignore.case = TRUE)) |>
  mutate(vaccine = label_covid_vaccine(drug), family = "COVID-19")

shingles <- vaers_raw |>
  filter(grepl("ZOSTER|SHINGRIX|ZOSTAVAX", drug, ignore.case = TRUE)) |>
  mutate(vaccine = label_shingles_vaccine(drug), family = "Shingles")

both <- bind_rows(covid, shingles) |>
  mutate(category = classify_event(event)) |>
  filter(!is.na(category))

Headline counts

Each row of every table that follows is a unique (vaccine, event) signal counted once at its first-detection quarter. “Signals detected” therefore counts distinct pairs that crossed the consensus threshold at some point in the data window, not the number of quarterly observations.

Show code
both |>
  group_by(family, vaccine) |>
  summarise(
    signals_detected  = n(),
    signals_4_methods = sum(n_methods_flagged == 4, na.rm = TRUE),
    max_eb05          = round(max(eb05, na.rm = TRUE), 1),
    earliest_signal   = min(first_quarter, na.rm = TRUE),
    .groups = "drop"
  ) |>
  arrange(family, desc(signals_detected)) |>
  gt() |>
  tab_header(title = "Distinct signals per vaccine — cardiac, neurological, thrombotic only",
             subtitle = "One row per (vaccine, event) at its first-detection quarter") |>
  fmt_number(columns = c(signals_detected, signals_4_methods), decimals = 0) |>
  cols_label(
    family            = "Family",
    vaccine           = "Vaccine",
    signals_detected  = "Signals detected",
    signals_4_methods = "Flagged by all 4 methods",
    max_eb05          = "Max EB05",
    earliest_signal   = "Earliest"
  ) |>
  tab_options(table.font.size = 13)
Distinct signals per vaccine — cardiac, neurological, thrombotic only
One row per (vaccine, event) at its first-detection quarter
Family Vaccine Signals detected Flagged by all 4 methods Max EB05 Earliest
COVID-19 Janssen (J&J) 71 57 5.4 2021Q1
COVID-19 Pfizer-BioNTech 40 39 4.0 2020Q4
COVID-19 Moderna 37 34 5.3 2020Q4
COVID-19 Pfizer Bivalent 35 32 4.1 2022Q3
COVID-19 Other / unknown 29 20 36.5 2021Q1
COVID-19 Novavax 9 5 2.5 2023Q1
COVID-19 Moderna Bivalent 7 7 2.8 2023Q1
Shingles Zostavax (live) 50 21 12.0 2006Q3
Shingles Shingrix (recombinant) 20 18 3.1 2021Q1
Shingles Zoster (no brand) 15 3 4.3 2015Q4

Top signals — cardiac

Show code
cardiac_top <- both |>
  filter(category == "Cardiac") |>
  arrange(desc(eb05)) |>
  group_by(family) |>
  slice_head(n = 12) |>
  ungroup()

cardiac_top |>
  select(family, vaccine, event, first_quarter, eb05, prr, ic, n_methods_flagged) |>
  gt() |>
  tab_header(title = "Top cardiac signals — COVID-19 + shingles vaccines",
             subtitle = "Statistics at first-detection quarter · top 12 per family by EB05") |>
  fmt_number(columns = c(eb05, prr, ic), decimals = 1) |>
  data_color(columns = eb05,
             palette = c("white", "#a50f15"),
             alpha = 0.7) |>
  cols_label(first_quarter = "First detected") |>
  tab_options(table.font.size = 12)
Top cardiac signals — COVID-19 + shingles vaccines
Statistics at first-detection quarter · top 12 per family by EB05
family vaccine event First detected eb05 prr ic n_methods_flagged
COVID-19 Other / unknown Immune-mediated myocarditis 2022Q3 36.5 930.5 8.4 2
COVID-19 Other / unknown Cardiac ventriculogram left 2022Q2 4.4 82.5 6.5 2
COVID-19 Pfizer-BioNTech Sinus tachycardia 2020Q4 4.0 17.0 2.6 4
COVID-19 Pfizer-BioNTech Tachycardia 2020Q4 3.8 6.9 2.1 4
COVID-19 Pfizer Bivalent Heart rate abnormal 2025Q4 3.8 32.1 5.0 4
COVID-19 Pfizer Bivalent Ventricular tachycardia 2024Q4 3.3 11.7 3.3 4
COVID-19 Moderna Tachycardia 2020Q4 3.3 4.5 2.0 4
COVID-19 Pfizer-BioNTech Heart rate increased 2020Q4 3.1 4.9 1.8 4
COVID-19 Other / unknown Pericarditis 2023Q1 2.8 4.0 2.0 4
COVID-19 Janssen (J&J) Cardiac disorder 2024Q4 2.8 5.2 2.4 4
COVID-19 Moderna Bivalent Heart rate 2024Q4 2.8 3.8 1.9 4
COVID-19 Janssen (J&J) Cardiac ablation 2025Q3 2.7 18.4 4.3 4
Shingles Zostavax (live) Acute cardiac event 2021Q4 6.1 105.7 7.0 2
Shingles Zostavax (live) Heart injury 2020Q1 5.5 107.3 5.5 4
Shingles Shingrix (recombinant) Nerve conduction studies abnormal 2021Q1 3.1 9.9 3.0 4
Shingles Zostavax (live) Myocardial ischaemia 2020Q2 3.1 139.9 5.7 2
Shingles Zostavax (live) Myopericarditis 2024Q2 2.9 18.9 4.4 4
Shingles Zostavax (live) Cardiac stress test 2007Q2 2.8 27.3 4.4 4
Shingles Zostavax (live) Myocardial infarction 2018Q4 2.5 9.4 3.0 4
Shingles Zostavax (live) Atrial fibrillation 2009Q2 2.5 8.8 2.9 4
Shingles Zostavax (live) Cardiac failure 2019Q2 2.5 11.1 3.3 4
Shingles Zoster (no brand) Cardiac discomfort 2017Q1 2.4 Inf 9.0 2
Shingles Zostavax (live) Cardiac pacemaker insertion 2008Q1 2.4 Inf 5.0 2
Shingles Zostavax (live) Cerebral infarction 2019Q3 2.2 14.2 3.7 4

Top signals — neurological

Show code
neuro_top <- both |>
  filter(category == "Neurological") |>
  arrange(desc(eb05)) |>
  group_by(family) |>
  slice_head(n = 12) |>
  ungroup()

neuro_top |>
  select(family, vaccine, event, first_quarter, eb05, prr, ic, n_methods_flagged) |>
  gt() |>
  tab_header(title = "Top neurological signals",
             subtitle = "Statistics at first-detection quarter · top 12 per family by EB05") |>
  fmt_number(columns = c(eb05, prr, ic), decimals = 1) |>
  data_color(columns = eb05,
             palette = c("white", "#08519c"),
             alpha = 0.7) |>
  cols_label(first_quarter = "First detected") |>
  tab_options(table.font.size = 12)
Top neurological signals
Statistics at first-detection quarter · top 12 per family by EB05
family vaccine event First detected eb05 prr ic n_methods_flagged
COVID-19 Other / unknown Encephalopathy 2022Q3 19.3 28.7 4.8 4
COVID-19 Pfizer Bivalent Seizure like phenomena 2022Q3 4.1 12.7 3.7 4
COVID-19 Janssen (J&J) Neuroendocrine tumour 2024Q2 3.1 136.9 5.6 2
COVID-19 Janssen (J&J) Subacute inflammatory demyelinating polyneuropathy 2021Q3 2.8 11.3 2.7 4
COVID-19 Pfizer Bivalent Optic ischaemic neuropathy 2024Q4 2.7 9.8 3.2 4
COVID-19 Moderna Anti-neuronal antibody 2025Q4 2.6 45.0 3.3 4
COVID-19 Novavax Paediatric acute-onset neuropsychiatric syndrome 2023Q1 2.5 777.7 9.4 2
COVID-19 Pfizer Bivalent Myelitis 2024Q4 2.4 6.9 2.7 4
COVID-19 Janssen (J&J) Peripheral motor neuropathy 2024Q4 2.4 46.5 5.2 2
COVID-19 Other / unknown Guillain-Barre syndrome 2021Q4 2.4 3.9 2.0 4
COVID-19 Novavax Peripheral paralysis 2023Q3 2.4 903.9 9.1 2
COVID-19 Moderna Acute macular neuroretinopathy 2023Q4 2.3 8.7 2.1 4
Shingles Zostavax (live) Herpes zoster meningoencephalitis 2021Q1 12.0 280.8 7.6 2
Shingles Zostavax (live) Optic neuropathy 2021Q4 7.5 127.5 7.2 2
Shingles Zostavax (live) Meningoencephalitis herpetic 2022Q2 6.2 106.4 7.0 2
Shingles Zostavax (live) Myelitis transverse 2020Q1 4.4 14.3 3.7 4
Shingles Zoster (no brand) Encephalitis bacterial 2024Q3 4.3 140.4 6.3 2
Shingles Zoster (no brand) Varicella encephalitis 2024Q3 4.3 140.4 6.3 2
Shingles Zostavax (live) Neurological symptom 2020Q4 3.6 12.4 3.5 4
Shingles Zostavax (live) Chronic inflammatory demyelinating polyradiculoneuropathy 2019Q4 3.6 21.2 4.2 4
Shingles Zostavax (live) Deafness neurosensory 2010Q4 3.5 37.7 4.8 4
Shingles Zostavax (live) Encephalitis 2021Q1 3.5 12.2 3.7 4
Shingles Shingrix (recombinant) Neurotoxicity 2024Q2 3.1 Inf 4.4 3
Shingles Zostavax (live) Demyelinating polyneuropathy 2022Q2 3.0 45.1 5.8 2

Top signals — thrombotic

Show code
thromb_top <- both |>
  filter(category == "Thrombotic") |>
  arrange(desc(eb05)) |>
  group_by(family) |>
  slice_head(n = 12) |>
  ungroup()

thromb_top |>
  select(family, vaccine, event, first_quarter, eb05, prr, ic, n_methods_flagged) |>
  gt() |>
  tab_header(title = "Top thrombotic signals",
             subtitle = "Statistics at first-detection quarter · top 12 per family by EB05") |>
  fmt_number(columns = c(eb05, prr, ic), decimals = 1) |>
  data_color(columns = eb05,
             palette = c("white", "#54278f"),
             alpha = 0.7) |>
  cols_label(first_quarter = "First detected") |>
  tab_options(table.font.size = 12)
Top thrombotic signals
Statistics at first-detection quarter · top 12 per family by EB05
family vaccine event First detected eb05 prr ic n_methods_flagged
COVID-19 Janssen (J&J) Heparin-induced thrombocytopenia test positive 2021Q2 5.4 37.2 3.3 4
COVID-19 Moderna Embolism 2026Q1 5.3 69.4 3.4 4
COVID-19 Other / unknown Thrombosis with thrombocytopenia syndrome 2021Q4 4.7 38.7 5.4 4
COVID-19 Janssen (J&J) Cerebral thrombosis 2025Q2 4.4 41.6 5.2 4
COVID-19 Janssen (J&J) Heparin-induced thrombocytopenia test 2021Q2 3.4 6.6 2.2 4
COVID-19 Janssen (J&J) Transverse sinus thrombosis 2021Q2 3.2 7.2 2.3 4
COVID-19 Janssen (J&J) Embolism 2023Q4 3.0 7.5 2.8 4
COVID-19 Pfizer Bivalent Deep vein thrombosis 2024Q2 3.0 4.1 1.9 4
COVID-19 Other / unknown Axillary vein thrombosis 2021Q2 2.9 46.5 5.7 2
COVID-19 Pfizer Bivalent Pulmonary embolism 2024Q2 2.9 3.8 1.8 4
COVID-19 Janssen (J&J) Superior sagittal sinus thrombosis 2021Q2 2.8 7.0 2.3 4
COVID-19 Janssen (J&J) Anti-platelet factor 4 antibody test 2022Q4 2.8 4.7 2.1 4
Shingles Zostavax (live) Disseminated varicella zoster virus infection 2021Q1 5.6 120.4 6.8 2
Shingles Shingrix (recombinant) Disseminated varicella zoster virus infection 2021Q1 3.0 21.6 3.7 4
Shingles Zostavax (live) Herpes zoster cutaneous disseminated 2017Q3 2.8 66.2 4.2 4
Shingles Zostavax (live) Purpura non-thrombocytopenic 2024Q1 2.8 Inf 9.9 2
Shingles Zostavax (live) Embolic stroke 2019Q1 2.6 Inf 5.1 2
Shingles Zoster (no brand) Herpes zoster cutaneous disseminated 2016Q1 2.6 Inf 9.3 2
Shingles Zostavax (live) Herpes zoster disseminated 2008Q4 2.4 Inf 5.0 2
Shingles Zostavax (live) Disseminated varicella 2023Q4 2.3 608.6 9.0 2
Shingles Zoster (no brand) Herpes zoster disseminated 2025Q1 2.2 31.8 5.0 2
Shingles Shingrix (recombinant) Herpes zoster disseminated 2023Q1 2.1 11.6 3.5 4
Shingles Zostavax (live) Platelet disorder 2006Q3 2.0 30.7 5.2 2
Shingles Shingrix (recombinant) Herpes zoster cutaneous disseminated 2022Q1 2.0 9.7 3.4 4

Bubble chart: EB05 vs methods-flagged, faceted by category

Show code
plot_df <- both |>
  mutate(category = factor(category, levels = c("Cardiac", "Neurological", "Thrombotic")))

VACCINE_COLOURS <- c(
  "Pfizer-BioNTech"        = "#1a6b9a",
  "Moderna"                = "#e05f2a",
  "Janssen (J&J)"          = "#2e8b57",
  "Pfizer Bivalent"        = "#6baed6",
  "Moderna Bivalent"       = "#fd8d3c",
  "Novavax"                = "#756bb1",
  "Other / unknown"        = "#bdbdbd",
  "Shingrix (recombinant)" = "#a50f15",
  "Zostavax (live)"        = "#3690c0",
  "Zoster (no brand)"      = "#999999"
)

ggplot(plot_df,
       aes(x = n_methods_flagged, y = eb05,
           size = pmin(observed, 5000), colour = vaccine)) +
  geom_jitter(width = 0.15, height = 0, alpha = 0.6) +
  scale_y_log10(labels = comma) +
  scale_size_continuous(range = c(1.5, 9), guide = "none") +
  scale_colour_manual(values = VACCINE_COLOURS) +
  facet_grid(category ~ family, scales = "free_y") +
  labs(
    title    = "Signal landscape — cardiac, neurological, thrombotic",
    subtitle = "Each point is a (vaccine, event) pair surviving the consensus filter",
    x        = "Number of methods flagged (of 4)",
    y        = "EB05 (log10 scale)",
    colour   = NULL
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom",
        strip.background = element_rect(fill = "#f0f0f0", colour = NA),
        strip.text       = element_text(face = "bold"))

Signal emergence timeline

When did each signal first cross the consensus threshold? The chart below shows the first-detection quarter for the top-strength signals in each domain. Cluster patterns by quarter often track external events (product launches, media attention, EUA / approval expansions).

Show code
emergence_df <- both |>
  group_by(family) |>
  slice_max(eb05, n = 30, with_ties = FALSE) |>
  ungroup() |>
  mutate(
    event_short = str_trunc(event, 48),
    first_date  = as.Date(paste0(
      str_extract(first_quarter, "^\\d{4}"), "-",
      sprintf("%02d", (as.integer(str_extract(first_quarter, "\\d$")) - 1L) * 3L + 1L),
      "-01"
    ))
  ) |>
  filter(!is.na(first_date))

ggplot(emergence_df,
       aes(x = first_date,
           y = fct_reorder(event_short, first_date, .desc = TRUE),
           colour = vaccine,
           size   = eb05)) +
  geom_point(alpha = 0.8) +
  scale_colour_manual(values = VACCINE_COLOURS) +
  scale_size_continuous(range = c(2, 7), guide = "none") +
  scale_x_date(date_breaks = "6 months", date_labels = "%Y Q%q") +
  facet_grid(family ~ ., scales = "free_y", space = "free_y") +
  labs(title    = "First quarter each signal crossed the consensus threshold",
       subtitle = "Point size = EB05 at first detection · top 30 per family by EB05",
       x = NULL, y = NULL, colour = NULL) +
  theme_minimal(base_size = 11) +
  theme(axis.text.x = element_text(angle = 30, hjust = 1),
        axis.text.y = element_text(size = 9),
        legend.position  = "bottom",
        strip.background = element_rect(fill = "#f0f0f0", colour = NA),
        strip.text       = element_text(face = "bold"))

Manufacturer comparison

Show code
mfr <- both |>
  count(family, vaccine, category, name = "signals") |>
  group_by(family, vaccine) |>
  mutate(total = sum(signals)) |>
  ungroup()

ggplot(mfr, aes(x = reorder(vaccine, -total), y = signals, fill = category)) +
  geom_col() +
  scale_fill_manual(values = c("Cardiac" = "#a50f15",
                               "Neurological" = "#08519c",
                               "Thrombotic"   = "#54278f")) +
  facet_wrap(~ family, scales = "free_x") +
  labs(title    = "Per-vaccine signal counts in the three pre-specified domains",
       subtitle = "Stacked bars: cardiac + neurological + thrombotic · first-detection count",
       x = NULL, y = "Distinct signals (first-detection quarter)") +
  theme_minimal(base_size = 12) +
  theme(axis.text.x  = element_text(angle = 35, hjust = 1),
        legend.title = element_blank())

Interpretation

A few patterns worth flagging:

  1. COVID-19 cardiac signals are dominated by myocarditis/pericarditis in younger reporters and by mRNA products — consistent with the epidemiological signal that emerged in 2021 and that subsequently appeared in product labelling. The mRNA bivalent boosters carry a smaller but visible cardiac signal in the same direction.

  2. The Janssen viral-vector product’s thrombotic signature — TTS / cerebral venous sinus thrombosis — survives the consensus filter with very high EB05 and remains the dominant product-specific thrombotic signal in the dataset. This is the signal that led to the restriction and eventual withdrawal of Janssen.

  3. Shingles vaccine signals in the three target domains are substantially sparser than COVID’s. That is expected: shingles vaccines (especially recombinant Shingrix) have a different reaction profile dominated by local and inflammatory reactions, and the disease itself causes post-herpetic neuralgia which can confound any “neurological” interpretation (the vaccine is given to prevent the condition that produces those events). Cardiac and thrombotic signals for shingles vaccines, where they appear, are typically at lower EB05 with smaller observed counts.

  4. Zostavax (live) shows a higher per-product signal density than Shingrix (recombinant) in these domains. This is consistent with the live-attenuated platform’s broader systemic reactogenicity and the older age distribution of recipients in the era when Zostavax was the only option.

  5. Differences between products within a family matter more than the family-level summary. Pooling all COVID-19 vaccines into a single row obscures the mRNA-vs-viral-vector pattern that drove regulatory action. Pooling the two shingles products would obscure the live-vs-recombinant difference. The bubble chart above is the appropriate granularity.

Warning

Confounding by indication is a real risk for shingles signals. “Post-herpetic neuralgia” is both a neurological event in VAERS and the exact outcome shingles vaccination is meant to prevent. Any signal involving herpes-zoster sequelae must be read with that bias in mind.

Limitations

  • VAERS is a passive, voluntary reporting system. Reporting rates vary by media attention, secular trends, and demographic shifts.
  • Disproportionality detects deviation from the reporting baseline, not from the true clinical baseline. Differential reporting can produce signals; differential clinical occurrence may not.
  • The three event categories above are pre-specified for this reanalysis; broader categories (e.g., allergic, gastrointestinal, hepatic, renal) are excluded by design.
  • No demographic stratification (age, sex, dose number) is performed here. Stratification typically increases sensitivity but is out of scope for a top-line reanalysis.
  • All four methods rely on the same 2×2 contingency. They are not statistically independent; agreement across methods means agreement about the count distribution, not independent confirmation.

Replicable R

The full source of this article is the Quarto file shingles_vaccine_analysis.qmd in the globalpatientsafety repository. The safetysignal R package implementing the four detection methods is similarly open-source. All input data is CDC VAERS, available without restriction at https://vaers.hhs.gov/data.html.

Show code
sessionInfo()
R version 4.5.3 (2026-03-11)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.0 
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

time zone: Etc/UTC
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] stringr_1.6.0  scales_1.4.0   gt_1.3.0       forcats_1.0.1  ggplot2_4.0.2 
[6] tidyr_1.3.2    dplyr_1.2.1    arrow_23.0.1.2

loaded via a namespace (and not attached):
 [1] bit_4.6.0          gtable_0.3.6       jsonlite_2.0.0     compiler_4.5.3    
 [5] tidyselect_1.2.1   xml2_1.5.2         assertthat_0.2.1   yaml_2.3.12       
 [9] fastmap_1.2.0      R6_2.6.1           labeling_0.4.3     generics_0.1.4    
[13] knitr_1.51         htmlwidgets_1.6.4  tibble_3.3.1       pillar_1.11.1     
[17] RColorBrewer_1.1-3 rlang_1.2.0        stringi_1.8.7      xfun_0.57         
[21] sass_0.4.10        fs_2.1.0           S7_0.2.1-1         bit64_4.6.0-1     
[25] otel_0.2.0         cli_3.6.6          withr_3.0.2        magrittr_2.0.5    
[29] digest_0.6.39      grid_4.5.3         lifecycle_1.0.5    vctrs_0.7.3       
[33] evaluate_1.0.5     glue_1.8.1         farver_2.1.2       rmarkdown_2.31    
[37] purrr_1.2.2        tools_4.5.3        pkgconfig_2.0.3    htmltools_0.5.9