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


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. – Darcy Jan 23 '19 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. – 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
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