Bland-Altman plot
original 1986 peak-flow data (two meters)
Example from the compendium of canonical charts
Python Code
"""Bland-Altman plot — original 1986 peak-flow data (two meters)."""
import numpy as np
import plotly.graph_objects as go
import requests, warnings, io
URL = "https://raw.githubusercontent.com/OxfordIHTM/teaching_datasets/main/ba.dat"
def generate():
print("fetching Bland-Altman peak-flow data …")
warnings.filterwarnings("ignore")
requests.packages.urllib3.disable_warnings()
r = requests.get(URL, verify=False, timeout=30)
r.raise_for_status()
lines = r.text.strip().split("\n")
print(f" first 3 lines: {lines[:3]}")
# First line is a header/label; data rows follow
data_lines = [l for l in lines[1:] if l.strip()]
data = []
for line in data_lines:
parts = line.split()
if len(parts) >= 2:
try:
data.append((float(parts[0]), float(parts[1])))
except ValueError:
pass
if len(data) == 0:
# Try reading whole file with pandas
import pandas as pd
df = pd.read_csv(io.StringIO(r.text), sep=r"\s+", header=None, skiprows=1)
data = [(row.iloc[0], row.iloc[1]) for _, row in df.iterrows()
if len(df.columns) >= 2]
print(f" parsed {len(data)} pairs")
m1 = np.array([d[0] for d in data])
m2 = np.array([d[1] for d in data])
means = (m1 + m2) / 2
diffs = m1 - m2
bias = np.mean(diffs)
sd_diff = np.std(diffs, ddof=1)
loa_hi = bias + 1.96 * sd_diff
loa_lo = bias - 1.96 * sd_diff
fig = go.Figure()
# Scatter
fig.add_trace(go.Scatter(
x=means, y=diffs,
mode="markers",
marker=dict(color=VIOLET, size=8, opacity=0.8),
name="Subject",
hovertemplate="Mean=%{x:.0f}<br>Difference=%{y:.0f}<extra></extra>",
))
# Horizontal reference lines
for val, label, color, dash in [
(loa_hi, f"+1.96 SD = {loa_hi:.1f}", PINK, "dash"),
(bias, f"Bias = {bias:.1f}", VIOLET, "solid"),
(loa_lo, f"−1.96 SD = {loa_lo:.1f}", PINK, "dash"),
]:
fig.add_hline(
y=val,
line=dict(color=color, width=1.5, dash=dash),
annotation_text=label,
annotation_position="right",
annotation_font=dict(size=10),
)
fig.update_layout(
title=dict(text="Bland-Altman Plot — Peak Flow (two meters, L/min)", x=0.5),
xaxis=dict(title="Mean of two measurements (L/min)"),
yaxis=dict(title="Difference (meter 1 − meter 2, L/min)"),
margin=dict(t=60, b=50, l=70, r=160),
height=440,
)
return fig
if __name__ == "__main__":
generate()
Made with Plotly