# Creating and interpreting Bland-Altman plot

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?

• 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. May 24, 2014 at 8:08
• Somewhat relevant: "How does one interpret a Bland-Altman plot?" May 24, 2014 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")


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)


(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.

• 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. Jan 23, 2019 at 21:19
• @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. Jan 23, 2019 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
plt.style.use('ggplot')


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

def plotblandaltman(x,y,title,sd_limit):
plt.figure(figsize=(20,8))
plt.suptitle(title, fontsize="20")
if len(x) != len(y):
raise ValueError('x does not have the same length as y')
else:
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)
plt.scatter(x=a,y=b)


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)


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