Metadata-Version: 2.4
Name: controlchartspy
Version: 0.1.1
Summary: Calculate and visualise SPC, funnel, and multi-indicator sigma charts
Keywords: spc,control-charts,funnel-plots,statistics
Author: Andrew R. Johnson, Healthcare Quality Intelligence Unit (Western Australia Health)
Author-email: Andrew R. Johnson <andrew.johnson@arjohnsonau.com>, Healthcare Quality Intelligence Unit (Western Australia Health) <safetyandquality@health.wa.gov.au>
License-Expression: GPL-3.0-only
License-File: LICENSE
License-File: NOTICE
License-File: licenses/controlcharts-LICENSE.txt
License-File: licenses/d3-selection-LICENSE.txt
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Typing :: Typed
Requires-Dist: quickjs-ng
Requires-Python: >=3.10
Project-URL: Documentation, https://github.com/AUS-DOH-Safety-and-Quality/controlchartspy#readme
Project-URL: Repository, https://github.com/AUS-DOH-Safety-and-Quality/controlchartspy
Project-URL: Issues, https://github.com/AUS-DOH-Safety-and-Quality/controlchartspy/issues
Project-URL: Changelog, https://github.com/AUS-DOH-Safety-and-Quality/controlchartspy/blob/main/CHANGELOG.md
Description-Content-Type: text/markdown

# controlchartspy

