Partial dependence, ICE and ALE - how the prediction moves with a feature.
Functions
Trace each sample's prediction as one feature is swept.
Parameters
estimator
X
feature
n_points
feature_names
Returns
explanation
values has shape (n_samples, n_grid). metadata['grid'] holds the grid, metadata['average'] the partial-dependence curve.
Notes
Complexity. n_grid prediction passes over the whole of X.
Like partial dependence, this evaluates the model on inputs the data may never contain — setting one feature to a value incompatible with the rest of the row. Read curves in dense regions with more confidence than in sparse ones.
References
See Also
>>> import numpy as np
>>> from tuiml.explain import individual_conditional_expectation
>>> from tuiml.algorithms.trees import RandomForestRegressor
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(size=(120, 3))
>>> y = 2.0 * X[:, 0] + rng.normal(0, 0.1, 120)
>>> model = RandomForestRegressor(n_estimators=30, random_state=0).fit(X, y)
>>> ice = individual_conditional_expectation(model, X, feature=0, n_points=10)
>>> ice.values.shape[0]
120
The average effect of a feature on the prediction.
Parameters
estimator
X
feature
n_points
feature_names
Returns
explanation
values is the curve, of shape (n_grid,). metadata['grid'] holds the grid and metadata['ice'] the individual curves it averages.
Notes
Complexity. n_grid prediction passes over the whole of X.
Two failure modes worth knowing before trusting a flat curve. First, averaging hides heterogeneity: curves rising for half the population and falling for the other half average to nothing. Always glance at individual_conditional_expectation before concluding a feature does not matter. Second, marginalising by averaging over the data creates rows the world cannot produce — a house with two rooms and 400 square metres — and the model is scored on them anyway. Where features are strongly correlated, accumulated_local_effects avoids that by only ever perturbing within a local window.
References
See Also
>>> import numpy as np
>>> from tuiml.explain import partial_dependence
>>> from tuiml.algorithms.trees import RandomForestRegressor
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(size=(200, 3))
>>> y = 2.0 * X[:, 0] + rng.normal(0, 0.1, 200)
>>> model = RandomForestRegressor(n_estimators=30, random_state=0).fit(X, y)
>>> pd = partial_dependence(model, X, feature=0, n_points=12)
>>> bool(np.all(np.diff(pd.values) > -0.05)) # rises with the feature
True
A feature's effect, computed without inventing impossible rows.
Partial dependence asks what the model says when a feature is set to a value the rest of the row makes implausible. ALE never does that: it divides the feature into quantile bins, and inside each bin asks only how the prediction changes between that bin's own edges, for the rows that actually fall there. Those local differences are then accumulated into a curve.
Under correlated features this is the difference between a trustworthy curve and an artefact.
Overview
- Cut the feature into quantile bins.
- For rows in a bin, predict twice — once with the feature at the bin's
- Average that difference within the bin: the local effect.
- Accumulate across bins, then centre so the curve averages to zero.
Theory
With bins (z_{k-1}, z_k] the uncentred ALE is
and is then centred by subtracting its data-weighted mean. Every evaluation keeps a row's other features intact and moves the target feature only within the narrow band it already occupies, so the model is never asked about a combination the data does not support.
Parameters
estimator
X
feature
n_bins
feature_names
Returns
explanation
values is the centred ALE curve at each bin edge. metadata['grid'] holds the edges and metadata['bin_counts'] the rows behind each estimate.
Notes
Complexity. Two prediction passes over the data in total, regardless of bin count — cheaper than partial dependence, which costs one pass per grid point.
The curve is an effect, not a level. ALE is centred at zero and says how the prediction changes relative to the average, so its absolute height carries no meaning; only its shape and range do.
Bins holding few rows give noisy local effects. Check metadata['bin_counts'] before reading anything into a sharp move at the tails.
References
>>> import numpy as np
>>> from tuiml.explain import accumulated_local_effects
>>> from tuiml.algorithms.trees import RandomForestRegressor
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(size=(300, 2))
>>> X[:, 1] = X[:, 0] + rng.normal(0, 0.1, 300) # strongly correlated
>>> y = 2.0 * X[:, 0] + rng.normal(0, 0.1, 300)
>>> model = RandomForestRegressor(n_estimators=30, random_state=0).fit(X, y)
>>> ale = accumulated_local_effects(model, X, feature=0, n_bins=10)
>>> bool(ale.values[-1] > ale.values[0]) # increasing effect
True