Skip to main content

SPC chart visuals using the NHS Making Data Count methodology — Python, Looker & Looker Studio

Project description

abspc — Python SPC Charts

The abspc Python package provides publication-ready Statistical Process Control (SPC) charts following the NHS Making Data Count methodology.

Built on matplotlib, it produces high-quality static images suitable for board reports, dashboards, and quality-improvement publications.


Installation

pip install abspc

Or install the development version from source:

git clone https://github.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals
cd biu_Making_Data_Count_CustomVisuals
pip install -e ".[dev]"

Quick Start

import pandas as pd
from abspc import plot_spc_chart, plot_run_chart

# Minimal XmR chart
data = pd.DataFrame({"value": [48, 52, 49, 55, 47, 51, 53, 50, 48, 54]})
fig, ax = plot_spc_chart(data, chart_type="XmR")
fig.savefig("my_xmr_chart.png")

Chart Types

XmR Chart

The XmR (individuals / moving-range) chart is the most common SPC chart type. It is suitable for individual measurements collected over time.

import numpy as np
import pandas as pd
from abspc import plot_spc_chart

data = pd.DataFrame({"value": np.random.normal(50, 3, 24)})

fig, ax = plot_spc_chart(
    data,
    chart_type="XmR",
    title="XmR Chart – Individual Measurements",
    xlabel="Month",
    ylabel="Value",
    shade_band=True,
    improvement_direction="high",
)

XmR Chart


p Chart

The p chart is for proportion data (e.g., percentage of patients waiting

4 hours). It requires a subgroup_size column containing the denominator.

data = pd.DataFrame({
    "value": [0.10, 0.12, 0.08, 0.15, 0.09, 0.11, 0.10, 0.13, 0.07, 0.12,
              0.09, 0.11, 0.10, 0.08, 0.14, 0.12, 0.09, 0.11, 0.08, 0.10,
              0.12, 0.09, 0.11, 0.10],
    "subgroup_size": [200] * 24,
})

fig, ax = plot_spc_chart(
    data,
    chart_type="p",
    title="p Chart – Proportion",
    xlabel="Month",
    ylabel="Proportion",
    improvement_direction="low",
)

p Chart

You can also pass a numerator column and a denominator column:

data = pd.DataFrame({
    "events":     [20, 24, 16, 30, 18],
    "population": [200, 200, 200, 200, 200],
})
fig, ax = plot_spc_chart(
    data,
    chart_type="p",
    value_col="population",
    numerator_col="events",
    improvement_direction="low",
)

c Chart

The c chart is for counts of events in a fixed sample size (e.g., adverse events per ward per month).

data = pd.DataFrame({"value": [3, 5, 2, 6, 4, 3, 7, 5, 4, 6,
                                 5, 3, 4, 6, 5, 4, 3, 5, 6, 4,
                                 3, 5, 4, 6]})

fig, ax = plot_spc_chart(
    data,
    chart_type="c",
    title="c Chart – Count of Events",
    xlabel="Month",
    ylabel="Count",
    improvement_direction="low",
)

c Chart


Run Chart

The run chart plots data against time with a median centre line and no control limits. It uses run-chart rules to detect signals (8-point shift and 6-point trend).

from abspc import plot_run_chart

data = pd.DataFrame({"value": np.random.normal(40, 4, 24)})

fig, ax = plot_run_chart(
    data,
    title="Run Chart – Median Centre Line",
    xlabel="Month",
    ylabel="Value",
    improvement_direction="high",
)

Run Chart

plot_spc_chart also accepts chart_type="run" and will automatically delegate to plot_run_chart:

fig, ax = plot_spc_chart(data, chart_type="run")

Features

Logo Placement

Pass any image (PNG, JPEG, etc.) via logo_path to display your organisation's logo at the top-right of the chart, level with the title.

fig, ax = plot_spc_chart(
    data,
    chart_type="XmR",
    title="A&E 4-Hour Waits – Aneurin Bevan UHB",
    logo_path="path/to/logo.png",
    logo_zoom=0.08,
)

Chart with Logo

Note: logo_path places the logo in the title margin (top-right). The legacy nhs_logo_path parameter places an image inside the plot area.


Date Axis

All chart functions automatically detect datetime data on the x-axis and apply smart date tick formatting.

Option 1 — DatetimeIndex (auto-detected):

dates = pd.date_range("2022-01-01", periods=30, freq="MS")
data  = pd.DataFrame({"value": np.random.normal(75, 6, 30)}, index=dates)

fig, ax = plot_spc_chart(data, chart_type="XmR", title="Monthly Date Axis")

