Skip to contents

Recipe

Level Intermediate  ·  Output Self-contained HTML report  ·  Requires gt, ggplot2, patchwork (optional), quarto (for template rendering)

Goal

Generate a self-contained HTML QC report that includes:

  • Summary statistics (feature counts, sample counts, batches)
  • CV distribution plots
  • Run-order scatter plots for key features
  • Calibration curve summaries (if applicable)
  • Feature pass/fail table
  • Flagged samples

Prerequisites

library(mrmhub)
library(dplyr)
library(ggplot2)
library(gt)

# A fully processed MRMhubExperiment
mexp <- readRDS("results/mexp_processed.rds")

Part 1: Summary Table

summary_tbl <- tibble::tibble(
  Metric = c("Total analyses", "Study samples (SPL)", "QC samples",
             "Blanks", "Features (total)", "Features (passed QC)",
             "Batches", "Drift corrected?", "Batch corrected?"),
  Value = c(
    get_analysis_count(mexp),
    mexp@annot_analyses |> filter(qc_type == "SPL") |> nrow(),
    mexp@annot_analyses |> filter(grepl("QC", qc_type)) |> nrow(),
    mexp@annot_analyses |> filter(grepl("BLK", qc_type)) |> nrow(),
    nrow(mexp@annot_features),
    get_feature_count(mexp),
    length(unique(mexp@annot_analyses$batch_id)),
    ifelse(length(mexp@var_drift_corrected) > 0, "Yes", "No"),
    ifelse(length(mexp@var_batch_corrected) > 0, "Yes", "No")
  )
)

summary_tbl |> gt::gt() |> gt::tab_header(title = "Study Summary")

Part 2: CV Distribution

cv_data <- mexp@metrics_qc |>
  select(feature_id, cv = norm_intensity_cv_bqc) |>
  filter(!is.na(cv))

ggplot(cv_data, aes(x = cv)) +
  geom_histogram(binwidth = 5, fill = "#5B8FA8", colour = "white") +
  geom_vline(xintercept = 30, linetype = "dashed", colour = "red") +
  labs(
    title = "QC CV Distribution",
    subtitle = paste0(sum(cv_data$cv <= 30), "/", nrow(cv_data),
                      " features with CV \u2264 30%"),
    x = "CV (%)", y = "Count"
  ) +
  theme_minimal()

Part 3: Run-Order Scatter (Worst Features)

# Plot run scatter for the 4 features with highest CV
top_cv_features <- cv_data |>
  arrange(desc(cv)) |>
  head(4) |>
  pull(feature_id)

plots <- lapply(top_cv_features, function(feat) {
  plot_runscatter(mexp,
                  variable = "norm_intensity",
                  include_feature_filter = feat,
                  qc_types = c("BQC", "SPL")) +
    ggtitle(feat)
})

if (requireNamespace("patchwork", quietly = TRUE)) {
  patchwork::wrap_plots(plots, ncol = 2)
}

Part 4: Feature Pass/Fail Table

qc_table <- mexp@metrics_qc |>
  select(feature_id, norm_intensity_cv_bqc, normint_dratio_sd_bqc) |>
  mutate(
    cv_pass = norm_intensity_cv_bqc <= 30,
    dratio_pass = normint_dratio_sd_bqc <= 0.5,
    status = case_when(
      cv_pass & dratio_pass ~ "PASS",
      !cv_pass & !dratio_pass ~ "FAIL (CV + D-ratio)",
      !cv_pass ~ "FAIL (CV)",
      !dratio_pass ~ "FAIL (D-ratio)"
    )
  ) |>
  arrange(desc(norm_intensity_cv_bqc))

qc_table |>
  gt::gt() |>
  gt::fmt_number(columns = c(norm_intensity_cv_bqc, normint_dratio_sd_bqc), decimals = 1) |>
  gt::data_color(
    columns = status,
    fn = function(x) ifelse(x == "PASS", "#d4e8d4", "#f8d4d4")
  ) |>
  gt::tab_header(title = "Feature QC Summary")

Part 5: Calibration Summary (if applicable)

if (nrow(mexp@metrics_calibration) > 0) {
  cal_summary <- get_calibration_metrics(mexp) |>
    select(feature_id, r2, fit_model, fit_weighting, lowest_cal, highest_cal) |>
    arrange(r2)

  cal_summary |>
    gt::gt() |>
    gt::fmt_number(columns = c(r2, lowest_cal, highest_cal), decimals = 4) |>
    gt::tab_header(title = "Calibration Curve Metrics")
}

Part 6: Flagged Samples

if (length(mexp@analyses_excluded) > 0) {
  tibble::tibble(
    analysis_id = mexp@analyses_excluded,
    reason = "Excluded during processing"
  ) |> gt::gt() |> gt::tab_header(title = "Excluded Analyses")
}

Parameterized Quarto Template

Full template (click to expand)

Save as qc-report-template.qmd:

---
title: "QC Report"
date: today
format:
  html:
    self-contained: true
    toc: true
params:
  rds_path: "results/mexp_processed.rds"
---

Then include each Part above as a code chunk in the template.

Render the template:

quarto::quarto_render(
  "qc-report-template.qmd",
  execute_params = list(rds_path = "results/mexp_processed.rds")
)

Tips

  • Self-contained HTML — a single file that can be emailed or archived.
  • Parameterize the RDS path so the same template works for any study.
  • Date stamp (date: today) for an audit trail.
  • Session info at the end for reproducibility.

Next steps