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. With indicators, return_objs defaults to "limits"; a single indicator group can still render a plot, but multiple groups cannot.

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,
  '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,
  '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.2

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.2
File Size Uploaded
controlchartspy-0.1.2.tar.gz 115.7 kB Details

Built distribution (wheel)

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

Total release size: 221.5 kB

Release files / controlchartspy-0.1.2.tar.gz

Download URL controlchartspy-0.1.2.tar.gz
Size 115.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d02f6c64154751b4783b24775c67142b2cb6696e02601b9eee7262d62f1aa2e4
BLAKE2b-256 checksum
How to use checksums
95a956cb58673107e5ff0dabf32bf5cb106f6f9b6dd6b891b1c0b876489d3d35
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","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.2-py3-none-any.whl

Download URL controlchartspy-0.1.2-py3-none-any.whl
Size 105.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e5840919d19ccb91fe6c6dfa9394c5ca55e1d1a12d492ef6049cc75d8fbdedbc
BLAKE2b-256 checksum
How to use checksums
65ff106561086bf46dc6147bce2fd51af0bb08f7f0c9c16eccee0d8dd6e391e4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","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.2 This release

2 release files

0.1.1

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