Chris Parmer — home

Bland-Altman plot

original 1986 peak-flow data (two meters)

Example from the compendium of canonical charts

Bland-Altman plot — original 1986 peak-flow data (two meters)

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