Date Axis

Option 2 — Explicit date column:

fig, ax = plot_run_chart(data, x_col="period", date_format="%b %Y")
date_format Example output
"%b %Y" Jan 2024
"%Y-%m" 2024-01
"%d/%m/%Y" 01/01/2024
None (default) Auto (ConciseDateFormatter)

Change-Point Annotations

Mark known process changes with vertical lines and labels:

fig, ax = plot_spc_chart(
    data,
    chart_type="XmR",
    change_points=[
        {"x": 9,  "label": "New protocol"},
        {"x": 20, "label": "Staff training"},
    ],
)

Change Points


Auto-Rebase on Sustained Improvement

When ≥ 8 consecutive points show sustained improvement, control limits can be automatically recalculated for the new phase:

fig, ax = plot_spc_chart(
    data,
    chart_type="XmR",
    improvement_direction="high",
    auto_rebase=True,
)

Auto-Rebase

Use rebase_control_limits for programmatic access without plotting:

from abspc import rebase_control_limits

result = rebase_control_limits(data, chart_type="XmR", improvement_direction="high")

Auto-rebase is supported for XmR, p, u, and c charts (not run charts).


MDC Variation & Assurance Icons

Set show_icons=True to display the official Making Data Count variation and assurance icons at the top-left of the chart:

fig, ax = plot_spc_chart(
    data,
    chart_type="XmR",
    improvement_direction="high",
    target=60,
    show_target=True,
    show_icons=True,
)

Programmatic access:

from abspc import (
    calculate_control_limits,
    detect_special_causes,
    determine_variation_type,
    determine_assurance_type,
)

result = detect_special_causes(calculate_control_limits(data, chart_type="XmR"))
variation = determine_variation_type(result, value_col="value", improvement_direction="high")
assurance = determine_assurance_type(result, target=60, improvement_direction="high")

For run charts only the variation icon is shown (no control limits means assurance cannot be calculated).


MDC Summary Table

plot_mdc_summary_table renders an NHS MDC-style summary table showing multiple measures at a glance:

from abspc import plot_mdc_summary_table

fig, ax = plot_mdc_summary_table(
    [
        {
            "data": df,
            "chart_type": "XmR",
            "measure": "A&E 4-Hour Waits",
            "description": "% patients seen within 4 hours",
            "value_col": "value",
            "improvement_direction": "high",
            "target": 95,
        },
        {
            "data": df_infections,
            "chart_type": "p",
            "measure": "Infection Rate",
            "description": "Proportion of infections per month",
            "value_col": "value",
            "improvement_direction": "low",
            "target": 0.05,
            "subgroup_col": "subgroup_size",
        },
    ],
    title="MDC Summary — Board Report",
)

Special-Cause Rules

Four NHS MDC rules (aligned with NHSRplotthedots):

Rule Name Description
1 Astronomical point Single value outside 3σ limits
2 Shift ≥ 8 consecutive points above or below the mean
3 Trend ≥ 6 consecutive points all rising or all falling
4 Two-in-three 2 of 3 consecutive points in the warning zone

Use the detection functions directly:

from abspc import calculate_control_limits, detect_special_causes

result = calculate_control_limits(data, chart_type="XmR")
flags  = detect_special_causes(result)
print(flags[["value", "mean", "ucl", "lcl", "rule1", "rule2", "rule3", "rule4", "special_cause"]])

API Reference

plot_spc_chart

