class_docstr: Computes SHAP values via an optimized exact enumeration.
  
  This works well for standard Shapley value maskers for models with less than ~15 features that vary
  from the background per sample. It also works well for Owen values from hclustering structured
  maskers when there are less than ~100 features that vary from the background per sample. This
  explainer minimizes the number of function evaluations needed by ordering the masking sets to
  minimize sequential differences. This is done using gray codes for standard Shapley values
  and a greedy sorting method for hclustering structured maskers.
class_docstr_tok: ["Computes","SHAP","values","via","an","optimized","exact","enumeration",".","This","works","well","for","standard","Shapley","value","maskers","for","models","with","less","than","~15","features","that","vary","from","the","background","per","sample",".","It","also","works","well","for","Owen","values","from","hclustering","structured","maskers","when","there","are","less","than","~100","features","that","vary","from","the","background","per","sample",".","This","explainer","minimizes","the","number","of","function","evaluations","needed","by","ordering","the","masking","sets","to","minimize","sequential","differences",".","This","is","done","using","gray","codes","for","standard","Shapley","values","and","a","greedy","sorting","method","for","hclustering","structured","maskers","."]
docstr: Explains a single row and returns the tuple (row_values, row_expected_values, row_mask_shapes).
          
docstr_tok: ["Explains","a","single","row","and","returns","the","tuple","(","row_values",",","row_expected_values",",","row_mask_shapes",")","."]
code: def explain_row(
      self,
      *row_args,
      max_evals,
      main_effects,
      error_bounds,
      batch_size,
      outputs,
      interactions,
      silent,
  ):
      """Explains a single row and returns the tuple (row_values, row_expected_values, row_mask_shapes)."""
  
      # build a masked version of the model for the current input sample
      fm = MaskedModel(
          self.model,
          self.masker,
          self.link,
          self.linearize_link,
          *row_args,
      )
  
      # do the standard Shapley values
      inds = None
      if getattr(self.masker, "clustering", None) is None:
          # see which elements we actually need to perturb
          inds = fm.varying_inputs()
  
          # make sure we have enough evals
          if (
              max_evals is not None
              and max_evals != "auto"
              and max_evals < 2 ** len(inds)
          ):
              raise ValueError(
                  f"It takes {2**len(inds)} masked evaluations to run the Exact explainer on this instance, but max_evals={max_evals}!"
              )
  
          # generate the masks in gray code order (so that we change the inputs as little
          # as possible while we iterate to minimize the need to re-eval when the inputs
          # don't vary from the background)
          delta_indexes = self._cached_gray_codes(len(inds))
  
          # map to a larger mask that includes the invariant entries
          extended_delta_indexes = np.zeros(2 ** len(inds), dtype=int)
          for i in range(2 ** len(inds)):
              if delta_indexes[i] == MaskedModel.delta_mask_noop_value:
                  extended_delta_indexes[i] = delta_indexes[i]
              else:
                  extended_delta_indexes[i] = inds[delta_indexes[i]]
  
          # run the model
          outputs = fm(
              extended_delta_indexes,
              zero_index=0,
              batch_size=batch_size,
          )
  
          # Shapley values
          # Care: Need to distinguish between `True` and `1`
          if interactions is False or (
              interactions == 1 and interactions is not True
          ):
              # loop over all the outputs to update the rows
              coeff = shapley_coefficients(len(inds))
              row_values = np.zeros((len(fm),) + outputs.shape[1:])
              mask = np.zeros(len(fm), dtype=bool)
              _compute_grey_code_row_values(
                  row_values,
                  mask,
                  inds,
                  outputs,
                  coeff,
                  extended_delta_indexes,
                  MaskedModel.delta_mask_noop_value,
              )
  
          # Shapley-Taylor interaction values
          elif interactions is True or interactions == 2:
              # loop over all the outputs to update the rows
              coeff = shapley_coefficients(len(inds))
              row_values = np.zeros(
                  (len(fm), len(fm)) + outputs.shape[1:]
              )
              mask = np.zeros(len(fm), dtype=bool)
              _compute_grey_code_row_values_st(
                  row_values,
                  mask,
                  inds,
                  outputs,
                  coeff,
                  extended_delta_indexes,
                  MaskedModel.delta_mask_noop_value,
              )
  
          elif interactions > 2:
              raise NotImplementedError(
                  "Currently the Exact explainer does not support interactions higher than order 2!"
              )
  
      # do a partition tree constrained version of Shapley values
      else:
          # make sure we have enough evals
          if (
              max_evals is not None
              and max_evals != "auto"
              and max_evals < len(fm) ** 2
          ):
              raise ValueError(
                  f"It takes {len(fm)**2} masked evaluations to run the Exact explainer on this instance, but max_evals={max_evals}!"
              )
  
          # generate the masks in a hclust order (so that we change the inputs as little
          # as possible while we iterate to minimize the need to re-eval when the inputs
          # don't vary from the background)
          delta_indexes = self._partition_delta_indexes
  
          # run the model
          outputs = fm(delta_indexes, batch_size=batch_size)
  
          # loop over each output feature
          row_values = np.zeros((len(fm),) + outputs.shape[1:])
          for i in range(len(fm)):
              on_outputs = outputs[self._partition_masks_inds[i][1]]
              off_outputs = outputs[self._partition_masks_inds[i][0]]
              row_values[i] = (on_outputs - off_outputs).mean(0)
  
      # compute the main effects if we need to
      main_effect_values = None
      if main_effects or interactions is True or interactions == 2:
          if inds is None:
              inds = np.arange(len(fm))
          main_effect_values = fm.main_effects(inds)
          if interactions is True or interactions == 2:
              for i in range(len(fm)):
                  row_values[i, i] = main_effect_values[i]
  
      return {
          "values": row_values,
          "expected_values": outputs[0],
          "mask_shapes": fm.mask_shapes,
          "main_effects": main_effect_values if main_effects else None,
          "clustering": getattr(self.masker, "clustering", None),
      }
  
file_path: shap/shap/explainers/_exact.py
imports: import logging
  import numpy
  from numba import njit
  from ..None import links
  from ..models import Model
  from ..utils import MaskedModel, delta_minimization_order, make_masks, shapley_coefficients
  from ._explainer import Explainer
label_desc: Use image node_id 4 for calling the ExactExplainer obj's underlying member method code with example usage: obj.explain_row() and returns: dict