Skip to content

statistical_process_control

statistical_process_control ¤

StatisticalProcessControlEvents ¤

StatisticalProcessControlEvents(
    dataframe: DataFrame,
    value_column: str,
    tolerance_uuid: str,
    actual_uuid: str,
    event_uuid: str,
    *,
    sigma_method: SigmaMethod = "moving_range"
)

Bases: Base

Inherits from Base and applies SPC rules (Western Electric Rules) to a DataFrame for event detection. Processes data based on control limit UUIDs, actual value UUIDs, and generates events with an event UUID.

rule_1 .. rule_8 are the eight tests of Nelson (1984), "The Shewhart Control Chart--Tests for Special Causes", Journal of Quality Technology 16(4); rules 1, 5 and 6 are the Western Electric zone rules (Montgomery, Introduction to Statistical Quality Control, Table 5.1). The center line and sigma come from the tolerance_uuid rows (the reference / phase I data).

The rules are building blocks, not detectors: each takes the actual signal's rows (dataframe, sorted by time) and, where it needs them, the limits from :meth:control_limits, and returns the violating rows with a boolean rule_<n> column. :meth:detect_rule_violations is the detector entry point: it runs the selected rules on this detector's data and returns the event frame (systime, uuid, source_uuid, rule, severity, ...). Call a rule directly only to compose a custom check.

Sigma: by default (sigma_method="moving_range") the textbook individuals-chart estimate sigma_hat = MR-bar / d2 with d2 = 1.128, where MR-bar is the mean moving range of consecutive reference values (Montgomery, Introduction to Statistical Quality Control, section 6.4, "Control charts for individual measurements"). It measures the short-term (common-cause) variation only, so a drift or shift inside the reference does not widen the limits. sigma_method="std" restores the sample standard deviation (ddof=1) that ts-shape used before October 2026. The same sigma is used by :meth:control_limits, the rules 1-8 zones, :meth:detect_cusum_shifts and the EWMA chart of :meth:dynamic_control_limits.

Example

import pandas as pd, ts_shape ref = ts_shape.make_timeseries(["ref"], n_points=50, seed=1) # in-control act = ts_shape.make_timeseries(["act"], n_points=50, n_outliers=2) spc = ts_shape.StatisticalProcessControlEvents( ... pd.concat([ref, act]), "value_double", "ref", "act", "quality:spc") out = spc.detect_rule_violations(selected_rules=["rule_1"]) out[["systime", "value", "rule", "severity"]].round({"value": 1}) systime value rule severity 0 2025-01-01 00:19:30 112.7 rule_1 critical 1 2025-01-01 00:24:30 88.1 rule_1 critical

Initializes StatisticalProcessControlEvents with UUIDs for tolerance, actual, and event values. Inherits the sorted dataframe from the Base class.

Parameters:

Name Type Description Default
dataframe DataFrame

The input DataFrame containing the data to be processed.

required
value_column str

The column containing the values to monitor.

required
tolerance_uuid str

UUID identifier for the reference rows the center line and sigma are estimated from.

required
actual_uuid str

UUID identifier for rows containing actual values.

required
event_uuid str

UUID to assign to generated events.

required
sigma_method SigmaMethod

How sigma is estimated from the reference rows. "moving_range" (default, textbook): MR-bar / 1.128 from consecutive reference values. "std": the sample standard deviation (ddof=1), the behavior before October 2026.

'moving_range'

control_limits ¤

control_limits() -> pd.DataFrame

Shewhart individuals-chart limits (center ± 1σ, 2σ, 3σ) from the reference rows.

Center line mean is the mean of the reference values; sigma is MR-bar / d2 (d2 = 1.128) of consecutive reference values with the default sigma_method="moving_range" (Montgomery, Introduction to Statistical Quality Control, section 6.4: UCL = x-bar + 3 MR-bar / d2, LCL = x-bar - 3 MR-bar / d2), or the sample standard deviation with sigma_method="std".

Returns:

Type Description
DataFrame

pd.DataFrame: One row with columns mean, 1sigma_upper, 1sigma_lower, 2sigma_upper, 2sigma_lower, 3sigma_upper and 3sigma_lower.

dynamic_control_limits ¤

dynamic_control_limits(
    method: Literal[
        "moving_range", "ewma"
    ] = "moving_range",
    window_size: int = 20,
    ewma_lambda: float | None = None,
    n_sigma: float = 3.0,
) -> pd.DataFrame

Control limits that adapt over time: a rolling individuals chart or an EWMA chart.

method="moving_range" -- rolling Shewhart individuals chart (Montgomery, Introduction to Statistical Quality Control, section 6.4). Each actual point x_i is tested against limits estimated from the window_size points before it, so a point never widens its own limits::

center_i = mean(x_{i-w}, ..., x_{i-1})
sigma_i  = mean(|x_j - x_{j-1}|, j = i-w+1 .. i-1) / 1.128

(sigma_method="std": sigma_i is the sample std of the same w previous points). The first window_size points have no complete history and get NaN limits, as does any point whose history contains a NaN value.

method="ewma" -- EWMA chart (Montgomery, section 9.2)::

