Metadata-Version: 2.4
Name: abspc
Version: 0.2.1
Summary: SPC chart visuals using the NHS Making Data Count methodology — Python, Looker & Looker Studio
Author: Aneurin Bevan University Health Board
Author-email: Daniel Westwood <daniel.westwood@wales.nhs.uk>
License: MIT License
        
        Copyright (c) 2024-2026 Aneurin Bevan University Health Board
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Documentation, https://github.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals#readme
Project-URL: Issues, https://github.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals/issues
Project-URL: Homepage, https://github.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals
Project-URL: Repository, https://github.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals
Keywords: spc,statistical process control,nhs,making data count,quality improvement,looker,looker studio
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Healthcare Industry
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.23
Requires-Dist: pandas>=1.5
Requires-Dist: matplotlib>=3.6
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Dynamic: license-file
Dynamic: requires-python

# abspc — Python SPC Charts

The `abspc` Python package provides publication-ready Statistical Process
Control (SPC) charts following the NHS
[Making Data Count](https://www.england.nhs.uk/publication/making-data-count/)
methodology.

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

---

## Installation

```bash
pip install abspc
```

Or install the development version from source:

```bash
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

```python
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.

```python
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](https://raw.githubusercontent.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals/main/docs/images/chart_xmr.png)

---

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

```python
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](https://raw.githubusercontent.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals/main/docs/images/chart_p.png)

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

```python
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).

```python
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](https://raw.githubusercontent.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals/main/docs/images/chart_c.png)

---

### 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).

```python
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](https://raw.githubusercontent.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals/main/docs/images/chart_run.png)

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

```python
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**.

```python
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](https://raw.githubusercontent.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals/main/docs/images/chart_with_logo.png)

> **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):**

```python
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](https://raw.githubusercontent.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals/main/docs/images/chart_date_axis.png)

**Option 2 — Explicit date column:**

```python
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:

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

![Change Points](https://raw.githubusercontent.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals/main/docs/images/chart_change_points.png)

---

### Auto-Rebase on Sustained Improvement

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

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

![Auto-Rebase](https://raw.githubusercontent.com/Aneurin-Bevan-University-Health-Board/biu_Making_Data_Count_CustomVisuals/main/docs/images/chart_auto_rebase.png)

Use `rebase_control_limits` for programmatic access without plotting:

```python
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:

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

**Programmatic access:**

```python
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:

```python
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:

```python
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`

```python
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`

```python
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.

```python
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](#mdc-summary-table) section above).

---

## Running Tests

```bash
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.
