docstr: Calculate the ALE curves for each feature with respect to the dataset `X`.
  
  Parameters
  ----------
  X
      An `N x F` tabular dataset used to calculate the ALE curves. This is typically the training dataset
      or a representative sample.
  features
      Features for which to calculate ALE.
  min_bin_points
      Minimum number of points each discretized interval should contain to ensure more precise
      ALE estimation. Only relevant for adaptive grid points (i.e., features without an entry in the
      `grid_points` dictionary).
  grid_points
      Custom grid points. Must be a `dict` where the keys are features indices and the values are
      monotonically increasing `numpy` arrays defining the grid points for each feature.
      See the :ref:`Notes<Notes ALE explain>` section for the default behavior when potential edge-cases arise
      when using grid-points. If no grid points are specified (i.e. the feature is missing from the `grid_points`
      dictionary), deciles discretization is used instead.
  
  Returns
  -------
  explanation
      An `Explanation` object containing the data and the metadata of the calculated ALE curves.
      See usage at `ALE examples`_ for details.
  
      .. _ALE examples:
          https://docs.seldon.io/projects/alibi/en/latest/methods/ALE.html
  
  Notes
  -----
  .. _Notes ALE explain:
  
  Consider `f` to be a feature of interest. We denote possible feature values of `f` by `X` (i.e. the values
  from the dataset column corresponding to feature `f`), by `O` a user-specified grid-point value, and by
  `(X|O)` an overlap between a grid-point and a feature value. We can encounter the following edge-cases:
  
   - Grid points outside the feature range. Consider the following example: `O O O X X O X O X O O`,         where 3 grid-points are smaller than the minimum value in `f`, and 2 grid-points are larger than the maximum         value in `f`. The empty leading and ending bins are removed. The grid-points considered
  will be: `O X X O X O X O`.
  
   - Grid points that do not cover the entire feature range. Consider the following example:         `X X O X X O X O X X X X X`. Two auxiliary grid-points are added which correspond the value of the minimum         and maximum value of feature `f`. The grid-points considered will be: `(O|X) X O X X O X O X X X X (X|O)`.
  
   - Grid points that do not contain any values in between. Consider the following example:         `(O|X) X X O O O X O X O O (X|O)`. The intervals which do not contain any feature values are removed/merged.         The grid-points considered will be: `(O|X) X X O X O X O (X|O)`.