z_i = lambda * x_i + (1 - lambda) * z_{i-1},   z_0 = mu_0
limits_i = mu_0 +/- L * sigma * sqrt(lambda / (2 - lambda)
                                  * (1 - (1 - lambda)^(2 i)))

mu_0 and sigma come from the reference (tolerance_uuid) rows, sigma by the instance's sigma_method. lambda is ewma_lambda when given, else 2 / (window_size + 1) (the smoothing of an EWMA with span window_size). Rows with a NaN value get NaN ewma and limits and do not advance i.

Parameters:

Name Type Description Default
method Literal['moving_range', 'ewma']

"moving_range" (default) or "ewma".

'moving_range'
window_size int

Number of previous points the rolling chart uses (at least 2); for "ewma" the span that sets lambda when ewma_lambda is not given (default: 20).

20
ewma_lambda float | None

EWMA weight 0 < lambda <= 1 ("ewma" only).

None
n_sigma float

Width L of the lcl / ucl columns in sigmas (default 3.0).

3.0

Returns:

Type Description
DataFrame

pd.DataFrame: One row per actual point. Columns: systime, value (the actual value), ewma (z_i; "ewma" only), mean (the center line: center_i, or mu_0 for EWMA), 1sigma_upper .. 3sigma_lower (center +/- k sigma_i; for EWMA mu_0 +/- k times the EWMA statistic's sigma), lcl / ucl (center -/+ n_sigma times the same sigma), uuid (the event uuid) and source_uuid (the actual signal's uuid). For EWMA compare ewma (not value) with the limits.

rule_1 ¤

rule_1(
    dataframe: DataFrame, limits: DataFrame
) -> pd.DataFrame

Rule 1 (Nelson 1984, test 1): one point more than 3σ from the center line.

A building block of :meth:detect_rule_violations (see the class docstring).

Parameters:

Name Type Description Default
dataframe DataFrame

Rows of the actual signal, sorted by time. Not modified.

required
limits DataFrame

Control limits as returned by :meth:control_limits; only the first row is used.

required

Returns:

Type Description
DataFrame

pd.DataFrame: The violating rows (a copy) with a boolean rule_1 column.

rule_2 ¤

rule_2(
    dataframe: DataFrame, *, limits: DataFrame | None = None
) -> pd.DataFrame

Rule 2 (Nelson 1984, test 2): nine points in a row on the same side of the center line.

A building block of :meth:detect_rule_violations (see the class docstring).

Parameters:

Name Type Description Default
dataframe DataFrame

Rows of the actual signal, sorted by time. Not modified.

required
limits DataFrame | None

Control limits as returned by :meth:control_limits; the center line is limits["mean"]. When omitted, the mean of dataframe's values is used as the center line.

None

Returns:

Type Description
DataFrame

pd.DataFrame: The violating rows (a copy) with a boolean rule_2 column (the ninth point of each run is flagged).

rule_3 ¤

rule_3(dataframe: DataFrame) -> pd.DataFrame

Rule 3 (Nelson 1984, test 3): six points in a row steadily increasing or decreasing.

A building block of :meth:detect_rule_violations (see the class docstring).

Six points means five consecutive strictly positive (or strictly negative) differences; an unchanged value breaks the run.

Parameters:

Name Type Description Default
dataframe DataFrame

Rows of the actual signal, sorted by time. Not modified.

required

Returns:

Type Description
DataFrame

pd.DataFrame: The violating rows (a copy) with a boolean rule_3 column (the sixth point of each run is flagged).

rule_4 ¤

rule_4(dataframe: DataFrame) -> pd.DataFrame

Rule 4 (Nelson 1984, test 4): fourteen points in a row alternating up and down.

A building block of :meth:detect_rule_violations (see the class docstring).

Fourteen points means thirteen consecutive non-zero differences whose signs alternate; an unchanged value breaks the run.

Parameters:

Name Type Description Default
dataframe DataFrame

Rows of the actual signal, sorted by time. Not modified.

required

Returns:

Type Description
DataFrame

pd.DataFrame: The violating rows (a copy) with a boolean rule_4 column (the fourteenth point of each run is flagged).

rule_5 ¤

rule_5(
    dataframe: DataFrame, limits: DataFrame
) -> pd.DataFrame

Rule 5 (Nelson 1984, test 5): two out of three points in a row more than 2σ from the center line, same side.

A building block of :meth:detect_rule_violations (see the class docstring).

Points beyond 3σ count as beyond 2σ (zone A or beyond).

Parameters:

Name Type Description Default
dataframe DataFrame

Rows of the actual signal, sorted by time. Not modified.

required
limits DataFrame

Control limits as returned by :meth:control_limits; only the first row is used.

required

Returns:

Type Description
DataFrame

pd.DataFrame: The violating rows (a copy) with a boolean rule_5 column (the last point of each qualifying three-point window is flagged).

rule_6 ¤

rule_6(
    dataframe: DataFrame, limits: DataFrame
) -> pd.DataFrame

Rule 6 (Nelson 1984, test 6): four out of five points in a row more than 1σ from the center line, same side.

A building block of :meth:detect_rule_violations (see the class docstring).

