# Extract Term-Level Contributions

A GAM prediction is an intercept plus a sum of smooth functions. `predict(type="terms")` exposes each smooth's contribution separately, so you can see how much each predictor is adding to the linear predictor at any covariate value. This is useful for explaining which variable is driving a high or low fitted value.


# Fit

Fit a two-smooth Gaussian GAM on the wages dataset.


``` python
# Import libraries
import whittaker as wk
import numpy as np

# Load data and fit two-smooth GAM
data = wk.load_dataset("wages")
model = wk.GAM("wage ~ s(age) + s(experience)").fit(data)
model.summary()
```


    GAM fit summary
    ============================================================
    Formula:    wage ~ s(age) + s(experience)
    Family:     Gaussian(link='identity')
    Inference:  GCV
    Observations: 800
    Coefficients: 19

    Parametric coefficients:
      Term                       Estimate    Std.Err    t value    p-value
      ------------------------ ---------- ---------- ---------- ----------
      (Intercept)                 45.2167     0.4174    108.341    < 1e-16

    Approximate significance of smooth terms:
      Term                        EDF Ref.df     Chi.sq    p-value
      ------------------------ ------ ------ ---------- ----------
      s(age)                     4.03      5   1363.371    < 1e-16
      s(experience)              3.16      4    184.291    < 1e-16

    Total EDF:  8.19
    Scale est:  139.348765
    Deviance:   110337.5740
    Null dev:   374733.5230
    Dev. expl:  70.6%
    GCV score:  140.790323
    AIC:        6228.08
    BIC:        6266.45


# Extract term contributions

Pass `type="terms"` to `predict()`. The return type is [TermsPredictionResult](../reference/TermsPredictionResult.md#whittaker.TermsPredictionResult) with a `.terms` dict and a `.labels` list.


``` python
# Define new observations
new_data = {"age": np.array([25, 35, 45, 55]), "experience": np.array([2, 10, 18, 28])}
# Extract term-level contributions
preds_terms = model.predict(new_data, type="terms")
preds_terms.labels
```


    ['s(age)', 's(experience)']


The labels show the exact term names used as keys in `.terms` (they match the formula notation).


# Inspect each smooth's contribution

Access each term's contribution array using its label.


``` python
preds_terms.terms["s(age)"].round(3)
```


    array([-21.521,  -9.109,   5.311,  14.86 ])


``` python
preds_terms.terms["s(experience)"].round(3)
```


    array([-4.84 ,  4.884, 12.298, 15.613])


Each value is the smooth's contribution to the linear predictor at the corresponding row in `new_data`, centered so that it integrates to zero over the data. A large positive value means that covariate value pushes the prediction upward.


# Sum of contributions

The `.values` property sums all term contributions, giving the linear predictor minus the intercept.


``` python
preds_terms.values.round(3)
```


    array([-26.361,  -4.225,  17.609,  30.473])


To compare with the full link-scale prediction (which adds the intercept), call `predict()` with `type="link"`.


``` python
# Predict on the link scale
link_preds = model.predict(new_data, type="link")
link_preds.values.round(3)
```


    array([18.856, 40.992, 62.825, 75.69 ])


The difference between the two is the intercept: a single scalar added to every prediction.


``` python
(link_preds.values - preds_terms.values).round(4)
```


    array([45.2167, 45.2167, 45.2167, 45.2167])


The constant difference confirms the identity: `link prediction = intercept + sum(term contributions)`.


# Compare contributions across observations

Stack the two smooth contributions side by side to see which predictor dominates at each point.


``` python
# Stack both smooth contributions side by side
np.column_stack([
    preds_terms.terms["s(age)"].round(3),
    preds_terms.terms["s(experience)"].round(3),
])
```


    array([[-21.521,  -4.84 ],
           [ -9.109,   4.884],
           [  5.311,  12.298],
           [ 14.86 ,  15.613]])


Rows correspond to the four observations. The first column is the `age` contribution; the second is `experience`. When the `age` value is large and `experience` is near zero, age is the primary driver of that prediction (and vice versa). This decomposition is more informative than inspecting the overall fitted value alone.