The `controlchartspy` package provides tools for both calculating and visualising
statistical process control (SPC), funnel, and multi-indicator sigma charts in
Python. It is based on the R
[controlcharts](https://github.com/AUS-DOH-Safety-and-Quality/controlcharts) package.

## Installation

Install from PyPi with:

```sh
uv add controlchartspy
```

Or install the development version from GitHub with:

```sh
uv add --git https://github.com/AUS-DOH-Safety-and-Quality/controlchartspy/
```

## Getting Started

### Constructing SPC Charts

Consider a basic time series comprising two years of monthly data:

```python
from datetime import date
from random import Random

from controlchartspy import spc, funnel

rng = Random(12324)
dat = {
    "month": [date(2024 + i // 12, i % 12 + 1, 1) for i in range(24)],
    "y": [rng.gauss(0, 1) for _ in range(24)],
}
```

By default, the `spc()` function constructs an `i`-chart (individuals chart,
also referred to as an XmR chart):

```python
spc_chart = spc(data=dat, keys="month", numerators="y")
```

The result is a `ControlChart` containing:

- `static_plot`: a static SVG plot of the SPC chart.
- `limits`: a list of records containing the calculated control limits.

Display the plot in a notebook:

```python
spc_chart.static_plot
```

![Individuals SPC chart for the 24 monthly observations](https://raw.githubusercontent.com/AUS-DOH-Safety-and-Quality/controlchartspy/main/docs/images/spc_chart.svg)

Inspect the first three limit records:

```python
from pprint import pprint

pprint(spc_chart.limits[:3], sort_dicts=False)
```

```text
[{'date': '01/01/2024',
  'value': 1.5421097574859834,
  'target': -0.594252476265739,
  'll99': -3.336604002943138,
  'll95': -2.422486827384005,
  'll68': -1.508369651824872,
  'ul68': 0.3198646992933941,
  'ul95': 1.2339818748525273,
  'ul99': 2.1480990504116604,
  'trend_line': -0.0438723999116986},
 {'date': '01/02/2024',
  'value': 0.8450795313134178,
  'target': -0.594252476265739,
  'll99': -3.336604002943138,
  'll95': -2.422486827384005,
  'll68': -1.508369651824872,
  'ul68': 0.3198646992933941,
  'ul95': 1.2339818748525273,
  'ul99': 2.1480990504116604,
  'trend_line': -0.09173153698596298},
 {'date': '01/03/2024',
  'value': -0.27791938061152177,
  'target': -0.594252476265739,
  'll99': -3.336604002943138,
  'll95': -2.422486827384005,
  'll68': -1.508369651824872,
  'ul68': 0.3198646992933941,
  'ul95': 1.2339818748525273,
  'ul99': 2.1480990504116604,
  'trend_line': -0.13959067406022735}]
```

### Funnel Plot

The same interface is provided for funnel charts. For this example, consider
proportion data recorded by ten organisations:

```python
funnel_data = {
    "organisation": list("abcdefghij"),
    "numerators": [27, 23, 35, 26, 38, 22, 46, 26, 27, 35],
    "denominators": [170, 122, 178, 184, 161, 105, 190, 121, 137, 197],
}
```

For funnel plots, the default is a proportions (`PR`) chart:

```python
funnel_chart = funnel(
    data=funnel_data,
    keys="organisation",
    numerators="numerators",
    denominators="denominators",
)
funnel_chart.static_plot
```

![Funnel plot comparing proportions across ten organisations](https://raw.githubusercontent.com/AUS-DOH-Safety-and-Quality/controlchartspy/main/docs/images/funnel_chart.svg)

Inspect the first three limit records. Values and limits are percentages;
records are ordered by denominator.

```python
pprint(funnel_chart.limits[:3], sort_dicts=False)
```

```text
[{'denominator': 105,
  'group': 'f',
  'numerator': 22,
  'value': 20.952380952380953,
  'z': 0.3734456258038048,
  'target': 19.488817891373802,
  'll99': 9.100195099772012,
  'll95': 12.51476452678048,
  'll68': 15.79416361293754,
  'ul68': 23.470617415242504,
  'ul95': 27.575733596704584,
  'ul99': 32.631402897252705},
 {'denominator': 121,
  'group': 'h',
  'numerator': 26,
  'value': 21.487603305785125,
  'z': 0.5448873656525446,
  'target': 19.488817891373802,
  'll99': 9.70266069838873,
  'll95': 12.951239970877559,
  'll68': 16.03721031743157,
  'ul68': 23.189626957479458,
  'ul95': 26.992492421239433,
  'ul99': 31.66717662194669},
 {'denominator': 122,
  'group': 'b',
  'numerator': 23,
  'value': 18.852459016393443,
  'z': -0.17856345913252486,
  'target': 19.488817891373802,
  'll99': 9.736836999381445,
  'll95': 12.97581932678086,
  'll68': 16.050836651226742,
  'ul68': 23.173959368513962,
  'ul95': 26.96001503043085,
  'ul99': 31.613520185472975}]
```

## Chart Types

The package supports fourteen SPC chart types and three funnel plot types.
Select a chart type with `spc_settings={"chart_type": ...}` or
`funnel_settings={"chart_type": ...}`:

| Function | Chart types | Additional required inputs |
| --- | --- | --- |
| `spc` | `run`, `i` (default), `i_m`, `i_mm`, `mr`, `c`, `g`, `t` | None |
| `spc` | `p`, `pp` (proportions), `u`, `up` (rates) | `denominators` |
| `spc` | `xbar` (sample means) | `denominators`, `xbar_sds` |
| `spc` | `s` (sample standard deviations) | `denominators`; supply standard deviations as `numerators` |
| `funnel` | `PR` (proportions, default), `SR` (indirectly standardised ratios), `RC` (rates) | `denominators` |

The individuals chart uses moving ranges to calculate control limits. `i_m`
uses a median centreline; `i_mm` also uses median moving-range limits. `pp` and
`up` are proportion and rate charts with large-sample correction.

## Data and Arguments

`data` accepts a mapping of columns or a sequence of records. Column arguments
accept a column name or a sequence in the original row order:

- `keys`: the categories of the chart; for SPC charts, these form the x-axis.
- `numerators`: the numeric observations for each category.
- `denominators`: the denominators for each category, where required.
- `labels`: optional labels for each category.

SPC keys accept Python `date` objects and ISO date strings (`YYYY-MM-DD`),
including mixtures in one column. Rows are sorted by key, within indicator
groups when supplied. Funnel keys identify organisations or other groups.

Numeric `None` and `NaN` values are treated as missing. Infinite values are
rejected. Repeated keys are aggregated: numerators and denominators
default to `sum`, while other input fields default to `first`. Override these
with `aggregations`, using `first`, `last`, `sum`, `mean`, `min`, `max`, `median`,
or `count`.

Numeric aggregations follow Power BI column aggregation: missing values are
ignored, zeros are included, and an entirely missing group stays missing.
`count` counts non-missing values. Numerators and denominators are aggregated
independently. `first` and `last` select by input row order, including missing values.

## Aesthetic Options

Visual adjustments are handled through dedicated settings arguments. Each group
of settings has its own argument, such as `canvas_settings`, `line_settings`,
`scatter_settings`, `x_axis_settings`, or `y_axis_settings`. SPC charts also accept
`date_settings`, while `label_settings` controls observation labels.

To see the full list of available options, retrieve the complete defaults or
one group:

```python
from controlchartspy import spc_default_settings, funnel_default_settings

spc_defaults = spc_default_settings()
x_axis_defaults = spc_default_settings("x_axis")
funnel_defaults = funnel_default_settings("funnel")
```

Each settings argument is a mapping. Values can be scalars, one-element
sequences, or sequences aligned with the original input rows.

For example, add a title and customise the x-axis:

```python
styled_chart = spc(
    data=dat,
    keys="month",
    numerators="y",
    title={"text": "Monthly Performance", "font_size": "20px"},
    x_axis_settings={
        "xlimit_tick_rotation": -45,
        "xlimit_label": "Month of Observation",
    },
)
styled_chart.static_plot
```

## Outlier Detection

Configure outlier detection through `outlier_settings`. SPC charts support
astronomical points, shifts, trends, and two-in-three patterns. Use
`spc_default_settings("outliers")` to see the available detection parameters.
NHS variation and assurance icons are configured through `nhs_icon_settings`.

Detection switches apply to the whole chart, separately for each SPC indicator.
With row-aligned settings, the engine uses the first row with valid outlier
settings after sorting and aggregating keys. Returned flag columns use those
same settings.

```python
spc_data = {
    "date": [date(2023, month, 1) for month in range(1, 13)],
    "value": [10, 12, 11, 15, 12, 11, 25, 12, 11, 10, 11, 12],
}
outlier_chart = spc(
    data=spc_data,
    keys="date",
    numerators="value",
    outlier_settings={
        "astronomical": True,
        "shift": True,
        "trend": True,
        "two_in_three": True,
    },
)
outlier_chart.static_plot
outlier_chart.limits
```

## Phases and Grouped Limits

Use `groupings` to calculate separate limits for phases of one time series:

```python
grouped_data = {
    **dat,
    "phase": ["Before"] * 12 + ["After"] * 12,
    "site": ["A"] * 12 + ["B"] * 12,
}
phased_chart = spc(
    data=grouped_data,
    keys="month",
    numerators="y",
    groupings="phase",
)
grouped_chart = spc(
    data=grouped_data,
    keys="month",
    numerators="y",
    indicators="site",
    return_objs="limits",
)
site_a_limits = grouped_chart.limits["A"]
```

`indicators` create separate series and nested limits. A mapping such as
`indicators={"Region": "region", "Site": "site"}` creates a nested mapping in
that order, with names available through `chart.indicator_names`. A single
indicator group can render a plot; multiple groups require `return_objs="limits"`.

## Multi-indicator Sigma Charts

Use `misc()` to compare one organisation across several indicators. Each indicator
uses an independent funnel calculation over its comparison population. The chart
shows the selected organisation's z-scores, with positive scores indicating
favourable performance.

```python
from controlchartspy import misc

comparison = {
    "organisation": list("ABCDABCD"),
    "indicator": ["Measure A"] * 4 + ["Measure B"] * 4,
    "numerator": [8, 5, 4, 6, 2, 4, 5, 3],
    "denominator": [10] * 8,
}
sigma_chart = misc(
    comparison,
    keys="organisation",
    numerators="numerator",
    denominators="denominator",
    indicators="indicator",
    target="A",
    outlier_settings={"improvement_direction": ["increase"] * 4 + ["decrease"] * 4},
)
sigma_chart.static_plot
```

![Sigma chart showing organisation A's scores for two indicators](https://raw.githubusercontent.com/AUS-DOH-Safety-and-Quality/controlchartspy/main/docs/images/sigma_chart.svg)

Inspect the returned records for the selected organisation:

```python
pprint(sigma_chart.limits, sort_dicts=False)
```

```text
[{'indicator': 'Measure A',
  'group': 'A',
  'z': 1.558790495298732,
  'score': 1.558790495298732,
  'three_sigma': 'none',
  'numerator': 8,
  'denominator': 10,
  'value': 80,
  'target': 57.49999999999999,
  'alt_target': None,
  'll99': 13.216714304122751,
  'll95': 27.39016233134321,
  'll68': 41.84129995477897,
  'ul68': 72.42308266420663,
  'ul95': 84.81980066370618,
  'ul99': 95.17325739282423},
 {'indicator': 'Measure B',
  'group': 'A',
  'z': -1.07140640762028,
  'score': 1.07140640762028,
  'three_sigma': 'none',
  'numerator': 2,
  'denominator': 10,
  'value': 20,
  'target': 35,
  'alt_target': None,
  'll99': 2.071914317061943,
  'll95': 10.084383484380846,
  'll68': 20.982115523920374,
  'ul68': 50.48911923810838,
  'ul95': 65.49569052552037,
  'ul99': 81.1481422890441}]
```

Measure B's score reverses its z-score because decreases indicate improvement.

`misc()` delegates to `funnel()` with `indicators` and
`misc_settings={"group": target}`. You can call `funnel()` directly with those
arguments; omitting `group` selects the first group. `indicators` accepts one
column or a mapping such as `{"Domain": "domain", "Measure": "indicator"}`.
Rows are sorted by indicator columns and key; indicators without the selected
target are omitted. Three-sigma outlier detection is always enabled.

All settings use the native Funnel options and align with the input rows. Each
indicator must have one `improvement_direction`. Use `misc_settings` for sigma
bounds and bar height, and `outlier_settings` for bar colours, such as
`three_sigma_colour_improvement`. `misc_default_settings()` returns the same
defaults as `funnel_default_settings()`.

## Results and Saving Plots

All constructors return a `ControlChart`. `return_objs` accepts one output name
or an iterable; its default is `("static_plot", "limits")`.

| Attribute | Contents |
| --- | --- |
| `static_plot` | `StaticPlot` with `to_svg()` and notebook SVG display |
| `limits` | A list of records, or nested mappings for grouped SPC series |
| `limit_lines` | Funnel control limits across the full range of denominators; available without `indicators`, by requesting `"limit_lines"` |
| `indicator_names` | SPC indicator names in nesting order |

Unrequested outputs are `None`. SPC records include the formatted `date`,
`value`, `target`, and applicable lower/upper limits (`ll68`, `ll95`, `ll99`,
`ul68`, `ul95`, `ul99`). Columns depend on chart type and requested signal flags.
Funnel records include `group`, `numerator`, `denominator`, `value`, `z`, `target`,
and limits. Proportion charts use percentage values under the default settings.
Sigma records include `indicator`, `group`, the original `z`, the
direction-adjusted `score`, `three_sigma`, and the corresponding funnel values
and limits. The selected organisation is recorded in `group`.

Save a plot as SVG with `save_plot()`:

```python
spc_chart.save_plot("spc_chart.svg")
funnel_chart.save_plot("funnel_chart.svg", width=960, height=600)
```

Specify `width` and `height` to render at a different size, or use
`spc_chart.static_plot.to_svg()` to get the SVG as a string. The default size is
640 × 400 pixels. Static plots display as SVGs in notebooks.

The [R package documentation](https://aus-doh-safety-and-quality.github.io/controlcharts/)
provides further information about the shared calculation and display settings.
