This tutorial demonstrates how to analyze a fully replicated factorial design (design state 1) using the by parameter to explore different views of the same data.
What You’ll Learn¶
Load and formulate a two-factor study
Use the
byparameter to aggregate Xbar/S charts at different levelsStratify X charts by factor combinations
Understand lane boundaries in collapsed charts
Chart residuals using the
valueparameter
Setup¶
from processbehavior import ProcessBehavior1. Load and Formulate¶
We’ll use the DS 1 validation dataset which has:
factor 1: 3 levels (F1_1, F1_2, F1_3)
factor 2: 2 levels (F2_1, F2_2)
time: 8 periods
Multiple replicates per cell
# Load the DS 1 validation data
pb = ProcessBehavior.read_csv('../../validation/sds1_data.csv')
print(f"Dataset: {pb.data.shape[0]} observations")
print(f"Factor 1 levels: {pb.data['factor 1'].unique().tolist()}")
print(f"Factor 2 levels: {pb.data['factor 2'].unique().tolist()}")
print(f"Time periods: {sorted(pb.data['time'].unique())}")
pb.data.head()Dataset: 161 observations
Factor 1 levels: ['F1_1', 'F1_2', 'F1_3']
Factor 2 levels: ['F2_1', 'F2_2']
Time periods: [np.int64(1), np.int64(2), np.int64(3), np.int64(4), np.int64(5), np.int64(6), np.int64(7), np.int64(8)]
# Formulate the study with both factors
study = pb.formulate(
response='y',
factors=['factor 1', 'factor 2'],
time='time'
)
print(f"ADS: {study.analytical_design_state.sds} ({study.ads_reason})")
print(f"Valid charts: {study.valid_charts}")
print(f"Residual charts: {study.residual_charts}")ADS: 1 (full_replication)
Valid charts: ['Histogram', 'Xbar', 'S', 'X', 'mR']
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')]
1.5 Quick Distribution Check: Histogram¶
Before diving into control charts, you can visualize the distribution of your response variable using a histogram.
# Histogram of response variable
study.execute(chart='Histogram', bins=15).plot(theme='ggplot').show()study.execute(chart='Histogram', by=['factor 1'], bins=25).plot(theme='dark',show_zones=True).show()
You can also:
Customize bins:
study.execute(chart='Histogram', bins=20)Stratify by factors:
study.execute(chart='Histogram', by=['factor 1'])Plot residual distributions:
study.execute(chart='Histogram', value='R5')
2. Xbar Charts - Factor Aggregation¶
The by parameter controls how data points are aggregated on Xbar charts:
Default (all factors): One point per factor combination (6 points)
Single factor: Aggregate across the other factor
Empty list: Collapse to grand mean (1 point)
2.1 Xbar by All Factors (Default)¶
# Default: aggregate by all factors
result = study.execute(chart='Xbar')
print("Xbar chart data (one point per factor combination):")
result.get_chart('Xbar')Xbar chart data (one point per factor combination):
result.plot(chart='Xbar', show_stats=True).show()2.2 Xbar by Factor 1 Only¶
# Aggregate by factor 1 only (3 points, one per F1 level)
result_f1 = study.execute(chart='Xbar', by=['factor 1'])
print("Xbar aggregated by factor 1:")
result_f1.get_chart('Xbar')Xbar aggregated by factor 1:
result_f1.plot(chart='Xbar', show_stats=True).show()2.3 Xbar by Factor 2 Only¶
# Aggregate by factor 2 only (2 points, one per F2 level)
result_f2 = study.execute(chart='Xbar', by=['factor 2'],)
print("Xbar aggregated by factor 2:")
result_f2.get_chart('Xbar')Xbar aggregated by factor 2:
result_f2.plot(chart='Xbar', show_stats=True).show()2.4 Xbar Collapsed (Grand Mean)¶
# Collapse all factors (single point - grand mean)
result_all = study.execute(chart='Xbar', by=[])
print("Xbar collapsed to grand mean:")
result_all.get_chart('Xbar')
result_all.plot('Xbar').show()Xbar collapsed to grand mean:
3. S Charts - Variation Analysis¶
S charts follow the same by parameter logic as Xbar charts.
# S chart by all factors (default)
result_s = study.execute(chart='S')
print("S chart (within-group standard deviation):")
result_s.get_chart('S')S chart (within-group standard deviation):
result_s.plot(chart='S', show_stats=True).show()# S chart by factor 1 only
result_s_f1 = study.execute(chart='S', by=['factor 1'])
print("S chart aggregated by factor 1:")
result_s_f1.get_chart('S')S chart aggregated by factor 1:
# S chart by factor 2 only
result_s_f2 = study.execute(chart='S', by=['factor 2'])
print("S chart aggregated by factor 2:")
result_s_f2.get_chart('S')S chart aggregated by factor 2:
4. X Charts - Stratified Analysis¶
X (individuals) charts with factors require an explicit by parameter. The by parameter controls stratification:
Both factors: Separate chart for each factor combination
Single factor: Charts per level with lane boundaries showing the other factor
Empty list: Single chart with lane boundaries for all factor transitions
4.1 X by Both Factors (6 Faceted Charts)¶
# X stratified by both factors
result_imr = study.execute(chart='X', by=['factor 1', 'factor 2'])
print(f"Strata: {result_imr.charts['X']['strata']}")
print(f"Each stratum has its own X chart")Strata: ['F1_1_F2_1', 'F1_1_F2_2', 'F1_2_F2_1', 'F1_2_F2_2', 'F1_3_F2_1', 'F1_3_F2_2']
Each stratum has its own X chart
result_imr.plot(chart='X', show_zones=True).show()4.2 X by Factor 1 Only (3 Charts with Lane Boundaries)¶
When stratifying by one factor, the collapsed factor creates multiple observations at each time point. Lane boundaries show where the collapsed factor changes.
# X stratified by factor 1 only
result_imr_f1 = study.execute(chart='X', by=['factor 1'])
print(f"Strata: {result_imr_f1.charts['X']['strata']}")
print("\nLane boundaries show where factor 2 changes within each chart")Strata: ['F1_1', 'F1_2', 'F1_3']
Lane boundaries show where factor 2 changes within each chart
result_imr_f1.plot(chart='X', show_zones=True).show()4.3 X by Factor 2 Only (2 Charts with Lane Boundaries)¶
# X stratified by factor 2 only
result_imr_f2 = study.execute(chart='X', by=['factor 2'])
print(f"Strata: {result_imr_f2.charts['X']['strata']}")
print("\nLane boundaries show where factor 1 changes within each chart")Strata: ['F2_1', 'F2_2']
Lane boundaries show where factor 1 changes within each chart
result_imr_f2.plot(chart='X', show_zones=True).show()4.4 Single X Chart (Collapsed, with Lane Boundaries)¶
# Single X chart with all factors collapsed
result_imr_all = study.execute(chart='X', by=[])
print("Single X chart with all data")
print("Lane boundaries show transitions between factor combinations")Single X chart with all data
Lane boundaries show transitions between factor combinations
result_imr_all.plot(chart='X', show_zones=True).show()5. Residual Charts¶
Use the value parameter to chart VAS residuals instead of the response variable.
5.1 R5 (Design Condition Main Effects) on Xbar¶
# R5 residuals show factor effects
result_r5 = study.execute(chart='Xbar', value='R5')
print("R5 Xbar chart (factor effects):")
result_r5.get_chart('Xbar')R5 Xbar chart (factor effects):
result_r5.plot(chart='Xbar', show_stats=True).show()5.2 Recentered Residuals¶
Use recentered=True to center residuals around zero.
# Recentered R5 residuals
result_r5_rc = study.execute(chart='Xbar', value='R5', recentered=True)
print("Recentered R5 Xbar chart:")
result_r5_rc.get_chart('Xbar')Recentered R5 Xbar chart:
result_r5_rc.plot(chart='Xbar', show_stats=True).show()5.3 R5 on S Chart¶
# R5 on S chart
study.execute(chart='S', value='R5', recentered=True).plot().show()6. Study Inspection¶
Before executing charts, you can inspect what the study supports. The study.support property shows all chart types, their availability, and the analytical question each answers.
# Full chart support matrix
study.support# Filter to available charts only
study.support[study.support['available']]# Understand why a specific (chart, residual) pair is unavailable
print(study.why_not('S', value='R1'))'S' with value='R1' is not valid for ADS 1.
Valid charts for R1: Xbar, X
Design Report¶
The study.design() method returns a DesignReport showing the structure of your study -- K (factor groups), T (time points), R (total cells), and N (observations per cell).
# Design report -- observed structure (no plan specified)
report = study.design()
print(report)Design Report (2 factors)
Design-state lineage:
PDS (Planned): no plan supplied
SDS (Sampling): 1 (Full Replication)
ADS (Analytical): 1 (Full Replication)
Min cell size: 2 | K: 6 | T: 8 | R: 48 | N: (min=2, median=3.0, max=5)
Factors:
factor 1: observed=['F1_1', 'F1_2', 'F1_3']
factor 2: observed=['F2_1', 'F2_2']
Structure: Complete structure
Available analyses (ADS 1):
Primary: Histogram, Xbar *, S, X, mR
R2: S, X
R3: Xbar, S
R4: Xbar, S
R5: Xbar, S
R6: Xbar, S
Methods: Capability, Loss Function, Maximum Information
7. Companion Charts¶
Wheeler recommends reading certain charts as pairs -- the variation chart first (S or R), then the location chart (Xbar or X). The companion=True parameter returns both charts in a single result.
Reading order:
Check the S chart -- is within-group variation stable?
Then read the Xbar chart -- only meaningful if S is stable
# Companion Xbar+S -- returns both charts in one result
result_paired = study.execute(chart='Xbar', companion=True)
print(f"Charts in companion result: {result_paired.all_charts}")
# Plot Xbar
result_paired.plot(chart='Xbar', show_stats=True).show()Charts in companion result: ['Xbar', 'S']
# Plot the companion S chart -- read this first
result_paired.plot(chart='S', show_stats=True).show()# Companion X+mR stratified by factor 1
result_paired_imr = study.execute(chart='X', by=['factor 1'], companion=True)
print(f"Charts in companion result: {result_paired_imr.all_charts}")
# Plot X
result_paired_imr.plot(chart='X', show_zones=True).show()Charts in companion result: ['X', 'mR']
# Plot the companion mR chart
result_paired_imr.plot(chart='mR', show_zones=True).show()8. Effects & Interaction Charts¶
When your study has factors (and optionally time), you can visualize main effects and interactions directly from the analysis result. These charts help answer: Which factors matter, and do they interact?
There are five effects chart types:
| Chart | What It Shows |
|---|---|
Effects | All main effects (factor + time) combined |
MainEffects | Factor main effects only |
TimeEffects | Time main effects only |
TimeInteraction | Factor x time interaction |
FactorInteraction | Factor x factor interaction |
# All main effects combined (factor + time)
result.plot(chart='Effects').show()# Factor main effects only
result.plot(chart='MainEffects').show()# Time effects only
result.plot(chart='TimeEffects').show()# Factor x time interaction
result.plot(chart='TimeInteraction').show()# Factor x factor interaction (requires 2+ factors)
result.plot(chart='FactorInteraction').show()Accessing Raw Effects Data¶
You can also access the underlying effects and interactions data programmatically.
# Raw effects data
print(f"Has effects: {result.has_effects}")
print(f"Has interactions: {result.has_interactions}")
print("\nEffects keys:", list(result.effects.keys()))
print("Interactions keys:", list(result.interactions.keys()))Has effects: True
Has interactions: True
Effects keys: ['factor 1', 'factor 2', 'main_effect', 'time', 'factor 1_MEs', 'factor 2_MEs', 'factor_interaction_effects']
Interactions keys: ['factor_time', 'factor_factor']
Summary¶
When to Use Each by Configuration¶
| Configuration | Use Case |
|---|---|
by=None (default) | Compare all factor combinations |
by=['factor 1'] | Focus on one factor, aggregate the other |
by=[] | Overall process view, collapsed factors |
by=['factor 1', 'factor 2'] | Individual charts per combination |
Key Concepts¶
Views, Not Recomputation: The
byparameter creates views over the same underlying data. Residuals never change.Lane Boundaries: When X charts collapse factors, vertical lane boundaries show where factor transitions occur.
Residuals via
value: Usevalue='R5'to chart residuals instead of response.Recentering: Plain residuals center around zero;
recentered=Trueadds the grand mean back (RCR*), putting the chart on the response scale.Study Inspection: Use
study.supportto see all available charts,study.why_not()to understand constraints, andstudy.design()to inspect the K/T/N/R structure.Companion Charts: Use
companion=Trueto get both location and variation charts together (Xbar+S or X+mR). Read the variation chart first.Effects Charts: Use
result.plot(chart='Effects')and related chart types to visualize factor and time effects. Requires a study with factors.