fig, ax = plot_spc_chart(
    data,
    chart_type,                     # "XmR" | "p" | "u" | "c" | "run"
    value_col="value",
    subgroup_col="subgroup_size",
    numerator_col=None,
    x_col=None,
    title=None,
    xlabel="Observation",
    ylabel="Value",
    improvement_direction="high",
    target=None,
    show_target=False,
    shade_band=False,
    shade_color="#41B6E6",
    nhs_logo_path=None,
    ax=None,
    figsize=(12, 5),
    show_legend=True,
    change_points=None,
    auto_rebase=False,
    date_format=None,
    logo_path=None,
    logo_zoom=0.07,
    show_icons=False,
    icon_zoom=0.06,
)
Parameter Type Default Description
data pd.DataFrame (required) Input DataFrame with at least the value_col column.
chart_type str (required) "XmR", "p", "u", "c", or "run" (case-insensitive).
value_col str "value" Column containing the measured values.
subgroup_col str | None "subgroup_size" Column with subgroup sizes. Required for "p" and "u".
numerator_col str | None None For "p" charts: column with event counts when value_col holds the denominator.
x_col str | None None Column for the x-axis. Auto-detects DatetimeIndex if None.
title str | None None Chart title. Auto-generated if omitted.
xlabel str "Observation" X-axis label.
ylabel str "Value" Y-axis label.
improvement_direction str "high" "high" or "low". Controls point colouring.
target float | None None Optional target value for the target line and assurance calculation.
show_target bool False Draw a dashed target line at target.
shade_band bool False Fill between UCL and LCL with a translucent band.
shade_color str "#41B6E6" Colour for tolerance-band shading.
nhs_logo_path str | None None Logo inside the axes (legacy). Use logo_path instead.
ax Axes | None None Existing axes to draw on. Creates a new figure when None.
figsize tuple (12, 5) Figure size in inches. Ignored when ax is provided.
show_legend bool True Add a colour legend.
change_points list[dict] | None None Vertical annotation lines. Each dict needs "x" and "label".
auto_rebase bool False Auto-detect sustained improvement and recalculate limits.
date_format str | None None strftime-style format for datetime x-axis.
logo_path str | None None Logo image at top-right of figure.
logo_zoom float 0.07 Logo height as fraction of figure height.
show_icons bool False Display MDC variation & assurance icons.
icon_zoom float 0.06 Icon height as fraction of figure height.

Returns: (fig, ax)matplotlib.figure.Figure and matplotlib.axes.Axes.


plot_run_chart

fig, ax = plot_run_chart(
    data,
    value_col="value",
    x_col=None,
    title=None,
    xlabel="Observation",
    ylabel="Value",
    improvement_direction="high",
    target=None,
    show_target=False,
    nhs_logo_path=None,
    ax=None,
    figsize=(12, 5),
    show_legend=True,
    change_points=None,
    date_format=None,
    logo_path=None,
    logo_zoom=0.07,
    show_icons=False,
    icon_zoom=0.06,
)

Same parameter semantics as plot_spc_chart (without chart_type, subgroup_col, numerator_col, shade_band, shade_color, auto_rebase).

Returns: (fig, ax).


calculate_control_limits

Returns the input DataFrame extended with mean, ucl, lcl, uwl, lwl columns (or just mean for run charts).

detect_special_causes

Returns the DataFrame extended with boolean columns rule1, rule2, rule3, rule4, and special_cause.

detect_run_chart_signals

Returns the DataFrame extended with run_shift, run_trend, and run_signal.

rebase_control_limits

Returns the DataFrame with limits recalculated per improvement phase and a rebase_phase integer column.

show_summary

Generates a programmatic summary dictionary for a chart, including variation type, assurance status, descriptive statistics, triggered SPC rules, and a list of signal points. Pass show_summary=True to plot_spc_chart or plot_run_chart to render the summary as an additional figure.

from abspc import show_summary

summary = show_summary(data, chart_type="XmR", improvement_direction="high", target=60)
print(summary["variation"], summary["assurance"])

plot_mdc_summary_table

Renders an NHS MDC-style summary table for one or more measures (see the MDC Summary Table section above).


Running Tests

pip install -e ".[dev]"
pytest

160 unit tests covering all chart types, SPC rules, run-chart signals, auto-rebase, change-point annotations, summary generation, and plotting.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

abspc-0.2.1.tar.gz (277.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

abspc-0.2.1-py3-none-any.whl (275.7 kB view details)

Uploaded Python 3

File details

Details for the file abspc-0.2.1.tar.gz.

File metadata

  • Download URL: abspc-0.2.1.tar.gz
  • Upload date:
  • Size: 277.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for abspc-0.2.1.tar.gz
Algorithm Hash digest
SHA256 e11de1e7719f19dbdbc2496fd847d6bceb2049be461a55428cd11e3e727b366f
MD5 a0d33df4614b269ebfc6f9e51f2e3a09
BLAKE2b-256 aaed3b41eac5fae69449b868504d2b199a4a7765c8c4d59bb181f0fa5509f3c2

See more details on using hashes here.

Provenance

The following attestation bundles were made for abspc-0.2.1.tar.gz:

Publisher: publish.yml on Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file abspc-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: abspc-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 275.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for abspc-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 20283825d99db0a430c996b9732d63d159ddf01eca3d9075dbecc3f44aeb1d77
MD5 63a305a57181fa1010e764ae4558328f
BLAKE2b-256 d7f226a21d1c4f485b3e3db87c5ffc6e7ba5d7c756b966ffd0eed816aef5cd72

See more details on using hashes here.

Provenance

The following attestation bundles were made for abspc-0.2.1-py3-none-any.whl:

Publisher: publish.yml on Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page