# Predict on New Data

Once a GAM is fitted, `predict()` accepts any dict mapping predictor names to arrays of new covariate values. There is no need to refit. You can evaluate the model at a fine grid, at held-out observations, or at arbitrary hypothetical inputs.


# Fit

Start with the `mcycle` dataset and a single-smooth Gaussian GAM.


``` python
import whittaker as wk

# Load mcycle dataset
data = wk.load_dataset("mcycle")

# Fit single-smooth Gaussian GAM
model = wk.GAM("accel ~ s(times)").fit(data)
model.summary()
```


    GAM fit summary
    ============================================================
    Formula:    accel ~ s(times)
    Family:     Gaussian(link='identity')
    Inference:  GCV
    Observations: 133
    Coefficients: 10

    Parametric coefficients:
      Term                       Estimate    Std.Err    t value    p-value
      ------------------------ ---------- ---------- ---------- ----------
      (Intercept)                -45.6924     1.8364    -24.882    < 1e-16

    Approximate significance of smooth terms:
      Term                        EDF Ref.df     Chi.sq    p-value
      ------------------------ ------ ------ ---------- ----------
      s(times)                   7.92      8    663.379    < 1e-16

    Total EDF:  8.92
    Scale est:  448.520495
    Deviance:   55654.5201
    Null dev:   357878.4929
    Dev. expl:  84.4%
    GCV score:  480.746115
    AIC:        1198.44
    BIC:        1224.21


# Predict on a grid

Build a 200-point grid covering the full range of `times`, then call `predict()`.


``` python
import numpy as np

# Build 200-point grid over full time range
grid = {"times": np.linspace(0, 57.6, 200)}

# Predict acceleration on the grid
preds = model.predict(grid)
preds.values[:5].round(2)
```


    array([3.27, 4.18, 5.08, 5.97, 6.83])


The first five fitted values show the smooth near the start of the time range. To see the full spread, check the minimum and maximum of the predictions.


``` python
preds.values.min(), preds.values.max()
```


    (np.float64(-122.67436599182616), np.float64(11.786547134320472))


The predicted range spans a wide swath of acceleration values, reflecting the sharp impulse captured by the smooth. A fine grid like this is also useful for plotting a smooth curve through the data.


# Multiple columns

When the formula has more than one predictor, every predictor named in the formula must appear in the new-data dict. The `wages` dataset demonstrates this with two predictors, `age` and `experience`.


``` python
# Load wages data and fit two-predictor GAM
wages_data = wk.load_dataset("wages")
wages_model = wk.GAM("wage ~ s(age) + s(experience)").fit(wages_data)
```


Pass both predictors together. Each position in the arrays corresponds to one new observation.


``` python
# Build new-data with paired age and experience values
new_data = {
    "age": np.array([25, 35, 45, 55]),
    "experience": np.array([2, 8, 15, 25]),
}

# Predict wages at new covariate values
preds_wages = wages_model.predict(new_data)
preds_wages.values.round(2)
```


    array([18.86, 38.75, 60.36, 75.58])


Omitting any predictor named in the formula raises an error, so the dict must be complete even if you are only interested in varying one variable.


# Link scale predictions

By default, predictions are returned on the response scale (after applying the inverse link). To obtain the linear predictor instead (useful for manual interval construction or custom back-transformation) pass `type="link"`.


``` python
preds_link = model.predict(grid, type="link")
preds_link.values[:5].round(4)
```


    array([3.272 , 4.1767, 5.0771, 5.9653, 6.8306])


For a Gaussian model the link is the identity, so response-scale and link-scale values are identical. The distinction matters most for Poisson and binomial families.