docstr_tok: ["Calculate","the","ALE","curves","for","each","feature","with","respect","to","the","dataset","`","X","`",".","Parameters","--","--","--","--","--","X","An","`","N","x","F","`","tabular","dataset","used","to","calculate","the","ALE","curves",".","This","is","typically","the","training","dataset","or","a","representative","sample",".","features","Features","for","which","to","calculate","ALE",".","min_bin_points","Minimum","number","of","points","each","discretized","interval","should","contain","to","ensure","more","precise","ALE","estimation",".","Only","relevant","for","adaptive","grid","points","(","i.e.",",","features","without","an","entry","in","the","`","grid_points","`","dictionary",")",".","grid_points","Custom","grid","points",".","Must","be","a","`","dict","`","where","the","keys","are","features","indices","and","the","values","are","monotonically","increasing","`","numpy","`","arrays","defining","the","grid","points","for","each","feature",".","See","the",":","ref",":","`","Notes","<","Notes","ALE","explain",">","`","section","for","the","default","behavior","when","potential","edge-cases","arise","when","using","grid-points",".","If","no","grid","points","are","specified","(","i.e",".","the","feature","is","missing","from","the","`","grid_points","`","dictionary",")",",","deciles","discretization","is","used","instead",".","Returns","--","--","--","-","explanation","An","`","Explanation","`","object","containing","the","data","and","the","metadata","of","the","calculated","ALE","curves",".","See","usage","at","`","ALE","examples","`","_","for","details",".","..","_ALE","examples",":","https",":","\/\/docs.seldon.io\/projects\/alibi\/en\/latest\/methods\/ALE.html","Notes","--","--","-","..","_Notes","ALE","explain",":","Consider","`","f","`","to","be","a","feature","of","interest",".","We","denote","possible","feature","values","of","`","f","`","by","`","X","`","(","i.e",".","the","values","from","the","dataset","column","corresponding","to","feature","`","f","`",")",",","by","`","O","`","a","user-specified","grid-point","value",",","and","by","`","(","X|O",")","`","an","overlap","between","a","grid-point","and","a","feature","value",".","We","can","encounter","the","following","edge-cases",":","-","Grid","points","outside","the","feature","range",".","Consider","the","following","example",":","`","O","O","O","X","X","O","X","O","X","O","O","`",",","where","3","grid-points","are","smaller","than","the","minimum","value","in","`","f","`",",","and","2","grid-points","are","larger","than","the","maximum","value","in","`","f","`",".","The","empty","leading","and","ending","bins","are","removed",".","The","grid-points","considered","will","be",":","`","O","X","X","O","X","O","X","O","`",".","-","Grid","points","that","do","not","cover","the","entire","feature","range",".","Consider","the","following","example",":","`","X","X","O","X","X","O","X","O","X","X","X","X","X","`",".","Two","auxiliary","grid-points","are","added","which","correspond","the","value","of","the","minimum","and","maximum","value","of","feature","`","f","`",".","The","grid-points","considered","will","be",":","`","(","O|X",")","X","O","X","X","O","X","O","X","X","X","X","(","X|O",")","`",".","-","Grid","points","that","do","not","contain","any","values","in","between",".","Consider","the","following","example",":","`","(","O|X",")","X","X","O","O","O","X","O","X","O","O","(","X|O",")","`",".","The","intervals","which","do","not","contain","any","feature","values","are","removed\/merged",".","The","grid-points","considered","will","be",":","`","(","O|X",")","X","X","O","X","O","X","O","(","X|O",")","`","."]
code: def explain(
      self,
      X: np.ndarray,
      features: Optional[List[int]] = None,
      min_bin_points: int = 4,
      grid_points: Optional[Dict[int, np.ndarray]] = None,
  ) -> Explanation:
      """
          Calculate the ALE curves for each feature with respect to the dataset `X`.
  
          Parameters
          ----------
          X
              An `N x F` tabular dataset used to calculate the ALE curves. This is typically the training dataset
              or a representative sample.
          features
              Features for which to calculate ALE.
          min_bin_points
              Minimum number of points each discretized interval should contain to ensure more precise
              ALE estimation. Only relevant for adaptive grid points (i.e., features without an entry in the
              `grid_points` dictionary).
          grid_points
              Custom grid points. Must be a `dict` where the keys are features indices and the values are
              monotonically increasing `numpy` arrays defining the grid points for each feature.
              See the :ref:`Notes<Notes ALE explain>` section for the default behavior when potential edge-cases arise
              when using grid-points. If no grid points are specified (i.e. the feature is missing from the `grid_points`
              dictionary), deciles discretization is used instead.
  
          Returns
          -------
          explanation
              An `Explanation` object containing the data and the metadata of the calculated ALE curves.
              See usage at `ALE examples`_ for details.
  
              .. _ALE examples:
                  https://docs.seldon.io/projects/alibi/en/latest/methods/ALE.html
  
          Notes
          -----
          .. _Notes ALE explain:
  
          Consider `f` to be a feature of interest. We denote possible feature values of `f` by `X` (i.e. the values
          from the dataset column corresponding to feature `f`), by `O` a user-specified grid-point value, and by
          `(X|O)` an overlap between a grid-point and a feature value. We can encounter the following edge-cases:
  
           - Grid points outside the feature range. Consider the following example: `O O O X X O X O X O O`, \
          where 3 grid-points are smaller than the minimum value in `f`, and 2 grid-points are larger than the maximum \
          value in `f`. The empty leading and ending bins are removed. The grid-points considered
          will be: `O X X O X O X O`.
  
           - Grid points that do not cover the entire feature range. Consider the following example: \
          `X X O X X O X O X X X X X`. Two auxiliary grid-points are added which correspond the value of the minimum \
          and maximum value of feature `f`. The grid-points considered will be: `(O|X) X O X X O X O X X X X (X|O)`.
  
           - Grid points that do not contain any values in between. Consider the following example: \
          `(O|X) X X O O O X O X O O (X|O)`. The intervals which do not contain any feature values are removed/merged. \
          The grid-points considered will be: `(O|X) X X O X O X O (X|O)`.
  
          """
      self.meta["params"].update(min_bin_points=min_bin_points)
  
      if X.ndim != 2:
          raise ValueError("The array X must be 2-dimensional")
      n_features = X.shape[1]
  
      # set feature and target names, this is done here as we don't know n_features at init time
      if self.feature_names is None:
          self.feature_names = [f"f_{i}" for i in range(n_features)]
      if self.target_names is None:
          pred = np.atleast_2d(self.predictor(X[0].reshape(1, -1)))
          n_targets = pred.shape[1]
          self.target_names = [f"c_{i}" for i in range(n_targets)]
      self.feature_names = np.array(self.feature_names)  # type: ignore
      self.target_names = np.array(self.target_names)  # type: ignore
  
      # only calculate ALE for the specified features and return the explanation for this subset
      if features:
          feature_names = self.feature_names[features]  # type: ignore
      else:
          feature_names = self.feature_names
          features = list(range(n_features))
  
      feature_values = []
      ale_values = []
      ale0 = []
      feature_deciles = []
  
      if grid_points is None:
          grid_points = {}
  
      # TODO: use joblib to parallelize?
      for feature in features:
          # Getting custom grid values. If the grid for a feature is not specified, `feature_grid_points = None`.
          feature_grid_points = grid_points.get(feature)
          fvals, ale, a0 = ale_num(
              self.predictor,
              X=X,
              feature=feature,
              feature_grid_points=feature_grid_points,
              min_bin_points=min_bin_points,
              check_feature_resolution=self.check_feature_resolution,
              low_resolution_threshold=self.low_resolution_threshold,
              extrapolate_constant=self.extrapolate_constant,
              extrapolate_constant_perc=self.extrapolate_constant_perc,
              extrapolate_constant_min=self.extrapolate_constant_min,
          )
          deciles = get_quantiles(X[:, feature], num_quantiles=11)
  
          feature_values.append(fvals)
          ale_values.append(ale)
          ale0.append(a0)
          feature_deciles.append(deciles)
  
      constant_value = self.predictor(X).mean()
      # TODO: an ALE plot ideally requires a rugplot to gauge density of instances in the feature space.
      # I've replaced this with feature deciles which is coarser but has constant space complexity
      # as opposed to a rugplot. Alternatively, could consider subsampling to produce a rug with some
      # maximum number of points.
      return self._build_explanation(
          ale_values=ale_values,
          ale0=ale0,
          constant_value=constant_value,
          feature_values=feature_values,
          feature_deciles=feature_deciles,
          feature_names=feature_names,
      )
  
imports: import copy
  import math
  import numpy
  import pandas
  from itertools import count
  from functools import partial
  from typing import Callable, List, Optional, Tuple, Union, Dict, TYPE_CHECKING, no_type_check
  import sys
  import logging
  from alibi.api.interfaces import Explainer, Explanation
  from alibi.api.defaults import DEFAULT_META_ALE, DEFAULT_DATA_ALE
label_desc: Use image node_id 2 for calling the ALE obj's underlying member method code with example usage: obj.explain(X, features, min_bin_points, grid_points) and returns: self