Add Accumulated local effects (ALE) to inspection

### Describe the workflow you want to enable

I'd love to push the inspection module further by adding Accumulated local effects (ALE) from Apley 2020. A great description can be found in Christoph's online book https://christophm.github.io/interpretable-ml-book/ale.html

ALE fix the problem of partial dependence that they force the model to be evaluated on impossible or rare feature combinations. ALE are defined for numeric features that can be binned. From both bin edges, the slope of the partial dependence is calculated locally, i.e., only using observations in the bin. The slopes from all bins are cumsummed and vertically centered to the average response or prediction.

### Reference

Apley, Daniel W., and Jingyu Zhu. 2020. Visualizing the Effects of Predictor Variables in Black Box Supervised Learning Models. Journal of the Royal Statistical Society Series B: Statistical Methodology, 82 (4): 1059–1086. doi:10.1111/rssb.12377.

### Describe your proposed solution

Pseudo code to calculate ALE for one feature:

``` py
pd_slopes = np.zeros_like(bins)

for i, bin in enumerate(bins):
  X_bin = pick n_per_bin = 200 rows from data in bin
  if X_bin is empty:
    next
  pd = partial_dependence_brute(model, X_bin, grid = [lower bin edge, upper bin edge], sample_weights)
  pd_slopes[i] = pd[1] - pd[0]

return np.cumsum(pd_slopes)
```

### Describe alternatives you've considered, if relevant

_No response_

### Additional context

_No response_

COMMENT:
Hello. Your code is actually much further than I had in mind, thx!

```py
from sklearn.datasets import load_diabetes
from sklearn.ensemble import HistGradientBoostingRegressor

X, y = load_diabetes(return_X_y=True, as_frame=True)

est = HistGradientBoostingRegressor(max_iter=50, max_depth=4).fit(X, y)

accumulated_local_effect(est, X=X, features=["s3"])
```

gives:

```py
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[16], line 8
      4 X, y = load_diabetes(return_X_y=True, as_frame=True)
      6 est = HistGradientBoostingRegressor(max_iter=50, max_depth=4).fit(X, y)
----> 8 accumulated_local_effect(est, X=X, features=["s3"])

File c:\Users\mayer\ai-tools-for-actuaries\.venv\Lib\site-packages\sklearn\utils\_param_validation.py:213, in validate_params.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
    207 try:
    208     with config_context(
    209         skip_parameter_validation=(
    210             prefer_skip_nested_validation or global_skip_validation
    211         )
    212     ):
--> 213         return func(*args, **kwargs)
    214 except InvalidParameterError as e:
    215     # When the function is just a wrapper around an estimator, we allow
    216     # the function to delegate validation to the estimator, but we replace
    217     # the name of the estimator by the name of the function in the error
    218     # message to avoid confusion.
    219     msg = re.sub(
    220         r"parameter of \w+ must be",
    221         f"parameter of {func.__qualname__} must be",
    222         str(e),
    223     )

Cell In[3], line 357, in accumulated_local_effect(estimator, X, features, sample_weight, categorical_features, feature_names, response_method, grid_resolution, custom_values)
    355     break
    356 # code partially copy from ALEpython (https://github.com/blent-ai/ALEPython/blob/dev/src/alepython/ale.py)
--> 357 quantile = quantiles[feature_idx]
    358 indice = indices[feature_idx]
    359 averaged_predictions, predictions = _partial_dependence_brute(
    360     estimator,
    361     [[quantile[indice]], [quantile[indice + 1]]],
   (...)    365     sample_weight,
    366 )

IndexError: list index out of range
```

But maybe I did something stupid.

Labels: New Feature, Needs Decision