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'
|
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'
|
window_size
|
int
|
Number of previous points the rolling chart uses
(at least 2); for |
20
|
ewma_lambda
|
float | None
|
EWMA weight |
None
|
n_sigma
|
float
|
Width |
3.0
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
pd.DataFrame: One row per actual point. Columns: systime, value
(the actual value), ewma ( |
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: |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
pd.DataFrame: The violating rows (a copy) with a boolean
|
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: |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
pd.DataFrame: The violating rows (a copy) with a boolean
|
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_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_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: |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
pd.DataFrame: The violating rows (a copy) with a boolean
|
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: |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
pd.DataFrame: The violating rows (a copy) with a boolean
|
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: |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
pd.DataFrame: The violating rows (a copy) with a boolean
|
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: |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
pd.DataFrame: The violating rows (a copy) with a boolean
|
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 ( |
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
|