Yesterday I heard of the Bland-Altman plot for the first time. I have to compare two methods of measuring blood pressure, and I need to produce a Bland-Altman plot. I am not sure if I get everything about it right, so here's what I think I know:

I have two sets of data. I calculate their mean (x value) and their difference (y value) and plot it around the axis y = mean of difference. Then, I calculate the standard deviation of the difference, and plot it as "limits of agreement". This is what I do not understand - limits of what agreement? What means 95% agreement in layman's terms? Is that supposed to tell me (provided that all points of the scatter graph are between the "limits of agreement") that the methods have 95% match?

  • 4
    $\begingroup$ A not for later readers -- statisticians might be more familiar with this kind of plot being called a Tukey mean-difference plot (equivalently, a Tukey-sum-difference plot -- the plots are identical, aside from the scale on one axis), since this was well known in statistics long before Bland and Altman popularized it in the medical literature. $\endgroup$ – Glen_b May 24 '14 at 8:08
  • 1
    $\begingroup$ Somewhat relevant: "How does one interpret a Bland-Altman plot?" $\endgroup$ – Glen_b May 24 '14 at 8:38

Have you looked at the Wikipedia entry I linked in your question?

You don't plot "the mean of the data", but for each data point measured in two ways, you plot the difference in the two measurements ($y$) against the average of the two measurements ($x$). Using R and some toy data:

> set.seed(1)
> measurements <- matrix(rnorm(20), ncol=2)
> measurements
            [,1]        [,2]
 [1,] -0.6264538  1.51178117
 [2,]  0.1836433  0.38984324
 [3,] -0.8356286 -0.62124058
 [4,]  1.5952808 -2.21469989
 [5,]  0.3295078  1.12493092
 [6,] -0.8204684 -0.04493361
 [7,]  0.4874291 -0.01619026
 [8,]  0.7383247  0.94383621
 [9,]  0.5757814  0.82122120
[10,] -0.3053884  0.59390132
> xx <- rowMeans(measurements)        # x coordinate: row-wise average
> yy <- apply(measurements, 1, diff)  # y coordinate: row-wise difference
> xx
 [1]  0.4426637  0.2867433 -0.7284346 -0.3097095  0.7272193 -0.4327010  0.2356194  
      0.8410805  0.6985013  0.1442565
> yy
 [1]  2.1382350  0.2061999  0.2143880 -3.8099807  0.7954231  0.7755348 -0.5036193  
      0.2055115  0.2454398  0.8992897
> plot(xx, yy, pch=19, xlab="Average", ylab="Difference")

Bland-Altman plot without limits of agreement

To get the limits of agreement (see under "Application" in the Wikipedia page), you calculate the mean and the standard deviation of the differences, i.e., the $y$ values, and plot horizontal lines at the mean $\pm 1.96$ standard deviations.

> upper <- mean(yy) + 1.96*sd(yy)
> lower <- mean(yy) - 1.96*sd(yy)
> upper
[1] 3.141753
> lower
[1] -2.908468
> abline(h=c(upper,lower), lty=2)

Bland-Altman plot with limits of agreement

(You can't see the upper limit of agreement because the plot only goes up to $y\approx 2.1$.)

As to the interpretation of the plot and the limits of agreement, again look to Wikipedia:

If the differences within mean ± 1.96 SD are not clinically important, the two methods may be used interchangeably.

| cite | improve this answer | |
  • $\begingroup$ So why does the x-axis matter on the plot? Everything I read about the Bland-Altman only interprets and performs analysis on the y-values (e.g. SD of y-values, mean of y-values, etc.). I don't understand what value the x-axis adds to the analysis. $\endgroup$ – Darcy Jan 23 '19 at 21:19
  • $\begingroup$ @Darcy: The x axis shows the average of the two measurements, and the y axis shows the difference between them. If, for instance, your dot lie roughly on a line with a positive slope, that indicates that the difference (the y axis) increases with an increase in the measurement (the x axis), which may well be an interesting piece of information - it may show value ranges where the two measurements agree and where they don't. $\endgroup$ – Stephan Kolassa Jan 23 '19 at 21:29

If you would like to do this in Python you can use this code

import matplotlib.pyplot as plt
import numpy as np
from numpy.random import random
%matplotlib inline

I just added the last line because I like the ggplot style.

def plotblandaltman(x,y,title,sd_limit):
    plt.suptitle(title, fontsize="20")
    if len(x) != len(y):
        raise ValueError('x does not have the same length as y')
        for i in range(len(x)):
            a = np.asarray(x)

            b = np.asarray(x)+np.asarray(y)
            mean_diff = np.mean(b)
            std_diff = np.std(b, axis=0)
            limit_of_agreement = sd_limit * std_diff
            lower = mean_diff - limit_of_agreement
            upper = mean_diff + limit_of_agreement

            difference = upper - lower
            lowerplot = lower - (difference * 0.5)
            upperplot = upper + (difference * 0.5)
            plt.axhline(y=mean_diff, linestyle = "--", color = "red", label="mean diff")

            plt.axhline(y=lower, linestyle = "--", color = "grey", label="-1.96 SD")
            plt.axhline(y=upper, linestyle = "--", color = "grey", label="1.96 SD")

            plt.text(a.max()*0.85, upper * 1.1, " 1.96 SD", color = "grey", fontsize = "14")
            plt.text(a.max()*0.85, lower * 0.9, "-1.96 SD", color = "grey", fontsize = "14")
            plt.text(a.max()*0.85, mean_diff * 0.85, "Mean", color = "red", fontsize = "14")
            plt.ylim(lowerplot, upperplot)

And finaly I just make some random values and compare them in this function

x = np.random.rand(100)
y = np.random.rand(100)
plotblandaltman(x,y,"Bland-altman plot",1.96)

Bland-altman plot

With some minor modification, you can easily add a for-loop and make several plots

| cite | improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.