Skip to content

multivariate_spc

multivariate_spc ¤

Multivariate SPC: Hotelling T² across correlated process variables.

Univariate control charts check each variable against its own limits. On a process whose variables move together (temperature and pressure, feed and torque) a fault often breaks the relationship while every variable stays inside its own 3-sigma band. Hotelling's T² measures the distance of each observation from the in-control mean in units of the in-control covariance, so it sees exactly that.

Phase I estimates the mean vector and covariance matrix on a reference period. Phase II scores every observation against them, with the exact F-distribution control limit for a covariance estimated from m reference observations (Montgomery, Introduction to Statistical Quality Control, ch. 11). Reference observations themselves are judged against the Phase I beta-distribution limit.

Signals are aligned on the timestamps of the first signal; every other signal contributes the value in force at that time (held backward, never across more than max_gap: no look-ahead).

MultivariateSPCEvents ¤

MultivariateSPCEvents(
    dataframe: DataFrame,
    signal_uuids: list[str],
    *,
    value_column: str = "value_double",
    event_uuid: str = "quality:hotelling_t2",
    max_gap: str | Timedelta | None = None,
    time_column: str = "systime"
)

Bases: Base

Quality: multivariate SPC with Hotelling T².

Methods: - hotelling_t2: T² and its control limit for every aligned observation. - contributions: Which variables drive each out-of-control T². - detect_out_of_control: Runs of out-of-control observations as intervals, with the top contributing variable.

Example

import ts_shape df = ts_shape.make_timeseries(["temp", "pressure"], n_points=200) df.loc[150, "value_double"] += 8 # one disturbed temp sample mspc = ts_shape.MultivariateSPCEvents(df, ["temp", "pressure"]) out = mspc.detect_out_of_control() out[["start", "max_t2", "ucl", "top_contributor"]].round( ... {"max_t2": 1, "ucl": 1} ... ) start max_t2 ucl top_contributor 0 2025-01-01 01:15:00 56.9 11.5 temp

Parameters:

Name Type Description Default
dataframe DataFrame

Long-format data.

required
signal_uuids list[str]

The process variables (at least two).

required
value_column str

Column holding the values.

'value_double'
event_uuid str

uuid of the emitted events.

'quality:hotelling_t2'
max_gap str | Timedelta | None

Longest time a value of the second and later signals is held to align it with the first signal; None = no cap.

None
time_column str

Timestamp column.

'systime'

hotelling_t2 ¤

hotelling_t2(
    reference_period: (
        tuple[TimeLike, TimeLike] | None
    ) = None,
    alpha: float = 0.0027,
) -> pd.DataFrame

Hotelling T² for every aligned observation.

Parameters:

Name Type Description Default
reference_period tuple[TimeLike, TimeLike] | None

(start, end) of the in-control reference data (end exclusive). None uses all data (a retrospective Phase I study).

None
alpha float

False-alarm probability per observation (0.0027 matches 3-sigma limits).

0.0027

Returns:

Type Description
DataFrame

Point events: systime, uuid, source_uuid (the signal

DataFrame

uuids, comma-joined), t2, ucl, phase ("I" for

DataFrame

reference observations, "II" otherwise) and

DataFrame

out_of_control. Empty, with a

DataFrame

class:~ts_shape.errors.DataQualityWarning, when the reference

DataFrame

period has too few aligned observations (p + 2 are needed

DataFrame

for p variables).

contributions ¤

contributions(
    reference_period: (
        tuple[TimeLike, TimeLike] | None
    ) = None,
    alpha: float = 0.0027,
    only_out_of_control: bool = True,
) -> pd.DataFrame

Per-variable decomposition of T².

Contribution of variable j: d_j * (S^-1 d)_j with d the deviation from the reference mean. The contributions of one observation sum to its T²; a large share points at the variable (or broken relationship) behind the signal. z_score is the plain univariate deviation, for comparison.

Parameters:

Name Type Description Default
reference_period tuple[TimeLike, TimeLike] | None

As in :meth:hotelling_t2.

None
alpha float

As in :meth:hotelling_t2.

0.0027
only_out_of_control bool

Keep only observations flagged out of control by :meth:hotelling_t2 (default). False returns every observation.

True

Returns:

Type Description
DataFrame

Point events, one row per (observation, variable): systime,

DataFrame

uuid, source_uuid (the variable), t2,

DataFrame

contribution, contribution_pct and z_score.

detect_out_of_control ¤

detect_out_of_control(
    reference_period: (
        tuple[TimeLike, TimeLike] | None
    ) = None,
    alpha: float = 0.0027,
    min_samples: int = 1,
) -> pd.DataFrame

Runs of consecutive out-of-control observations.

Parameters:

Name Type Description Default
reference_period tuple[TimeLike, TimeLike] | None

As in :meth:hotelling_t2.

None
alpha float

As in :meth:hotelling_t2.

0.0027
min_samples int

Minimum observations in a run.

1

Returns:

Type Description
DataFrame

Intervals from the first to the last out-of-control observation

DataFrame

of each run: start, end, duration_seconds, uuid,

DataFrame

source_uuid, max_t2, ucl (at the peak),

DataFrame

sample_count and top_contributor (the variable with the

DataFrame

largest summed contribution over the run).

phase2_ucl ¤

phase2_ucl(p: int, m: int, alpha: float) -> float

Phase II T² upper control limit for new observations.

p (m + 1)(m - 1) / (m (m - p)) * F(1 - alpha; p, m - p) for a mean and covariance estimated from m reference observations.

phase1_ucl ¤

phase1_ucl(p: int, m: int, alpha: float) -> float

Phase I T² limit for the reference observations themselves.

(m - 1)^2 / m * Beta(1 - alpha; p / 2, (m - p - 1) / 2).