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.

Complete Design-State-1 Analysis: Exploring Design Condition Main Effects

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

  1. Load and formulate a two-factor study

  2. Use the by parameter to aggregate Xbar/S charts at different levels

  3. Stratify X charts by factor combinations

  4. Understand lane boundaries in collapsed charts

  5. Chart residuals using the value parameter

Setup

from processbehavior import ProcessBehavior

1. 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)]
Loading...
# 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()
Loading...
study.execute(chart='Histogram', by=['factor 1'], bins=25).plot(theme='dark',show_zones=True).show()
Loading...

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):
Loading...
result.plot(chart='Xbar', show_stats=True).show()
Loading...

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:
Loading...
result_f1.plot(chart='Xbar', show_stats=True).show()
Loading...

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:
Loading...
result_f2.plot(chart='Xbar', show_stats=True).show()
Loading...

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:
Loading...

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):
Loading...
result_s.plot(chart='S', show_stats=True).show()
Loading...
# 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:
Loading...
# 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:
Loading...

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()
Loading...

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()
Loading...

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()
Loading...

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()
Loading...

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):
Loading...
result_r5.plot(chart='Xbar', show_stats=True).show()
Loading...

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:
Loading...
result_r5_rc.plot(chart='Xbar', show_stats=True).show()
Loading...

5.3 R5 on S Chart

# R5 on S chart

study.execute(chart='S', value='R5', recentered=True).plot().show()
Loading...

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
Loading...
# Filter to available charts only
study.support[study.support['available']]
Loading...
# 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:

  1. Check the S chart -- is within-group variation stable?

  2. 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']
Loading...
# Plot the companion S chart -- read this first
result_paired.plot(chart='S', show_stats=True).show()
Loading...
# 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']
Loading...
# Plot the companion mR chart
result_paired_imr.plot(chart='mR', show_zones=True).show()
Loading...

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:

ChartWhat It Shows
EffectsAll main effects (factor + time) combined
MainEffectsFactor main effects only
TimeEffectsTime main effects only
TimeInteractionFactor x time interaction
FactorInteractionFactor x factor interaction
# All main effects combined (factor + time)
result.plot(chart='Effects').show()
Loading...
# Factor main effects only
result.plot(chart='MainEffects').show()
Loading...
# Time effects only
result.plot(chart='TimeEffects').show()
Loading...
# Factor x time interaction
result.plot(chart='TimeInteraction').show()
Loading...
# Factor x factor interaction (requires 2+ factors)
result.plot(chart='FactorInteraction').show()
Loading...

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

ConfigurationUse 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

  1. Views, Not Recomputation: The by parameter creates views over the same underlying data. Residuals never change.

  2. Lane Boundaries: When X charts collapse factors, vertical lane boundaries show where factor transitions occur.

  3. Residuals via value: Use value='R5' to chart residuals instead of response.

  4. Recentering: Plain residuals center around zero; recentered=True adds the grand mean back (RCR*), putting the chart on the response scale.

  5. Study Inspection: Use study.support to see all available charts, study.why_not() to understand constraints, and study.design() to inspect the K/T/N/R structure.

  6. Companion Charts: Use companion=True to get both location and variation charts together (Xbar+S or X+mR). Read the variation chart first.

  7. Effects Charts: Use result.plot(chart='Effects') and related chart types to visualize factor and time effects. Requires a study with factors.