Bland-Altman Plot

Authors

[Editor] Shixiang Wang;

[Contributors] ZCode (GLM) โ€” AI-generated, independently AI-reviewed.

Modified

2026-10-31

๐Ÿค– AI Skill โ€” Download the Bizard unified skill for your AI assistant โฌ‡๏ธ Download Skill ZIP
๐Ÿ”Œ MCP Server โ€” Let AI agents (Claude Code, Cursor, โ€ฆ) query Bizard live: claude mcp add bizard -- npx @openbiox/bizard-mcp โ€” or the free hosted instance (no install, faster from mainland China): claude mcp add --transport http bizard http://biotree.top:38123/bizard-mcp/mcp ๐Ÿ“– MCP Docs
๐Ÿ“– Cite Bizard โ€” if Bizard helps your research, please cite: Li, K., Zheng, H., Huang, K., et al. (2026). Bizard: A Community-Driven Platform for Accelerating and Enhancing Biomedical Data Visualization. iMetaMed, e70038. ๐Ÿ”— DOI / BibTeX

The Bland-Altman plot is the standard graphical tool for assessing agreement between two quantitative measurement methods โ€” for example, a new point-of-care assay versus a reference laboratory method, or a wearable device versus clinic blood-pressure measurement [1]. Each subject is measured by both methods; the plot shows the difference between the two readings against their average. Three horizontal summaries anchor the interpretation: the mean difference (bias, the systematic offset between methods) and the 95% limits of agreement (bias ยฑ 1.96 ร— SD of the differences, the range within which 95% of the disagreements between the two methods are expected to fall [2].

The key insight the plot teaches is that a large Pearson correlation coefficient does not imply agreement: correlation measures the strength of a linear association, which any consistently inflated method will still show. Agreement is judged on the magnitude of the differences relative to clinical relevance, not on r.

This tutorial covers four practical workflows in R:

  1. A paired scatter plot that shows why correlation is not agreement;
  2. A hand-built Bland-Altman plot with bias, 95% limits of agreement and their confidence intervals (ggplot2);
  3. The same analysis with the blandr package [3];
  4. A percentage difference plot that stabilizes the variance when measurement error grows with the magnitude of the measurement (proportional bias).

Example

BlandAltmanPlot DEMO

Setup

  • System Requirements: Cross-platform (Linux/MacOS/Windows)

  • Programming language: R

  • Dependent packages: blandr; ggplot2; dplyr

# Installing necessary packages
if (!requireNamespace("blandr", quietly = TRUE)) {
  install.packages("blandr")
}

# Load packages
library(ggplot2)
library(dplyr)
library(blandr)
sessioninfo::session_info("attached")
โ”€ Session info โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 setting  value
 version  R version 4.6.2 (2026-10-29)
 os       Ubuntu 24.04.5 LTS
 system   x86_64, linux-gnu
 ui       X11
 language (EN)
 collate  C.UTF-8
 ctype    C.UTF-8
 tz       UTC
 date     2026-10-30
 pandoc   3.1.3 @ /usr/bin/ (via rmarkdown)
 quarto   1.10.19 @ /usr/local/bin/quarto

โ”€ Packages โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 ! package * version    date (UTC) lib source
   blandr  * 0.6.0      2024-06-09 [1] RSPM
 P dplyr   * 1.2.1      2026-04-03 [?] RSPM
 P ggplot2 * 4.0.3.9000 2026-10-30 [?] Github (tidyverse/ggplot2@b638d65)

 [1] /home/runner/.cache/R/renv/library/Bizard-5140bdc6/linux-ubuntu-noble/R-4.6/x86_64-pc-linux-gnu
 [2] /home/runner/work/_temp/Library
 [3] /opt/R/4.6.2/lib/R/library

 * โ”€โ”€ Packages attached to the search path.
 P โ”€โ”€ Loaded and on-disk path mismatch.

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

Data Preparation

We simulate a method-comparison study of \(n = 120\) samples assayed for a serum biomarker (mg/L) by a reference laboratory method (ref) and a new point-of-care method (new). The simulation embeds two features commonly seen in real method comparisons: a modest proportional overestimation by the new method, and measurement error whose spread grows with the biomarker concentration (heteroscedasticity). Both features will surface graphically in the Bland-Altman plot below.

set.seed(2026)

n <- 120
# Right-skewed biomarker concentrations, as tumor markers typically are
true_conc <- rgamma(n, shape = 2, scale = 20)

method_data <- data.frame(
  ref = true_conc * (1 + rnorm(n, 0, 0.05)),      # reference method, ~5% CV
  new = true_conc * (1 + rnorm(n, 0.06, 0.09))    # new method: +6% proportional bias, ~9% CV
) |>
  mutate(
    avg  = (ref + new) / 2,   # best available estimate of the true value
    diff = ref - new          # reference minus new
  )

head(method_data)
        ref       new       avg       diff
1 45.288522 49.246859 47.267690 -3.9583372
2  9.888475  8.631028  9.259751  1.2574460
3  1.269740  1.134599  1.202170  0.1351402
4 15.974590 16.826417 16.400504 -0.8518274
5 44.133269 47.347588 45.740429 -3.2143182
6 60.887603 66.438457 63.663030 -5.5508542

Visualization

1. Why Correlation Is Not Agreement

r <- cor(method_data$ref, method_data$new)

ggplot(method_data, aes(x = ref, y = new)) +
  geom_point(color = "#2166AC", alpha = 0.7, size = 2) +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = "grey40") +
  annotate("text", x = max(method_data$ref) * 0.72, y = min(method_data$new) * 1.6,
           label = sprintf("Pearson r = %.2f", r), size = 4.2) +
  labs(x = "Reference method (mg/L)", y = "New method (mg/L)",
       title = "High correlation does not imply agreement")
