class_docstr: The Nearest Neighbor (NN) Contrastive algorithm uses an auto-encoder to train low dimensional
  representation for each data point, for the nearest neighbor search. The dimensional reduction improves
  the reliability of the neighborhood search. Along with dimensionality reduction, the
  current implementation also allows for imposing class structure-driven orientation of the
  embedding space. For example, in a loan application, a high-income applicant and a
  low-income applicant may have very different evaluation criteria. The auto-encoder uses
  high-income and low-income tag classes during the auto-encoder training, ensuring
  instances with the same tags are in close neighborhoods.
  
  The implementation allows exemplar selection in two ways, (1) contrastive exemplar
  selection is guided by a model prediction, and (2) the user explicitly provides an
  exemplar with a different class tag than the query instance (model-free). Given a query
  instance, the resulting explanation is a set of nearest neighbor exemplars with different
  class tags than the query instances.
docstr: Explain (local explanation) the model prediction for provided instance(s).
  
  Args:
      x (Union[pd.DataFrame, np.ndarray]): input instance to be explained.
  
  Additional Parameters:
      neighbors (int): Number of neighbors
          Overrides neighbors parameter provided in the initializer.
  
  Returns:
      Union(List[dict], dict): explanation object
          Dictionary or list of dictionaries with keys: features,
          categorical_features, query, neighbors, distances.
docstr_tok: ["Explain","(","local","explanation",")","the","model","prediction","for","provided","instance","(","s",")",".","Args",":","x","(","Union","[","pd.DataFrame",",","np.ndarray","]",")",":","input","instance","to","be","explained",".","Additional","Parameters",":","neighbors","(","int",")",":","Number","of","neighbors","Overrides","neighbors","parameter","provided","in","the","initializer",".","Returns",":","Union","(","List","[","dict","]",",","dict",")",":","explanation","object","Dictionary","or","list","of","dictionaries","with","keys",":","features",",","categorical_features",",","query",",","neighbors",",","distances","."]
code: def explain_instance(self, x, **kwargs):
      """Explain (local explanation) the model prediction for provided instance(s).
  
      Args:
          x (Union[pd.DataFrame, np.ndarray]): input instance to be explained.
  
      Additional Parameters:
          neighbors (int): Number of neighbors
              Overrides neighbors parameter provided in the initializer.
  
      Returns:
          Union(List[dict], dict): explanation object
              Dictionary or list of dictionaries with keys: features,
              categorical_features, query, neighbors, distances.
  
      """
      if not self.is_fitted:
          raise RuntimeError(
              f"Error: model needs to be fitted to data prior to explanation!"
          )
      x = np.asarray(x)
      k = kwargs.get("neighbors", self._config.get("neighbors"))
      single_instance = False
      if len(x.shape) == 1:
          x = x[np.newaxis, ...]
          single_instance = True
      emb_x = self._embedding.predict(x)
  
      class_x = None
      filtered_exemplars = self._exemplars
      if self.model is not None:
          class_x = self.model(x)
          if isinstance(class_x, (list, np.ndarray)):
              class_x = list(np.array(class_x, dtype=int).reshape(-1))
          else:
              class_x = list(np.array([class_x], dtype=int).reshape(-1))
          if len(class_x) != emb_x.shape[0]:
              raise ValueError(
                  f"Error: inconsistent class predictor shape!"
              )
  
          # class_x[0] may not be present in self._class_based_neighbor if train data is not complete.
          filtered_exemplars = self._exemplars.drop(
              index=self._class_based_neighbor.get(class_x[0], [])
          )
          self._train_neighbor_tree(exemplars=filtered_exemplars)
  
      dist, idx = self._neighbor_tree.query(emb_x, k=k)
  
      if single_instance:
          data = {}
          data["features"] = self._embedding.features
          data[
              "categorical_features"
          ] = self._embedding.categorical_features()
          data["query"] = x[0].tolist()
          data["neighbors"] = []
          data["distances"] = []
          idx = idx[0].tolist()
  
          filtered_idx = []
          filtered_dist_idx = []
          if (class_x is not None) and (
              class_x[0] in self._class_based_neighbor
          ):  # in case of model driven, filter same class neighbors.
              for k, j in enumerate(idx):
                  if j not in self._class_based_neighbor[class_x[0]]:
                      filtered_dist_idx.append(k)
                      filtered_idx.append(j)
          else:  # in case of model free, all the returned indices are valid neighbors.
              filtered_idx = idx
              filtered_dist_idx = list(range(len(idx)))
  
          if len(filtered_idx) > 0:
              data["neighbors"] = filtered_exemplars.iloc[
                  filtered_idx, :
              ].values.tolist()
              data["distances"] = list(dist[0][filtered_dist_idx])
          return data
      else:
          explanations = []
          for i, idx_set in enumerate(idx):
              data = {}
              data["features"] = self._embedding.features
              data[
                  "categorical_features"
              ] = self._embedding.categorical_features()
              data["query"] = x[i].tolist()
              data["neighbors"] = []
              data["distances"] = []
  
              idx_set = idx_set.tolist()
  
              filtered_idx = []
              filtered_dist_idx = []
              if (class_x is not None) and (
                  class_x[i] in self._class_based_neighbor
              ):  # in case of model driven, filter same class neighbors.
                  for k, j in enumerate(idx_set):
                      if (
                          j
                          not in self._class_based_neighbor[class_x[i]]
                      ):
                          filtered_dist_idx.append(k)
                          filtered_idx.append(j)
              else:  # in case of model free, all the returned indices are valid neighbors.
                  filtered_idx = idx_set
                  filtered_dist_idx = list(range(len(idx_set)))
  
              if len(filtered_idx) > 0:
                  data["neighbors"] = filtered_exemplars.iloc[
                      filtered_idx, :
                  ].values.tolist()
                  data["distances"] = list(dist[i][filtered_dist_idx])
              explanations.append(data)
          return explanations
  
imports: from enum import Enum
  from typing import List, Union, Callable
  import numpy
  import pandas
  from sklearn.neighbors import KDTree
  from aix360.algorithms.lbbe import LocalBBExplainer
  from aix360.algorithms.nncontrastive.embedding import AEEmbedding, JointAEEmbedding
label_desc: Use image node_id 12 for calling the NearestNeighborContrastiveExplainer obj's underlying member method code with example usage: obj.explain_instance(x) and returns: data, explanations