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.

Chart Types

ProcessBehavior supports multiple chart types, each suited for different data structures and analytical questions. This guide helps you choose the right chart.

Available Chart Types

Standard Charts

ChartFull NamePurposeRequirements
XbarX-bar ChartMonitor subgroup meansn >= 2 per subgroup
SS ChartMonitor subgroup variationn >= 2 per subgroup
XX (Individual) ChartMonitor individual valuesAny structure
mRmR (Moving Range) ChartMonitor individual moving rangesAny structure

VAS Residual Charts

Use the value parameter to chart residuals instead of the response:

ResidualChartPurpose
R2S or XCheck that the unexplained noise is stable
R3Xbar, S, or XDetect factor × time interactions
R4Xbar, S, or XDetect time effects
R5Xbar, S, or XDetect factor effects
# Chart R5 residuals on Xbar
result = study.execute(chart='Xbar', value='R5')

# Chart R4 residuals on stratified X
result = study.execute(chart='X', by=['lane'], value='R4')

The by Parameter

The by parameter controls grouping and stratification:

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

# Xbar/S - aggregate by different levels
result = study.execute(chart='Xbar')                  # By all factors (default)
result = study.execute(chart='Xbar', by=['lane'])     # By single factor
result = study.execute(chart='Xbar', by=[])           # Collapse to grand mean

# X - stratify by factor(s)
result = study.execute(chart='X', by=['lane'])      # One chart per lane

Use study.valid_charts and study.residuals to see options.

Chart Selection Guide

Question: “Are my subgroups different from each other?”

Use: Xbar Chart

result = study.execute(chart='Xbar')
fig = result.plot(chart='Xbar', show_zones=True, highlight_signals=True)

Points beyond limits indicate subgroups that differ from the overall average.

Question: “Is within-group variation stable?”

Use: S Chart

result = study.execute(chart='S')
fig = result.plot(chart='S', show_zones=True)

Points beyond limits indicate subgroups with unusual variation.

Question: “How is my process behaving over time?”

Use: X Chart

result = study.execute(chart='X', by=['lane'])
fig = result.plot(show_zones=True, show_rules=True)

For stratified data, this creates one X chart per factor level.

Question: “Do factor effects change over time?”

Use: X with value='R3' (Interactions)

result = study.execute(chart='X', by=['lane'], value='R3')
fig = result.plot(title='Factor × Time Interactions')

Signals indicate that factor effects are not consistent over time.

Use: X with value='R4'

result = study.execute(chart='X', by=['lane'], value='R4')
fig = result.plot(title='Time Effects')

Signals indicate process drift or shifts over time.

Question: “Are there design condition differences after removing time effects?”

Use: X with value='R5'

result = study.execute(chart='X', by=['lane'], value='R5')
fig = result.plot(title='Design Condition Main Effects')

Signals indicate true differences between process design conditions.

Chart Validity by DS

Not all charts are valid for all Design States:

ChartDS 1DS 2DS 3DS 4DS 5-6
Xbar✅✅✅✅✅
S✅✅✅✅✅
X✅✅✅✅✅
mR✅✅✅✅✅

DS 4–6 collapse to DS 1–3 once empty cells are dropped, and their charts follow (Xbar is recommended for DS 1, 3, 4 and 6; X for DS 2 and 5).

Residual availability depends on DS. Use study.residuals to check:

print(f"Valid charts: {study.valid_charts}")
print(f"Available residuals: {study.residuals}")

Note on R2: DS 1 and 4 (every cell replicated) use the within-cell deviation, R2 = Y - Ȳ_kt. DS 2, 3, 5 and 6 (any one-observation cell) remove the condition and period effects, difference what is left along the condition-then-time sequence, and divide by twice the R2 scale factor. See VAS Residuals for details.

Companion Charts

Wheeler recommends reading certain charts as pairs: Xbar with S, and X with mR. The companion parameter returns both charts in one result:

# Returns both Xbar and S charts
result = study.execute(chart='Xbar', companion=True)
result.plot(chart='Xbar')  # Xbar chart
result.plot(chart='S')     # S chart

# Returns both X and mR charts, stratified
result = study.execute(chart='X', by=['lane'], companion=True)

Either chart in the pair triggers the pair: chart='S', companion=True also returns Xbar+S.

Effects and Interaction Charts

When your study has factors, ProcessBehavior can visualize main effects and interactions. These charts help answer: Are the factor and time effects practically significant?

ChartWhat It ShowsRequirements
EffectsAll main effects (factor + time) combinedFactors and time
MainEffectsFactor main effects onlyFactors
TimeEffectsTime main effects onlyTime
TimeInteractionFactor x time interactionFactors and time
FactorInteractionFactor x factor interaction2+ factors
result = study.execute(chart='Xbar')

