Skip to main content

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

Installation

Install from PyPi with:

uv add controlchartspy

Or install the development version from GitHub with:

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:

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

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:

spc_chart.static_plot

Individuals SPC chart for the 24 monthly observations

Inspect the first three limit records:

from pprint import pprint

pprint(spc_chart.limits[:3], sort_dicts=False)
[{'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:

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:

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

Funnel plot comparing proportions across ten organisations

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

pprint(funnel_chart.limits[:3], sort_dicts=False)
[{'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:

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:

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.

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:

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.

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

Inspect the returned records for the selected organisation:

pprint(sigma_chart.limits, sort_dicts=False)
[{'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():

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 provides further information about the shared calculation and display settings.

Metadata

Release files for controlchartspy 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for controlchartspy 0.1.1
File Size Uploaded
controlchartspy-0.1.1.tar.gz 235.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for controlchartspy 0.1.1
File Interpreter ABI Platform
controlchartspy-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 369.8 kB

Release files / controlchartspy-0.1.1.tar.gz

Download URL controlchartspy-0.1.1.tar.gz
Size 235.8 kB
Tags Source
SHA-256 checksum
How to use checksums
55be5c54fb7dc6c76692ea56e443670ea558a41ff174daefa9b55272083dd05b
BLAKE2b-256 checksum
How to use checksums
0499524f989f9b188d0e506d3d53185f40abcf79ddaa221cb888e829f2ff3132
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / controlchartspy-0.1.1-py3-none-any.whl

Download URL controlchartspy-0.1.1-py3-none-any.whl
Size 134.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ac7d84acf2ec13d934d97f7d71fedf5351e2346be3d95bed5aadf6d0dc6511c2
BLAKE2b-256 checksum
How to use checksums
6d57f4a7d97b2d9759129434639cb29004f197fc068dcd2be85c43d16ce0ce0a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page