Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Xbar-S Analysis

Xbar-S charts are the gold standard for monitoring processes with subgrouped data. They’re ideal when you have:

  • Multiple measurements per time period

  • Rational subgroups (factors like machines, operators, batches)

  • Need to monitor both location (mean) and spread (variation)

What You’ll Learn

  1. Create Xbar and S charts from replicated data

  2. Understand how design states affect variance estimation

  3. Compare factor levels using control charts

  4. Access VAS residuals for deeper analysis

Setup

import numpy as np
import pandas as pd
from processbehavior import ProcessBehavior

Create Replicated Data

We’ll simulate a filling machine with:

  • 3 operators (A, B, C)

  • 8 time periods

  • 4 replicate measurements per operator per time period

This creates design state 1 (full replication) — the most powerful design on Bishop’s 1–6 reference scale.

np.random.seed(42)

operators = ['A', 'B', 'C']
n_times = 8
n_reps = 4

data = []
for t in range(n_times):
    for op in operators:
        # Each operator has a slightly different mean
        op_effect = {'A': 0, 'B': 2, 'C': -1}[op]
        
        # Add time trend (process drift)
        time_effect = t * 0.3
        
        for rep in range(n_reps):
            # Add special cause for Operator B at time 6
            special = 8 if (op == 'B' and t == 6) else 0
            
            value = 100 + op_effect + time_effect + special + np.random.normal(0, 1.5)
            data.append({
                'time': t + 1,
                'operator': op,
                'weight': round(value, 2)
            })

df = pd.DataFrame(data)
print(f"Dataset: {len(df)} observations")
print(f"Structure: {len(operators)} operators x {n_times} times x {n_reps} reps")
df.head(12)
Dataset: 96 observations
Structure: 3 operators x 8 times x 4 reps
Loading...

Formulate the Study

pb = ProcessBehavior(df)

study = pb.formulate(
    response=pb.cols.weight,
    factors=[pb.cols.operator],
    time=pb.cols.time
)

print(f"ADS: {study.analytical_design_state.sds} ({study.ads_reason})")
print(f"Description: {study.ads_description}")
print(f"\nValid charts: {study.valid_charts}")
print(f"Recommended: {study.recommended_chart}")
print(f"Residual charts: {study.residual_charts}")
ADS: 1 (full_replication)
Description: Full replication (all cells n≥2)

Valid charts: ['Histogram', 'Xbar', 'S', 'X', 'mR']
Recommended: Xbar
Residual charts: [('Xbar', 'R1'), ('X', 'R1'), ('S', 'R2'), ('X', 'R2'), ('Xbar', 'R3'), ('S', 'R3'), ('Xbar', 'R4'), ('S', 'R4'), ('Xbar', 'R5'), ('S', 'R5'), ('Xbar', 'R6'), ('S', 'R6')]

Understanding design state 1

Design state 1 (full replication) is the most powerful because:

  1. Every (operator, time) cell has multiple observations

  2. Within-cell variance can be estimated exactly

  3. All stored VAS residuals (R1–R5) are computed, and the per-request R6 is available

  4. Interactions can be detected

The formula for control limits uses the pooled within-cell standard deviation.

Execute Xbar-S Charts Analysis

# companion=True computes the S chart alongside the recommended Xbar —
# execute() alone returns only the recommended chart.
result = study.execute(companion=True)

print(f"Charts created: {result.all_charts}")
print(f"Has residuals: {result.has_residuals}")
Charts created: ['Xbar', 'S']
Has residuals: True

View Chart Data

# Xbar chart shows subgroup means
xbar_data = result.get_chart('Xbar')
print("Xbar Chart Data (subgroup means):")
xbar_data.head(10)
Xbar Chart Data (subgroup means):
Loading...
# S chart shows subgroup standard deviations
s_data = result.get_chart('S')
print("\nS Chart Data (subgroup std devs):")
s_data.head(10)

S Chart Data (subgroup std devs):
Loading...
# Statistics for both charts
print("Xbar Statistics:")
display(result.get_statistics('Xbar'))

print("\nS Statistics:")
display(result.get_statistics('S'))
Xbar Statistics:
{'center': np.float64(101.549), 'N': np.int64(4), 'upl': np.float64(103.651), 'lpl': np.float64(99.448)}

S Statistics:
{'center': np.float64(1.29), 'N': np.int64(4), 'upl': np.float64(2.924), 'lpl': np.float64(0.0)}

Visualize Xbar Chart

fig = result.plot(
    chart='Xbar',
    show_zones=True,
    highlight_signals=True,
    show_stats=True
)
fig.show()
Loading...

Visualize S Chart

fig = result.plot(
    chart='S',
    show_zones=True,
    highlight_signals=True
)
fig.show()
Loading...

Understanding Xbar-S Charts

The Xbar Chart

  • Plots the mean of each subgroup

  • Centerline: Grand mean of all observations

  • Limits based on within-subgroup variation

  • Detects shifts in process level

The S Chart

  • Plots the standard deviation of each subgroup

  • Centerline: Pooled within-subgroup standard deviation

  • Limits based on chi-square distribution

  • Detects changes in process variation

Reading Order

  1. First check the S chart - Variation must be stable

  2. Then interpret the Xbar chart - Only valid if S is stable

  3. Points on Xbar beyond limits → investigate the specific subgroup

Signal Detection for Xbar-S

For Xbar and S charts (categorical comparisons), only Rule 1 applies - points beyond the control limits.

# Detect signals on Xbar
signals = result.detect_signals(chart='Xbar')

print(f"Xbar signals: {signals.count}")
if signals.has_signals:
    print("\nViolations:")
    display(signals.violations)
Xbar signals: 9

Violations:
Loading...
# Detect signals on Sbar
signals_s = result.detect_signals(chart='S')

print(f"S chart signals: {signals_s.count}")
S chart signals: 0

Accessing VAS Residuals

With full replication, the stored residuals R1–R5 are computed at formulate() time and live on the result. (R6 is request-scoped — computed per execute(value='R6', by=...) call — so it is deliberately not in this frame.)

# View the computed residuals
residuals = result.residuals
print("VAS Residuals:")
residuals.head(10)
VAS Residuals:
Loading...
# Analyze time effects using R4 residuals on S chart
result_r4 = study.execute(chart='S', value='R4')

# View the chart data
print("R4 Residual on S Chart (Time Effects):")
print(f"Charts: {result_r4.all_charts}")
result_r4.get_chart('S').head()
R4 Residual on S Chart (Time Effects):
Charts: ['S']
Loading...
# Analyze factor (operator) effects using R5 residuals on S chart
result_r5 = study.execute(chart='S', value='R5')

# View the chart data
print("R5 Residual on S Chart (Operator Effects):")
print(f"Charts: {result_r5.all_charts}")
result_r5.get_chart('S').head()
R5 Residual on S Chart (Operator Effects):
Charts: ['S']
Loading...

Chart Table Summary

Get a compact summary table for reporting:

# Summary table with subgroup info, values, and limits
table = result.chart_table('Xbar')
table
Loading...

Summary

In this tutorial, you learned:

  • Xbar-S charts require subgrouped data (n >= 2 per cell)

  • Design state 1 (full replication) provides the most analytical power

  • The S chart monitors variation; the Xbar chart monitors level

  • Only Rule 1 applies to Xbar-S charts

  • VAS residuals enable deeper root cause analysis

Next Steps