class_docstr: This method approximates the Shapley values by iterating through permutations of the inputs.
  
  This is a model agnostic explainer that guarantees local accuracy (additivity) by iterating completely
  through an entire permutation of the features in both forward and reverse directions (antithetic sampling).
  If we do this once, then we get the exact SHAP values for models with up to second order interaction effects.
  We can iterate this many times over many random permutations to get better SHAP value estimates for models
  with higher order interactions. This sequential ordering formulation also allows for easy reuse of
  model evaluations and the ability to efficiently avoid evaluating the model when the background values
  for a feature are the same as the current input value. We can also account for hierarchical data
  structures with partition trees, something not currently implemented for KernalExplainer or SamplingExplainer.
class_docstr_tok: ["This","method","approximates","the","Shapley","values","by","iterating","through","permutations","of","the","inputs",".","This","is","a","model","agnostic","explainer","that","guarantees","local","accuracy","(","additivity",")","by","iterating","completely","through","an","entire","permutation","of","the","features","in","both","forward","and","reverse","directions","(","antithetic","sampling",")",".","If","we","do","this","once",",","then","we","get","the","exact","SHAP","values","for","models","with","up","to","second","order","interaction","effects",".","We","can","iterate","this","many","times","over","many","random","permutations","to","get","better","SHAP","value","estimates","for","models","with","higher","order","interactions",".","This","sequential","ordering","formulation","also","allows","for","easy","reuse","of","model","evaluations","and","the","ability","to","efficiently","avoid","evaluating","the","model","when","the","background","values","for","a","feature","are","the","same","as","the","current","input","value",".","We","can","also","account","for","hierarchical","data","structures","with","partition","trees",",","something","not","currently","implemented","for","KernalExplainer","or","SamplingExplainer","."]
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,
      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,
      )
  
      # by default we run 10 permutations forward and backward
      if max_evals == "auto":
          max_evals = 10 * 2 * len(fm)
  
      # compute any custom clustering for this row
      row_clustering = None
      if getattr(self.masker, "clustering", None) is not None:
          if isinstance(self.masker.clustering, np.ndarray):
              row_clustering = self.masker.clustering
          elif callable(self.masker.clustering):
              row_clustering = self.masker.clustering(*row_args)
          else:
              raise NotImplementedError(
                  "The masker passed has a .clustering attribute that is not yet supported by the Permutation explainer!"
              )
  
      # loop over many permutations
      inds = fm.varying_inputs()
      inds_mask = np.zeros(len(fm), dtype=bool)
      inds_mask[inds] = True
      masks = np.zeros(2 * len(inds) + 1, dtype=int)
      masks[0] = MaskedModel.delta_mask_noop_value
      npermutations = max_evals // (2 * len(inds) + 1)
      row_values = None
      row_values_history = None
      history_pos = 0
      main_effect_values = None
      if len(inds) > 0:
          for _ in range(npermutations):
              # shuffle the indexes so we get a random permutation ordering
              if row_clustering is not None:
                  # [TODO] This is shuffle does not work when inds is not a complete set of integers from 0 to M TODO: still true?
                  # assert len(inds) == len(fm), "Need to support partition shuffle when not all the inds vary!!"
                  partition_tree_shuffle(
                      inds, inds_mask, row_clustering
                  )
              else:
                  np.random.shuffle(inds)
  
              # create a large batch of masks to evaluate
              i = 1
              for ind in inds:
                  masks[i] = ind
                  i += 1
              for ind in inds:
                  masks[i] = ind
                  i += 1
  
              # evaluate the masked model
              outputs = fm(masks, zero_index=0, batch_size=batch_size)
  
              if row_values is None:
                  row_values = np.zeros((len(fm),) + outputs.shape[1:])
  
                  if error_bounds:
                      row_values_history = np.zeros(
                          (
                              2 * npermutations,
                              len(fm),
                          )
                          + outputs.shape[1:]
                      )
  
              # update our SHAP value estimates
              i = 0
              for ind in inds:  # forward
                  row_values[ind] += outputs[i + 1] - outputs[i]
                  if error_bounds:
                      row_values_history[history_pos][ind] = (
                          outputs[i + 1] - outputs[i]
                      )
                  i += 1
              history_pos += 1
              for ind in inds:  # backward
                  row_values[ind] += outputs[i] - outputs[i + 1]
                  if error_bounds:
                      row_values_history[history_pos][ind] = (
                          outputs[i] - outputs[i + 1]
                      )
                  i += 1
              history_pos += 1
  
          if npermutations == 0:
              raise ValueError(
                  f"max_evals={max_evals} is too low for the Permutation explainer, it must be at least 2 * num_features + 1 = {2 * len(inds) + 1}!"
              )
  
          expected_value = outputs[0]
  
          # compute the main effects if we need to
          if main_effects:
              main_effect_values = fm.main_effects(
                  inds, batch_size=batch_size
              )
      else:
          masks = np.zeros(1, dtype=int)
          outputs = fm(masks, zero_index=0, batch_size=1)
          expected_value = outputs[0]
          row_values = np.zeros((len(fm),) + outputs.shape[1:])
          if error_bounds:
              row_values_history = np.zeros(
                  (
                      2 * npermutations,
                      len(fm),
                  )
                  + outputs.shape[1:]
              )
  
      return {
          "values": row_values / (2 * npermutations),
          "expected_values": expected_value,
          "mask_shapes": fm.mask_shapes,
          "main_effects": main_effect_values,
          "clustering": row_clustering,
          "error_std": None
          if row_values_history is None
          else row_values_history.std(0),
          "output_names": self.model.output_names
          if hasattr(self.model, "output_names")
          else None,
      }
  
file_path: shap/shap/explainers/_permutation.py
imports: import warnings
  import numpy
  from ..None import links
  from ..models import Model
  from ..utils import MaskedModel, partition_tree_shuffle
  from ._explainer import Explainer
label_desc: Use image node_id 3 for calling the PermutationExplainer obj's underlying member method code with example usage: obj.explain_row() and returns: dict