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.

Taguchi Loss Function Analysis

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:

ComponentLabelWhat It Measures
CenteringMEANLoss from process mean being off-target
UnexplainedUNEXPLAINEDWithin-cell variation (noise floor)
PDCPDCVariation due to Process Design Conditions (factor levels)
TimePTVariation due to Production Time (temporal drift)
InteractionPDCxPT INTVariation 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

  1. Assess loss function decomposition with study.loss_function(target=)

  2. Read and interpret the 5-component Pareto breakdown

  3. Visualize unstructured (5-bar) and structured (7-bar) Pareto charts

  4. Access PDC decomposition by individual factor

  5. Verify the decomposition identity (5 components = total)

  6. 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)
result
LossResult: 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()
Loading...

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

Interpreting the Pareto

Read the Pareto top-down. The largest percentage is the biggest lever for improvement:

  1. 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.

  2. UNEXPLAINED (23.0%) — the noise floor. Reducing this requires fundamentally changing the measurement or process.

  3. MEAN (16.8%) — re-centering the process on the target would eliminate this component entirely.

  4. F1 (8.9%) — FACTOR 1 main effect contributes more than FACTOR 2.

  5. F2 (3.7%) — FACTOR 2 contributes less individually.

  6. PT (2.7%) — temporal drift is small.

  7. 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_sds2
DS: 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_1f
PDC 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):

EL=(Yˉ..T)2+σ^w2+1Kkρk2+1Ttτt2+1KTk,t(ρτ)kt2EL = (\bar{Y}_{..} - T)^2 + \hat{\sigma}^2_w + \frac{1}{K}\sum_k \rho_k^2 + \frac{1}{T}\sum_t \tau_t^2 + \frac{1}{KT}\sum_{k,t} (\rho\tau)_{kt}^2

Where the 5 terms are:

TermComponentEquation
(Yˉ..T)2(\bar{Y}_{..} - T)^2CenteringSquared distance from grand mean to target
σ^w2\hat{\sigma}^2_wUnexplainedWithin-cell sigma squared (Eq. 15.16-15.19)
1Kkρk2\frac{1}{K}\sum_k \rho_k^2PDCMean squared PDC effect (Eq. 15.20)
1Ttτt2\frac{1}{T}\sum_t \tau_t^2TimeMean squared time effect
1KTk,t(ρτ)kt2\frac{1}{KT}\sum_{k,t} (\rho\tau)_{kt}^2InteractionMean squared PDC x time interaction

Unexplained — replicated cells (DS 1, Eq. 15.16/15.17):

σ^w2=1KTk,t(Sktc4(nkt))2\hat{\sigma}^2_w = \frac{1}{KT}\sum_{k,t} \left(\frac{S_{kt}}{c_4(n_{kt})}\right)^2

Unexplained — ADS 2/3 (Eq. 15.18/15.19):

σ^w2=(SR20.7)2\hat{\sigma}^2_w = \left(\frac{S_{R2}}{0.7}\right)^2

Effects:

ρk=Yˉk.Yˉ..τt=Yˉ.tYˉ..(ρτ)kt=YˉktYˉk.Yˉ.t+Yˉ..\rho_k = \bar{Y}_{k.} - \bar{Y}_{..} \qquad \tau_t = \bar{Y}_{.t} - \bar{Y}_{..} \qquad (\rho\tau)_{kt} = \bar{Y}_{kt} - \bar{Y}_{k.} - \bar{Y}_{.t} + \bar{Y}_{..}

Summary

ConceptKey Point
study.loss_function(target=)Decompose expected loss — no re-formulation needed
5 componentsCentering, Unexplained, PDC, Time, Interaction
Pareto rankingLargest % = biggest improvement lever
result.plot()Unstructured Pareto (5 bars)
result.plot(structured=True)Structured Pareto — PDC split into per-factor bars
pdc_by_factorPer-factor main effect losses (multi-factor)
Default targetTarget = grand mean, centering = 0
Identity5 components sum exactly to total
DS 2/3Unexplained uses pooled R2 sigma instead of within-cell
RequiresFactors + time (VAS residuals)

Next Steps