Points beyond 2σ or 3σ count as beyond 1σ (zone B or beyond).

Parameters:

Name Type Description Default
dataframe DataFrame

Rows of the actual signal, sorted by time. Not modified.

required
limits DataFrame

Control limits as returned by :meth:control_limits; only the first row is used.

required

Returns:

Type Description
DataFrame

pd.DataFrame: The violating rows (a copy) with a boolean rule_6 column (the last point of each qualifying five-point window is flagged).

rule_7 ¤

rule_7(
    dataframe: DataFrame, limits: DataFrame
) -> pd.DataFrame

Rule 7 (Nelson 1984, test 7): fifteen points in a row within 1σ of the center line (either side).

A building block of :meth:detect_rule_violations (see the class docstring).

Parameters:

Name Type Description Default
dataframe DataFrame

Rows of the actual signal, sorted by time. Not modified.

required
limits DataFrame

Control limits as returned by :meth:control_limits; only the first row is used.

required

Returns:

Type Description
DataFrame

pd.DataFrame: The violating rows (a copy) with a boolean rule_7 column.

rule_8 ¤

rule_8(
    dataframe: DataFrame, limits: DataFrame
) -> pd.DataFrame

Rule 8 (Nelson 1984, test 8): eight points in a row with none within 1σ of the center line.

A building block of :meth:detect_rule_violations (see the class docstring).

The points may lie on either side of the center line; what matters is that none falls in zone C (within 1σ).

Parameters:

Name Type Description Default
dataframe DataFrame

Rows of the actual signal, sorted by time. Not modified.

required
limits DataFrame

Control limits as returned by :meth:control_limits; only the first row is used.

required

Returns:

Type Description
DataFrame

pd.DataFrame: The violating rows (a copy) with a boolean rule_8 column.

detect_rule_violations ¤

detect_rule_violations(
    selected_rules: list[str] | None = None,
) -> pd.DataFrame

Applies SPC rules using vectorized operations with optimized multi-rule processing. Processes multiple rules in fewer passes through the data for better performance.

Parameters:

Name Type Description Default
selected_rules Optional[List[str]]

List of rule names to apply. If None, applies all rules.

None

Returns:

Type Description
DataFrame

pd.DataFrame: DataFrame with rule violations. Columns: systime, value, rule, severity, uuid (the event uuid) and source_uuid (the actual signal's uuid).

detect_cusum_shifts ¤

detect_cusum_shifts(
    target: float | None = None,
    k: float = 0.5,
    h: float = 5.0,
) -> pd.DataFrame

Detect process shifts using CUSUM (Cumulative Sum) control chart.

CUSUM charts are effective at detecting small shifts in the process mean and are more sensitive than traditional Shewhart control charts.

Tabular CUSUM (Montgomery, Introduction to Statistical Quality Control, section 9.1)::

C+_i = max(0, x_i - (mu_0 + K) + C+_{i-1})
C-_i = max(0, (mu_0 - K) - x_i + C-_{i-1}),   C+_0 = C-_0 = 0

with K = k * sigma and decision interval H = h * sigma; a point is a shift when C+_i > H or C-_i > H. sigma is estimated from the reference rows by the instance's sigma_method (default MR-bar / 1.128).

Parameters:

Name Type Description Default
target Optional[float]

Target mean value. If None, uses the mean of tolerance data.

None
k float

Reference value (slack parameter) in sigmas, typically 0.5 to 1.0. Smaller k detects smaller shifts. Default: 0.5

0.5
h float

Decision interval in sigmas. Typical values are 4-5. Smaller h gives faster detection but more false alarms. Default: 5.0

5.0

Returns:

Type Description
DataFrame

pd.DataFrame: DataFrame with CUSUM statistics and detected shifts. Columns: systime, value_column, cusum_high, cusum_low, shift_direction, severity, uuid (the event uuid) and source_uuid (the actual signal's uuid).

interpret_violations ¤

interpret_violations(
    violations_df: DataFrame,
) -> pd.DataFrame

Add human-readable interpretations to rule violations.

Parameters:

Name Type Description Default
violations_df DataFrame

DataFrame with rule violations (output from detect_rule_violations or process methods)

required

Returns:

Type Description
DataFrame

pd.DataFrame: Enhanced DataFrame with interpretation and recommendation columns.

detect_violations ¤

detect_violations(
    selected_rules: list[str] | None = None,
    include_severity: bool = True,
) -> pd.DataFrame

Applies the selected SPC rules and generates a DataFrame of events where any rules are violated.

Parameters:

Name Type Description Default
selected_rules Optional[List[str]]

List of rule names (e.g., ['rule_1', 'rule_3']) to apply.

None
include_severity bool

Include the rule that flagged each point (triggered_rule) and its severity (default True). False returns only the point columns.

True

Returns:

Type Description
DataFrame

pd.DataFrame: One row per violating point (a point that violates several rules is reported once, with the first selected rule that flagged it). Columns: [systime, value_column, uuid, triggered_rule, severity, source_uuid]; without triggered_rule and severity when include_severity=False. source_uuid is the actual signal's uuid. An empty result has the same columns.