# All main effects combined
result.plot(chart='Effects')

# Factor effects only
result.plot(chart='MainEffects')

# Time effects only
result.plot(chart='TimeEffects')

# Factor x time interaction
result.plot(chart='TimeInteraction')

# Factor x factor interaction (requires 2+ factors)
result.plot(chart='FactorInteraction')

Effects charts require result.has_effects == True (i.e., the study must have factors). Interaction charts require the corresponding dimensions (factors + time for TimeInteraction, 2+ factors for FactorInteraction).

You can also access the raw effects data:

# Keys are named after your own factor columns, plus 'main_effect' and 'time'.
# For factors=['machine', 'shift']:
result.effects['machine']   # per-level main effects  (column: Main_Effect)
result.effects['time']      # time main effects       (column: PT_ME)
result.effects              # see AnalysisResult.effects for the full key list
result.interactions         # Dict of interaction terms

Understanding Xbar-S Charts

The Xbar Chart

Plots the mean of each subgroup (factor level at each time point).

The S Chart

Plots the standard deviation of each subgroup.

Reading Order

  1. First check S chart - Variation must be stable

  2. Then interpret Xbar - Only meaningful if S is stable

  3. Investigate signals - What makes that subgroup different?

Staged Xbar/S

A staged study records the stage as a factor, and the default Xbar chart already lays the subgroups out stage by stage: every subgroup of stage A, then every subgroup of stage B, each in time order. phased=True gives each stage its own center line and limits on that one chart, drawn as stepped lines with a divider at each stage change.

study = pb.formulate(df, response='Thickness', time='Hour', factors=['Cavity'])
staged = study.execute(chart='Xbar', phased=True, companion=True)
staged.plot(chart='Xbar')
staged.phase_statistics('Xbar')   # one row per stage: center, S-bar, sigma-hat, limits

Each stage is computed as a separate analysis, exactly as by=['Hour'] computes one chart per stage: its center is the unweighted mean of its subgroup means, its S̄ is the mean of its subgroup standard deviations, and its σ̂ and limits follow from those (10-12 manual Eqs 11-5, 11-8, 11-9, 11-16, 11-17). Nothing is pooled across stages. On Wheeler’s four-cavity thickness data the per-cavity centers and limits match Minitab’s staged Xbar-S chart to the digit; the per-cavity charts of by=['Hour'] give the same numbers, one chart at a time.

With more factors than the stage, by=['Cavity', 'Hour'] pools the others into the subgroup and phases on Cavity. The center is then the mean of the pooled subgroup averages, per Dr. Bishop: ignore the other factor completely, group by stage and time, and chart per the usual rules. That equals charting a study formulated without the pooled factors.

What the phased chart reports:

Stages are ordered as the factor’s levels sort, so label them to sort (1 before, 2 after, or an ordered pandas Categorical). by=[time] with phased=True is refused: that view is already one chart per stage. Residual values and calibrations are refused on phased Xbar/S.

Understanding X and mR Charts

The X (Individual) Chart

Plots each individual observation.

The mR (Moving Range) Chart

Plots the absolute difference between consecutive observations.

Stratified X Charts

When you have factors and time, use the by parameter to stratify X charts:

# Stratify by lane - creates one chart per lane
result = study.execute(chart='X', by=['lane'])

# Check the strata
print(result.charts['X']['strata'])  # ['A', 'B', 'C', 'D']

Each stratum has its own control limits based on its internal variation:

# View faceted plot with all lanes
fig = result.plot(chart='X', show_zones=True)

Lane Boundaries

When you collapse factors (use fewer factors in by than exist in the study), lane boundaries show where the collapsed factors change:

# Single X chart with lane boundaries
result = study.execute(chart='X', by=[])  # Collapse all factors
fig = result.plot(chart='X')  # Vertical lines show factor transitions

Three Views of the Same Study

With factors and time, the same observations can be charted three ways. Each answers a different question, and the library gives you the question rather than one chart with one set of limits.

1. Is this one process? Every subgroup on one chart, one center line and one set of limits across the whole study. Points are ordered subgroup first, then time within subgroup, so each subgroup occupies its own lane at a common scale and the same pattern can be read lane by lane. The moving range runs across the whole sequence in that order, including the step from the last point of one subgroup to the first point of the next.

combined = study.execute(chart='X', by=[], companion=True)
combined.plot(chart='X')

2. How does each subgroup behave, side by side? The same lanes, each with its own center line and limits computed from its own moving ranges. The transitions between subgroups no longer enter the calculation.

phased = study.execute(chart='X', by=[], phased=True, companion=True)
phased.plot(chart='X')

