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: The method creates perturbed samples by first replacing all partial anchors with partial anchors drawn
  from the training set. Then remainder of the features are then replaced with random values drawn from
  the same bin for discretized continuous features and same value for categorical features.
  
  Parameters
  ----------
  samples
      Randomly drawn samples, where the anchor does not apply.
  allowed_rows
      Maps feature ids to the rows indices in training set where the feature has same value as instance (cat.)
      or is in the same bin.
  uniq_feat_ids
      Multiple encoded features in the anchor can map to the same original feature id. Unique features in the
      anchor. This is the list of unique original features id in the anchor.
  partial_anchor_rows
      The rows in the training set where each partial anchor applies. Last entry is an array of row indices where
      the entire anchor applies.
  nb_partial_anchors
      The number of training records which contain each partial anchor.
  num_samples
      Number of perturbed samples to be returned.
code: def replace_features(
      self,
      samples: np.ndarray,
      allowed_rows: Dict[int, Any],
      uniq_feat_ids: List[int],
      partial_anchor_rows: List[np.ndarray],
      nb_partial_anchors: np.ndarray,
      num_samples: int,
  ) -> None:
      """
      The method creates perturbed samples by first replacing all partial anchors with partial anchors drawn
      from the training set. Then remainder of the features are then replaced with random values drawn from
      the same bin for discretized continuous features and same value for categorical features.
  
      Parameters
      ----------
      samples
          Randomly drawn samples, where the anchor does not apply.
      allowed_rows
          Maps feature ids to the rows indices in training set where the feature has same value as instance (cat.)
          or is in the same bin.
      uniq_feat_ids
          Multiple encoded features in the anchor can map to the same original feature id. Unique features in the
          anchor. This is the list of unique original features id in the anchor.
      partial_anchor_rows
          The rows in the training set where each partial anchor applies. Last entry is an array of row indices where
          the entire anchor applies.
      nb_partial_anchors
          The number of training records which contain each partial anchor.
      num_samples
          Number of perturbed samples to be returned.
      """
  
      requested_samples = num_samples
      start, n_anchor_feats = 0, len(partial_anchor_rows)
      uniq_feat_ids = list(reversed(uniq_feat_ids))
  
      try:
          start_idx = np.nonzero(nb_partial_anchors)[0][
              0
          ]  # skip anchors with no samples in the database
      except IndexError:
          # there are no samples in the database, need to break out of the function
          # and go straight to treating unknown features
          return
  
      end_idx = np.searchsorted(
          np.cumsum(nb_partial_anchors), num_samples
      )
  
      # replace partial anchors with partial anchors drawn from the training dataset
      # samp_idxs are arrays of training set row indices from where partial anchors are extracted for replacement
      for idx, n_samp in enumerate(
          nb_partial_anchors[start_idx : end_idx + 1], start=start_idx
      ):
          if num_samples >= n_samp:
              samp_idxs = partial_anchor_rows[n_anchor_feats - idx - 1]
              num_samples -= n_samp
          else:
              if (
                  num_samples
                  <= partial_anchor_rows[
                      n_anchor_feats - idx - 1
                  ].shape[0]
              ):
                  samp_idxs = np.random.choice(
                      partial_anchor_rows[n_anchor_feats - idx - 1],
                      num_samples,
                  )
              else:
                  samp_idxs = np.random.choice(
                      partial_anchor_rows[n_anchor_feats - idx - 1],
                      num_samples,
                      replace=True,
                  )
              n_samp = num_samples
          samples[
              start : start + n_samp, uniq_feat_ids[idx:]
          ] = self.train_data[
              np.ix_(samp_idxs, uniq_feat_ids[idx:])
          ]  # type: ignore[arg-type]
  
          # deal with partial anchors; idx = 0 means that we actually sample the entire anchor
          if idx > 0:
              # choose replacement values at random from training set
              feats_to_replace = uniq_feat_ids[:idx]
              samp_idxs = np.zeros(
                  (len(feats_to_replace), n_samp)
              ).astype(
                  int
              )  # =: P x Q
              for i, feat_idx in enumerate(feats_to_replace):
                  samp_idxs[i, :] = np.random.choice(
                      allowed_rows[feat_idx], n_samp, replace=True
                  )
  
              # N x F ->  P X Q x F -> P X Q
              # First slice takes the data rows indicated in rows of samp_idxs; each row corresponds to a diff. feat
              # Second slice takes the column indicate in feats_to_replace from each of P Q x F sub-tensors
              to_replace_vals = self.train_data[samp_idxs][
                  np.arange(len(feats_to_replace)), :, feats_to_replace
              ]
              samples[
                  start : start + n_samp, feats_to_replace
              ] = to_replace_vals.transpose()
          start += n_samp
  
      # possible that the dataset doesn't contain enough partial examples. Eg, in anchor is (10,) and have
      # 50 examples in training set but require batch size of 100, all features have to be replaced  one by one in
      # last 50 samples
      max_samples_available = nb_partial_anchors.sum()
      if max_samples_available <= requested_samples:
          n_samp = samples.shape[0] - start
  
          samp_idxs = np.zeros((len(uniq_feat_ids), n_samp)).astype(int)
          for i, feat_idx in enumerate(uniq_feat_ids):
              samp_idxs[i, :] = np.random.choice(
                  allowed_rows[feat_idx], n_samp, replace=True
              )
  
          to_replace_vals = self.train_data[samp_idxs][
              np.arange(len(uniq_feat_ids)), :, uniq_feat_ids
          ]
          samples[start:, uniq_feat_ids] = to_replace_vals.transpose()
  
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 13 for calling the TabularSampler obj's underlying member method code with example usage: obj.replace_features(samples, allowed_rows, uniq_feat_ids, partial_anchor_rows, nb_partial_anchors, num_samples) without return types