Figureย 1: Paired measurements by the two methods; the Pearson correlation is high although the new method reads systematically high.

The two methods correlate almost perfectly (r โ‰ˆ 0.99), yet the points drift progressively above the identity line as concentrations rise โ€” the new method systematically over-reads at high values. Correlation is blind to exactly this behavior, which is why agreement needs its own plot.

2. Hand-Built Bland-Altman Plot with Confidence Intervals

bias    <- mean(method_data$diff)
sd_diff <- sd(method_data$diff)
loa     <- c(bias - 1.96 * sd_diff, bias + 1.96 * sd_diff)
n       <- nrow(method_data)

# Exact standard errors (Bland & Altman 1986): the limits are estimated
# quantities and carry their own uncertainty, especially in small samples.
se_bias <- sd_diff / sqrt(n)
se_loa  <- sqrt(sd_diff^2 * (1 / n + 1.96^2 / (2 * (n - 1))))

ggplot(method_data, aes(x = avg, y = diff)) +
  geom_point(color = "#2166AC", alpha = 0.7, size = 2) +
  geom_ribbon(aes(ymin = bias - 1.96 * se_bias, ymax = bias + 1.96 * se_bias),
              alpha = 0.2, color = NA) +
  geom_hline(yintercept = bias, color = "black") +
  geom_hline(yintercept = loa, color = "#B2182B", linetype = "dashed") +
  annotate("text", x = max(method_data$avg), y = bias, vjust = -1, hjust = 1,
           label = sprintf("Bias = %.2f", bias), size = 3.6) +
  annotate("text", x = max(method_data$avg), y = loa[2], vjust = -0.8, hjust = 1,
           label = sprintf("+1.96 SD = %.2f", loa[2]), size = 3.6, color = "#B2182B") +
  annotate("text", x = max(method_data$avg), y = loa[1], vjust = 1.7, hjust = 1,
           label = sprintf("-1.96 SD = %.2f", loa[1]), size = 3.6, color = "#B2182B") +
  labs(x = "Mean of both methods (mg/L)", y = "Difference (Reference โˆ’ New)",
       title = "Bland-Altman plot with 95% limits of agreement")
Figureย 2: Bland-Altman plot of the same data: mean difference (bias) with its 95% CI (grey band) and the 95% limits of agreement (dashed).

The average difference of about โˆ’3 mg/L is the bias: the new method reads higher than the reference on average, so the difference (Reference โˆ’ New) is negative. The dashed lines are the 95% limits of agreement โ€” the interval within which 95% of the differences between the two methods are expected to lie. Whether that interval is acceptable is a clinical judgement, not a statistical one: an agreement limit of ยฑ10 mg/L is irrelevant for a biomarker whose decision threshold is 50 mg/L but unacceptable if the threshold is 5 mg/L.

