Process behavior charts answer “Is this process stable?” The Taguchi Loss Function (Bishop Chapter 15) answers a different question: “Where is the variation coming from, and how much does each source contribute to expected loss?”
It decomposes the expected squared loss into 5 components:
| Component | Label | What It Measures |
|---|---|---|
| Centering | MEAN | Loss from process mean being off-target |
| Unexplained | UNEXPLAINED | Within-cell variation (noise floor) |
| PDC | PDC | Variation due to Process Design Conditions (factor levels) |
| Time | PT | Variation due to Production Time (temporal drift) |
| Interaction | PDCxPT INT | Variation from PDC-by-time interaction (inconsistent factor effects over time) |
The Pareto ranking tells the analyst which source of variation to attack first for the greatest reduction in loss.
What You’ll Learn¶
Assess loss function decomposition with
study.loss_function(target=)Read and interpret the 5-component Pareto breakdown
Visualize unstructured (5-bar) and structured (7-bar) Pareto charts
Access PDC decomposition by individual factor
Verify the decomposition identity (5 components = total)
Understand when the loss function is unavailable
Setup¶
We use Bishop’s PBTESTDATABASE_T100 — a 4000-row validation dataset with 4 factor levels x 2 factor levels x 100 time periods, and response columns designed to exercise each Design State (DS).
from processbehavior import ProcessBehavior
# Load the PBTESTDATABASE_T100
pb = ProcessBehavior.read_csv('../../validation/PBTESTDATABASE_T100.csv')
print(f"Dataset: {pb.data.shape[0]} rows x {pb.data.shape[1]} columns")
print(f"Columns: {pb.data.columns.tolist()}")Found 11655 garbage/NA values across 5 column(s):
• PM SDS 2: 3200 values
• PM SDS 3: 958 values
• PM SDS 4: 2134 values
• PM SDS 5: 3359 values
• PM SDS 6: 2004 values
These values were converted to NA and will be excluded from analysis.
Dataset: 4000 rows x 11 columns
Columns: ['PRODUCTION TIME', 'FACTOR 1', 'FACTOR 2', 'FACTOR 1xFACTOR 2', 'PM SDS 1', 'PM SDS 2', 'PM SDS 3', 'PM SDS 4', 'PM SDS 5', 'PM SDS 6', 'PM INERT']
# Formulate a DS 1 study (full replication — has VAS residuals)
study = pb.formulate(
response='PM SDS 1',
factors=['FACTOR 1', 'FACTOR 2'],
time='PRODUCTION TIME',
precision=3
)
print(study)Study(response='PM SDS 1', factors=[FACTOR 1, FACTOR 2], time='PRODUCTION TIME', ads=1)
Valid: Histogram, Xbar, S, X, mR | Recommended: Xbar
Residuals: R1, R2, R3, R4, R5, R6
→ study.execute() or study.support for details
Basic Loss Assessment¶
Call study.loss_function(target=) to decompose expected loss relative to a target value. The target of 237 reproduces Tom’s reference output:
result = study.loss_function(target=237.0)
resultLossResult:
Target=237.0, Ybar=237.817, N=4000
K=8 PDC levels, T=100 periods, SDS=1
Expected Loss (EL) = 3.962
Loss Decomposition:
PDCxPT INT: 43.32%
UNEXPLAINED: 22.986%
MEAN: 16.841%
PDC: 14.17%
PT: 2.683%
PDC Decomposition:
FACTOR 1: 8.937%
FACTOR 2: 3.715%
PDF INT: 1.517%Reading the Output¶
The Pareto ranking tells you where to focus improvement efforts:
PDCxPT INT (largest) — factor effects are inconsistent over time. This is the dominant source of loss — the interaction between process design conditions and production time.
UNEXPLAINED — within-cell noise floor. This is the irreducible variation given the current measurement and process design.
MEAN — the process mean is off-target. Adjusting the process center toward T=237 would reduce this component.
PDC — variation due to factor levels. Some factor combinations produce systematically different results.
PT (smallest) — temporal drift is minimal. The process mean is relatively stable over time.
# Verify Tom's reference percentages (PM SDS 1, T=237)
print("Loss Decomposition (Tom's reference):")
print(f" PDCxPT INT: {result.pct_interaction:.1f}% (expected: 43.3%)")
print(f" UNEXPLAINED: {result.pct_unexplained:.1f}% (expected: 23.0%)")
print(f" MEAN: {result.pct_centering:.1f}% (expected: 16.8%)")
print(f" PDC: {result.pct_pdc:.1f}% (expected: 14.2%)")
print(f" PT: {result.pct_time:.1f}% (expected: 2.7%)")Loss Decomposition (Tom's reference):
PDCxPT INT: 43.3% (expected: 43.3%)
UNEXPLAINED: 23.0% (expected: 23.0%)
MEAN: 16.8% (expected: 16.8%)
PDC: 14.2% (expected: 14.2%)
PT: 2.7% (expected: 2.7%)
Visualizing: Unstructured Pareto¶
result.plot() produces a horizontal bar chart of the 5 components in Pareto order (Fig 15-4). The largest contributor to loss appears at the top:
result.plot().show()Visualizing: Structured Pareto¶
result.plot(structured=True) expands the PDC bar into its per-factor components (Fig 15-5). Instead of one “PDC” bar, you see:
F1, F2 — main effect loss for each factor
PDF INT — the factor interaction loss (PDC minus individual factor contributions)
This gives the analyst a more actionable view: which specific factor is contributing most to PDC loss?
result.plot(structured=True).show()Interpreting the Pareto¶
Read the Pareto top-down. The largest percentage is the biggest lever for improvement:
PDCxPT INT (43.3%) — the factor-by-time interaction dominates. Factor effects are not consistent over time. This is an assignable cause that warrants investigation.
UNEXPLAINED (23.0%) — the noise floor. Reducing this requires fundamentally changing the measurement or process.
MEAN (16.8%) — re-centering the process on the target would eliminate this component entirely.
F1 (8.9%) — FACTOR 1 main effect contributes more than FACTOR 2.
F2 (3.7%) — FACTOR 2 contributes less individually.
PT (2.7%) — temporal drift is small.
PDF INT (1.5%) — the interaction between the two factors is small.
PDC Decomposition¶
When there are multiple factors, PDC decomposes into per-factor main effect losses plus a factor interaction term. Access these via result.pdc_by_factor and result.pdc_factor_interaction:
# Per-factor PDC breakdown
print("PDC by factor:")
for factor, loss_val in result.pdc_by_factor.items():
pct = loss_val / result.total * 100
print(f" {factor}: {pct:.1f}%")
pdc_int_pct = result.pdc_factor_interaction / result.total * 100
print(f" PDF INT: {pdc_int_pct:.1f}%")
# Verify identity: F1 + F2 + PDF INT = PDC
pdc_sum = sum(result.pdc_by_factor.values()) + result.pdc_factor_interaction
print(f"\nF1 + F2 + PDF INT = {pdc_sum:.6f}")
print(f"PDC = {result.pdc:.6f}")
print(f"Match: {abs(pdc_sum - result.pdc) < 1e-10}")PDC by factor:
FACTOR 1: 8.9%
FACTOR 2: 3.7%
PDF INT: 1.5%
F1 + F2 + PDF INT = 0.561437
PDC = 0.561437
Match: True
Default Target¶
When no target is specified, the loss function defaults to the grand mean. This sets the centering component to zero — useful when you want to focus purely on variance decomposition:
result_default = study.loss_function()
print(f"Target: {result_default.target:.3f} (grand mean)")
print(f"Target is default: {result_default.target_is_default}")
print(f"Centering: {result_default.pct_centering:.1f}%")
print()
print("Without centering loss, the remaining 4 components")
print("share 100% of the total:")
print(f" UNEXPLAINED: {result_default.pct_unexplained:.1f}%")
print(f" PDC: {result_default.pct_pdc:.1f}%")
print(f" PT: {result_default.pct_time:.1f}%")
print(f" PDCxPT INT: {result_default.pct_interaction:.1f}%")Target: 237.817 (grand mean)
Target is default: True
Centering: 0.0%
Without centering loss, the remaining 4 components
share 100% of the total:
UNEXPLAINED: 27.6%
PDC: 17.0%
PT: 3.2%
PDCxPT INT: 52.1%
Decomposition Identity¶
The 5 components always sum exactly to the total expected loss. This is an algebraic identity, not an approximation:
# 5 components sum to total
component_sum = (
result.centering + result.unexplained + result.pdc +
result.time + result.interaction
)
print(f"Centering: {result.centering:.6f}")
print(f"Unexplained: {result.unexplained:.6f}")
print(f"PDC: {result.pdc:.6f}")
print(f"Time: {result.time:.6f}")
print(f"Interaction: {result.interaction:.6f}")
print(f"{'─' * 30}")
print(f"Sum: {component_sum:.6f}")
print(f"Total: {result.total:.6f}")
print(f"Match: {abs(component_sum - result.total) < 1e-10}")
print()
print(f"Percentages sum: {result.pct_centering + result.pct_unexplained + result.pct_pdc + result.pct_time + result.pct_interaction:.1f}%")Centering: 0.667270
Unexplained: 0.910775
PDC: 0.561437
Time: 0.106317
Interaction: 1.716448
──────────────────────────────
Sum: 3.962246
Total: 3.962246
Match: True
Percentages sum: 100.0%
The as_dict() Method¶
For programmatic use or export, as_dict() returns a rounded dictionary. You can override the rounding precision:
result.as_dict(round_to=4){'target': 237.0,
'target_is_default': False,
'y_bar': 237.8169,
'n': 4000,
'K': 8,
'T_periods': 100,
'sds': 1,
'centering': 0.6673,
'unexplained': 0.9108,
'pdc': 0.5614,
'time': 0.1063,
'interaction': 1.7164,
'total': 3.9622,
'pct_centering': 16.8407,
'pct_unexplained': 22.9863,
'pct_pdc': 14.1697,
'pct_time': 2.6832,
'pct_interaction': 43.3201,
'pdc_by_factor': {'FACTOR 1': 0.3541, 'FACTOR 2': 0.1472},
'pdc_factor_interaction': 0.0601}DS 2: ADS 2/3 Unexplained¶
DS 2 has singleton cells. The unexplained component uses the pooled R2 sigma (Eq 15.18: S_R2 / 0.7) instead of within-cell standard deviations:
# DS 2 — one observation per cell
study_sds2 = pb.formulate(
response='PM SDS 2',
factors=['FACTOR 1', 'FACTOR 2'],
time='PRODUCTION TIME',
precision=3
)
result_sds2 = study_sds2.loss_function(target=237.0)
print(f"DS: {result_sds2.sds}")
print(f"N={result_sds2.n}, K={result_sds2.K}, T={result_sds2.T_periods}")
print()
result_sds2DS: 2
N=800, K=8, T=100
LossResult:
Target=237.0, Ybar=237.782, N=800
K=8 PDC levels, T=100 periods, SDS=2
Expected Loss (EL) = 3.804
Loss Decomposition:
PDCxPT INT: 42.494%
UNEXPLAINED: 23.585%
MEAN: 16.093%
PDC: 15.58%
PT: 2.248%
PDC Decomposition:
FACTOR 1: 10.558%
FACTOR 2: 4.131%
PDF INT: 0.891%Single Factor¶
With only one factor, there is no factor interaction. pdc_factor_interaction is zero, and the structured plot shows the single factor bar instead of PDC:
# Single factor — no factor interaction
study_1f = pb.formulate(
response='PM SDS 1',
factors=['FACTOR 1'],
time='PRODUCTION TIME',
precision=3
)
result_1f = study_1f.loss_function(target=237.0)
print(f"PDC by factor: {result_1f.pdc_by_factor}")
print(f"Factor interaction: {result_1f.pdc_factor_interaction}")
print()
result_1fPDC by factor: {'FACTOR 1': 0.3541064951990689}
Factor interaction: 0.0
LossResult:
Target=237.0, Ybar=237.817, N=4000
K=4 PDC levels, T=100 periods, SDS=1
Expected Loss (EL) = 4.079
Loss Decomposition:
UNEXPLAINED: 63.325%
MEAN: 16.359%
PDCxPT INT: 9.028%
PDC: 8.681%
PT: 2.606%When Loss Function Is Unavailable¶
The loss function requires VAS residuals — which means the study must have both factors and time. Without time, VAS residuals are not computed, and loss_function() raises a ValidationError:
# Without time — no VAS residuals
study_no_time = pb.formulate(
response='PM SDS 1',
factors=['FACTOR 1', 'FACTOR 2'],
)
try:
study_no_time.loss_function(target=237.0)
except Exception as e:
print(f"{type(e).__name__}: {e}")ValidationError: Loss function analysis requires VAS residuals (factors + time design). Current design (SDS 1) does not have VAS residuals.
The Equations¶
For reference, here are the Bishop Chapter 15 equations implemented by loss_function():
Expected Loss Decomposition (Eq. 15.13):
Where the 5 terms are:
| Term | Component | Equation |
|---|---|---|
| Centering | Squared distance from grand mean to target | |
| Unexplained | Within-cell sigma squared (Eq. 15.16-15.19) | |
| PDC | Mean squared PDC effect (Eq. 15.20) | |
| Time | Mean squared time effect | |
| Interaction | Mean squared PDC x time interaction |
Unexplained — replicated cells (DS 1, Eq. 15.16/15.17):
Unexplained — ADS 2/3 (Eq. 15.18/15.19):
Effects:
Summary¶
| Concept | Key Point |
|---|---|
study.loss_function(target=) | Decompose expected loss — no re-formulation needed |
| 5 components | Centering, Unexplained, PDC, Time, Interaction |
| Pareto ranking | Largest % = biggest improvement lever |
result.plot() | Unstructured Pareto (5 bars) |
result.plot(structured=True) | Structured Pareto — PDC split into per-factor bars |
pdc_by_factor | Per-factor main effect losses (multi-factor) |
| Default target | Target = grand mean, centering = 0 |
| Identity | 5 components sum exactly to total |
| DS 2/3 | Unexplained uses pooled R2 sigma instead of within-cell |
| Requires | Factors + time (VAS residuals) |
Next Steps¶
Process Capability — assess capability against specification limits
DS Validation — see how DS affects available analyses
Complete DS 1 Analysis — full deep dive with VAS residuals