The same view exists for subgrouped data: study.execute(chart='Xbar', phased=True, companion=True) gives each factor combination its own center and limits on one Xbar/S chart (see Staged Xbar/S).

3. One subgroup at a time. Full stratification: one chart per subgroup, each with its own limits.

per_lane = study.execute(chart='X', by=['lane'], companion=True)
per_lane.plot(chart='X', facet=True, ncols=2)

Reading a Boundary Signal

On the combined chart, the moving range at a subgroup transition is the difference between one subgroup’s last observation and the next subgroup’s first. A signal there is evidence about the traversal: those two subgroups, in that order, do not join into one stable stream. It is not a pairwise property of the two subgroups. Reorder the lanes and different transitions are flagged, even though the overall conclusion, one process or not, survives any ordering. On a trending process the boundary step also resets time, from the last period of one lane to the first period of the next, so it is systematically larger than a plain difference in level.

The transitions stay in the calculation deliberately. That is how Bishop computes the combined chart, and it is what reproduces his reference limits (see Validation). The connecting line is not drawn across a boundary, so the eye is not led to read the step as a movement within a subgroup.

Which Claims Are Order-Free

Different charts make different claims, and not all of them survive a change in lane order. validation/mr_permutation_invariance.py holds every observation fixed, shuffles only the lane order, and reports which results move.

ClaimChartSurvives reordering the lanes?
“These subgroups are not one process”chart='X', by=[]Yes. The signal count never reaches zero under any ordering tested.
“This transition is a signal”chart='mR', by=[]No. Which transitions are flagged depends on which lanes are adjacent.
“Subgroup k differs in level”chart='Xbar', by=['lane'], value='R5', recentered=TrueThe points, yes: each is a subgroup mean. The limits, not entirely in DS 2 and 3: they are set from R2, which differences the condition-and-period-adjusted series along the same lane-major sequence. The level difference between lanes no longer enters R2, but which observations are paired still depends on lane order. In DS 1, R2 is within-cell and carries no order.
“Subgroup k is stable on its own”phased=True, or by=['lane']Yes. Only that subgroup’s own ranges enter.
“Stage k is stable on its own” (subgrouped data)chart='Xbar', phased=TrueYes. Only that stage’s own subgroups enter its center, S̄ and limits.

So a boundary signal should be read as evidence along the path, and claims about the subgroups themselves should come from the charts that have no path: the R5 chart for which subgroups differ in level, and the phased or stratified view for how each behaves. This was worked out with a contributor on issue #114, whose permutation check is the script above.

Re-centered Residual Charts

By default, residual charts are centered at zero. Use recentered=True to show residuals on the original measurement scale:

# Zero-centered (default)
result = study.execute(chart='X', by=['lane'], value='R4')

# Re-centered on original scale
result = study.execute(chart='X', by=['lane'], value='R4', recentered=True)

Re-centering adds back the grand mean, RCR = R + Y̅ (Bishop’s 10-12 manual, Eq 14-26, for R1 and R3–R6), so a re-centered chart plots the effect plus R2 on the measurement scale. The chart shifts by Y̅; the limit width is unchanged. RCR2 is the exception: Y̅_kt + R2. Chart period effects from R4; an Xbar of RCR3 by period is flat, because the interaction sums to zero over conditions.

Decision Tree

Do you have factors?
├── No → Do you have time?
│   └── Yes → How many observations per time point?
│       ├── Two or more at every point → DS 1: Xbar-S
│       ├── One at some points, more at others → DS 3: Xbar-S (recommended)
│       └── One at every point → DS 2: X
└── Yes → Do you have time?
    ├── No → Use Xbar to compare factors
    └── Yes → Do you have replication (n>=2 per cell)?
        ├── All cells → DS 1: Full Xbar-S + VAS residuals (R2 within-cell)
        ├── Some cells → DS 3: Xbar-S (recommended) + VAS residuals (R2 by scaled difference)
        └── No cells → DS 2: X (recommended) + VAS residuals (R2 by scaled difference)

VAS residuals (R1–R6) need both factors and time.

Summary

QuestionChartSignal Meaning
Is this one process?chart='X', by=[], companion=TrueSubgroups do not join into one stable stream
Each subgroup on its own?chart='X', by=[], phased=TrueSpecial cause within that subgroup
Each stage on its own, with subgroups?chart='Xbar', phased=True, companion=TrueSpecial cause within that stage
Are groups different?chart='Xbar'Group deviates from average
Is variation stable?chart='S'Group has unusual variation
Process over time?chart='X', by=[...]Special cause detected
Interactions?chart='X', value='R3'Factor effect changes over time
Time trends?chart='X', value='R4'Process drift/shift
Factor effects?chart='Xbar', value='R5'True factor differences

Next Steps