class_docstr: A sampler that uses an underlying training set to draw records that have a subset of features with
  values specified in an instance to be explained, `X`. 
class_docstr_tok: ["A","sampler","that","uses","an","underlying","training","set","to","draw","records","that","have","a","subset","of","features","with","values","specified","in","an","instance","to","be","explained",",","`","X","`","."]
docstr: Implements functionality described in
  :py:meth:`alibi.explainers.anchors.anchor_tabular.TabularSampler.__call__`.
  
  Parameters
  ----------
  anchor:
      Each int is an encoded feature id.
  num_samples
      Number of samples.
  
  Returns
  -------
  samples
      Sampled data from training set.
  d_samples
      Like samples, but continuous data is converted to ordinal discrete data (binned).
  coverage
      The coverage of the result in the training data.
code: def perturbation(
      self, anchor: tuple, num_samples: int
  ) -> Tuple[np.ndarray, np.ndarray, float]:
      """
      Implements functionality described in
      :py:meth:`alibi.explainers.anchors.anchor_tabular.TabularSampler.__call__`.
  
      Parameters
      ----------
      anchor:
          Each int is an encoded feature id.
      num_samples
          Number of samples.
  
      Returns
      -------
      samples
          Sampled data from training set.
      d_samples
          Like samples, but continuous data is converted to ordinal discrete data (binned).
      coverage
          The coverage of the result in the training data.
      """
  
      # initialise samples randomly
      init_sample_idx = np.random.choice(
          range(self.train_data.shape[0]), num_samples, replace=True
      )
      samples = self.train_data[init_sample_idx]
      d_samples = self.d_train_data[init_sample_idx]
  
      if not anchor:
          return samples, d_samples, -1.0
  
      # find the training set row indices where each feature in the anchor has same value as instance
      # for discretized continuous features in the anchor, find which bins it should be sampled from
      # find any features what have values/are in bins that don't exist in the training set
      (
          allowed_bins,
          allowed_rows,
          unk_feat_vals,
      ) = self.get_features_index(anchor)
  
      #  count number of samples available and find the indices for each partial anchor & the full anchor
      uniq_feat_ids = list(
          OrderedDict.fromkeys(
              [self.enc2feat_idx[enc_idx] for enc_idx in anchor]
          )
      )
      uniq_feat_ids = [
          feat
          for feat in uniq_feat_ids
          if feat not in [f for f, _, _ in unk_feat_vals]
      ]
      partial_anchor_rows = list(
          accumulate(
              [allowed_rows[feat] for feat in uniq_feat_ids],
              np.intersect1d,
          ),
      )
      if partial_anchor_rows == []:
          # edge case - if there are no rows at all then `partial_anchor_rows` is the empty list, but it should
          # be a list of an empty array to not cause an error in calculating coverage (which will be 0)
          partial_anchor_rows = [np.array([], dtype=int)]
      nb_partial_anchors = np.array(
          [
              len(n_records)
              for n_records in reversed(partial_anchor_rows)
          ]
      )  # reverse required for np.searchsorted later
      coverage = (
          nb_partial_anchors[0] / self.n_records
      )  # since we sorted, the correct coverage is first not last
  
      # if there are enough train records containing the anchor, replace the original records and return...
      num_samples_pos = np.searchsorted(nb_partial_anchors, num_samples)
      if num_samples_pos == 0:
          samples_idxs = np.random.choice(
              partial_anchor_rows[-1], num_samples
          )
          samples[:, uniq_feat_ids] = self.train_data[np.ix_(samples_idxs, uniq_feat_ids)]  # type: ignore[arg-type]
          d_samples[:, uniq_feat_ids] = self.d_train_data[
              np.ix_(samples_idxs, uniq_feat_ids)
          ]  # type: ignore[arg-type]
  
          return samples, d_samples, coverage
  
      # ... otherwise, replace the record with partial anchors first and then sample the remainder of the feats
      # from  the same bin or set them to the same value as in the instance to be explained
      # NB: this function modifies samples since it gets a reference to this array so it doesn't return
      self.replace_features(
          samples,
          allowed_rows,
          uniq_feat_ids,
          partial_anchor_rows,
          nb_partial_anchors,
          num_samples,
      )
  
      if unk_feat_vals:
          self.handle_unk_features(
              allowed_bins, num_samples, samples, unk_feat_vals
          )
  
      return samples, self.disc.discretize(samples), coverage
  
imports: import copy
  from collections import OrderedDict, defaultdict
  from itertools import accumulate
  from typing import Any, Callable, DefaultDict, Dict, List, Optional, Set, Tuple, Type, Union
  import numpy
  from alibi.api.defaults import DEFAULT_DATA_ANCHOR, DEFAULT_META_ANCHOR
  from alibi.api.interfaces import Explainer, Explanation, FitMixin
  from alibi.exceptions import NotFittedError, PredictorCallError, PredictorReturnTypeError
  from alibi.utils.discretizer import Discretizer
  from alibi.utils.mapping import ohe_to_ord, ord_to_ohe
  from alibi.utils.wrappers import ArgmaxTransformer
  from .anchor_base import AnchorBaseBeam
  from .anchor_explanation import AnchorExplanation
label_desc: Use image node_id 11 for calling the TabularSampler obj's underlying member method code with example usage: obj.perturbation(anchor, num_samples) and returns: samples,  self,  coverage, samples,  d_samples,  unknown, samples,  d_samples,  coverage