Note the funnel shape: the scatter of differences widens at high concentrations, a warning sign addressed in Section 4.

3. The Same Analysis with blandr

blandr.draw(method_data$ref, method_data$new,
            plotTitle = "blandr package output")
Figureย 3: blandr draws the plot and shades the confidence regions of the bias and both limits.

blandr.draw() reproduces the hand-built analysis: same bias, same limits of agreement, and โ€” like the grey band above โ€” confidence intervals around each estimated line (shaded regions). The package also provides numeric summaries via blandr.output.text() for reporting.

4. Percentage Differences for Proportional Bias

When the scatter of differences grows with the magnitude of the measurement (the funnel in Section 2), expressing the difference as a percentage of the average โ€” or log-transforming both measurements before the analysis [2] โ€” restores a constant spread, and the limits of agreement become interpretable across the whole measurement range.

method_data <- method_data |>
  mutate(pct_diff = diff / avg * 100)

pct_bias <- mean(method_data$pct_diff)
pct_loa  <- c(pct_bias - 1.96 * sd(method_data$pct_diff),
              pct_bias + 1.96 * sd(method_data$pct_diff))

ggplot(method_data, aes(x = avg, y = pct_diff)) +
  geom_point(color = "#2166AC", alpha = 0.7, size = 2) +
  geom_hline(yintercept = pct_bias, color = "black") +
  geom_hline(yintercept = pct_loa, color = "#B2182B", linetype = "dashed") +
  annotate("text", x = max(method_data$avg), y = pct_bias, vjust = -1, hjust = 1,
           label = sprintf("Bias = %.1f%%", pct_bias), size = 3.6) +
  annotate("text", x = max(method_data$avg), y = pct_loa[2], vjust = -0.8, hjust = 1,
           label = sprintf("+1.96 SD = %.1f%%", pct_loa[2]), size = 3.6, color = "#B2182B") +
  annotate("text", x = max(method_data$avg), y = pct_loa[1], vjust = 1.7, hjust = 1,
           label = sprintf("-1.96 SD = %.1f%%", pct_loa[1]), size = 3.6, color = "#B2182B") +
  labs(x = "Mean of both methods (mg/L)", y = "Difference (% of mean)",
       title = "Percentage difference plot")
Figureย 4: Percentage difference plot: the funnel disappears once differences are scaled by the mean, giving one bias (โˆ’6.4%) and one pair of limits valid across the range.

On the percentage scale the bias is โˆ’6.4% with limits of about โˆ’25% to +13%: a compact statement of method agreement that holds at low and high concentrations alike.

TipPractice tips
  • The x-axis should be the average of the two methods (the best estimate of the true value), never one method alone โ€” plotting against a single method induces spurious correlation in the differences.
  • Report the confidence intervals of the bias and of both limits; point estimates alone overstate the precision of small agreement studies.
  • Check the funnel shape before quoting raw-unit limits. If spread grows with magnitude, switch to percentage differences or log-transform.
  • Do not substitute the paired t-test for the plot: a non-significant t-test does not establish agreement, and a significant one does not necessarily preclude it.

Application

Method-comparison plots are required when laboratories introduce a new assay or device: clinical chemistry (new analyzer vs incumbent), physiology (wearable vs catheter blood pressure), and imaging-derived quantities (AI segmentation vs manual contour). The panel below mirrors a typical publication layout: (A) the paired scatter that motivates the analysis, (B) the Bland-Altman plot with bias and limits, (C) the package output with confidence regions, and (D) the percentage plot for proportional bias.

BlandAltmanPlot Application

Reference

[1] Bland JM, Altman DG. Statistical methods for assessing agreement between two methods of clinical measurement. Lancet. 1986;327(8476):307-310. doi:10.1016/S0140-6736(86)90837-8

[2] Bland JM, Altman DG. Measuring agreement in method comparison studies. Stat Methods Med Res. 1999;8(2):135-160. doi:10.1177/096228029900800204

[3] Datta D. blandr: a Bland-Altman Method Comparison package for R. Zenodo. 2017. doi:10.5281/zenodo.824514