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¶
Create Xbar and S charts from replicated data
Understand how design states affect variance estimation
Compare factor levels using control charts
Access VAS residuals for deeper analysis
Setup¶
import numpy as np
import pandas as pd
from processbehavior import ProcessBehaviorCreate 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
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:
Every (operator, time) cell has multiple observations
Within-cell variance can be estimated exactly
All stored VAS residuals (R1–R5) are computed, and the per-request R6 is available
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):
# 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):
# 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()Visualize S Chart¶
fig = result.plot(
chart='S',
show_zones=True,
highlight_signals=True
)
fig.show()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¶
First check the S chart - Variation must be stable
Then interpret the Xbar chart - Only valid if S is stable
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:
# 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:
# 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']
# 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']
Chart Table Summary¶
Get a compact summary table for reporting:
# Summary table with subgroup info, values, and limits
table = result.chart_table('Xbar')
tableSummary¶
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¶
Stratified Analysis - Separate charts per factor level
VAS Residuals - Deep dive into VAS residuals
Signal Detection - All Western Electric rules