docstr: Post-hoc method to encourage sparsity in a generated counterfactuals.
  
  :param final_cfs_sparse: Final CFs in original user-fed format, in a pandas dataframe.
  :param query_instance: Query instance in original user-fed format, in a pandas dataframe.
  :param posthoc_sparsity_param: Parameter for the post-hoc operation on continuous features to enhance sparsity.
  :param posthoc_sparsity_algorithm: Perform either linear or binary search.
                                     Prefer binary search when a feature range is
                                     large (for instance, income varying from 10k to 1000k)
                                     and only if the features share a monotonic relationship
                                     with predicted outcome in the model.
  :param limit_steps_ls: Defines the limit of steps to be done in the linear search,
                          necessary to avoid infinite loops
code: def do_posthoc_sparsity_enhancement(
      self,
      final_cfs_sparse,
      query_instance,
      posthoc_sparsity_param,
      posthoc_sparsity_algorithm,
      limit_steps_ls,
  ):
      """Post-hoc method to encourage sparsity in a generated counterfactuals.
  
      :param final_cfs_sparse: Final CFs in original user-fed format, in a pandas dataframe.
      :param query_instance: Query instance in original user-fed format, in a pandas dataframe.
      :param posthoc_sparsity_param: Parameter for the post-hoc operation on continuous features to enhance sparsity.
      :param posthoc_sparsity_algorithm: Perform either linear or binary search.
                                         Prefer binary search when a feature range is
                                         large (for instance, income varying from 10k to 1000k)
                                         and only if the features share a monotonic relationship
                                         with predicted outcome in the model.
      :param limit_steps_ls: Defines the limit of steps to be done in the linear search,
                              necessary to avoid infinite loops
      """
      if final_cfs_sparse is None:
          return final_cfs_sparse
  
      # quantiles of the deviation from median for every continuous feature
      quantiles = self.data_interface.get_quantiles_from_training_data(
          quantile=posthoc_sparsity_param
      )
      mads = self.data_interface.get_valid_mads()
      # Setting the quantile of a feature to be the minimum of mad and quantile
      # Thus, the maximum deviation can be mad.
      for feature in quantiles:
          quantiles[feature] = min(quantiles[feature], mads[feature])
  
      # Sorting features such that the feature with the highest quantile deviation
      # is first
      features_sorted = sorted(
          quantiles.items(), key=lambda kv: kv[1], reverse=True
      )
      for ix in range(len(features_sorted)):
          features_sorted[ix] = features_sorted[ix][0]
      precs = self.data_interface.get_decimal_precisions()
      decimal_prec = dict(
          zip(self.data_interface.continuous_feature_names, precs)
      )
  
      cfs_preds_sparse = []
  
      for cf_ix in list(final_cfs_sparse.index):
          current_pred = self.predict_fn_for_sparsity(
              final_cfs_sparse.loc[[cf_ix]][
                  self.data_interface.feature_names
              ]
          )
          for feature in features_sorted:
              # current_pred = self.predict_fn_for_sparsity(final_cfs_sparse.iat[[cf_ix]][self.data_interface.feature_names])
              # feat_ix = self.data_interface.continuous_feature_names.index(feature)
              diff = (
                  query_instance[feature].iat[0]
                  - final_cfs_sparse.at[cf_ix, feature]
              )
              if abs(diff) <= quantiles[feature]:
                  if posthoc_sparsity_algorithm == "linear":
                      final_cfs_sparse = self.do_linear_search(
                          diff,
                          decimal_prec,
                          query_instance,
                          cf_ix,
                          feature,
                          final_cfs_sparse,
                          current_pred,
                          limit_steps_ls,
                      )
  
                  elif posthoc_sparsity_algorithm == "binary":
                      final_cfs_sparse = self.do_binary_search(
                          diff,
                          decimal_prec,
                          query_instance,
                          cf_ix,
                          feature,
                          final_cfs_sparse,
                          current_pred,
                      )
  
          temp_preds = self.predict_fn_for_sparsity(
              final_cfs_sparse.loc[[cf_ix]][
                  self.data_interface.feature_names
              ]
          )
          cfs_preds_sparse.append(temp_preds[0])
      final_cfs_sparse[
          self.data_interface.outcome_name
      ] = self.get_model_output_from_scores(cfs_preds_sparse)
      # final_cfs_sparse[self.data_interface.outcome_name] = np.round(final_cfs_sparse[self.data_interface.outcome_name], 3)
      return final_cfs_sparse
  
imports: import pickle
  from abc import ABC, abstractmethod
  from collections.abc import Iterable
  from typing import Any, List
  import numpy
  import pandas
  from raiutils.exceptions import UserConfigValidationException
  from sklearn.neighbors import KDTree
  from tqdm import tqdm
  from dice_ml.constants import ModelTypes, _PostHocSparsityTypes
  from dice_ml.counterfactual_explanations import CounterfactualExplanations
label_desc: Use image node_id 13 for calling the ExplainerBase obj's underlying member method code with example usage: obj.do_posthoc_sparsity_enhancement(final_cfs_sparse, query_instance, posthoc_sparsity_param, posthoc_sparsity_algorithm, limit_steps_ls) and returns: final_cfs_sparse, final_cfs_sparse