docstr: :param coordinates: data frame containing having as columns the covariates/features
  :param expectations: data series containing the expectations/expected outcomes
  :param outcomes: data series containing the outcomes/observed outcomes
  :param penalty: penalty coefficient
  :param num_iters: number of iteration
  :param verbose: logging flag
  :param seed: numpy seed. Default equals 0
  :param mode: one of ['binary', 'continuous', 'nominal', 'ordinal']. Defaults to binary.
  :return: [best subset, best score]
docstr_tok: [":","param","coordinates",":","data","frame","containing","having","as","columns","the","covariates\/features",":","param","expectations",":","data","series","containing","the","expectations\/expected","outcomes",":","param","outcomes",":","data","series","containing","the","outcomes\/observed","outcomes",":","param","penalty",":","penalty","coefficient",":","param","num_iters",":","number","of","iteration",":","param","verbose",":","logging","flag",":","param","seed",":","numpy","seed",".","Default","equals","0",":","param","mode",":","one","of","[","'binary","'",",","'continuous","'",",","'nominal","'",",","'ordinal","'","]",".","Defaults","to","binary",".",":","return",":","[","best","subset",",","best","score","]"]
code: def scan(
      self,
      coordinates: pd.DataFrame,
      expectations: pd.Series,
      outcomes: pd.Series,
      penalty: float,
      num_iters: int,
      verbose: bool = False,
      seed: int = 0,
      mode: str = "binary",
  ):
      """
      :param coordinates: data frame containing having as columns the covariates/features
      :param expectations: data series containing the expectations/expected outcomes
      :param outcomes: data series containing the outcomes/observed outcomes
      :param penalty: penalty coefficient
      :param num_iters: number of iteration
      :param verbose: logging flag
      :param seed: numpy seed. Default equals 0
      :param mode: one of ['binary', 'continuous', 'nominal', 'ordinal']. Defaults to binary.
      :return: [best subset, best score]
      """
      np.random.seed(seed)
  
      # Reset indexes
      coordinates = coordinates.reset_index(drop=True)
      expectations = expectations.reset_index(drop=True)
      outcomes = outcomes.reset_index(drop=True)
  
      assert (
          len(coordinates) == len(expectations) == len(outcomes)
      ), f"Lengths of coordinates, expectations, and outcomes should be equal."
  
      # Check that the appropriate scoring function is used
  
      if isinstance(self.scoring_function, BerkJones):
          modes = ["binary", "continuous", "nominal", "ordinal"]
          assert (
              mode in modes
          ), f"Expected one of {modes} for BerkJones,  got {mode}."
  
          # Ensure that BerkJones only work in Autostrat mode
          unique_expectations = expectations.unique()
          if (
              isinstance(self.scoring_function, BerkJones)
              and len(unique_expectations) != 1
          ):
              raise Exception(
                  "BerkJones scorer supports scanning in autostrat mode only."
              )
  
          # Bin the continuous outcomes column for Berk Jones in continuous mode
          alpha = self.scoring_function.alpha
          direction = self.scoring_function.direction
  
          if mode == "continuous":
              quantile = outcomes.quantile(alpha)
              outcomes = (outcomes > quantile).apply(int)
  
          # Flip outcomes to scan in the negative direction for BerkJones
          # This is equivalent to switching the p-values
          if direction == "negative":
              outcomes = 1 - outcomes
  
      if isinstance(self.scoring_function, Bernoulli):
          modes = ["binary", "nominal"]
          assert (
              mode in modes
          ), f"Expected one of {modes} for Bernoulli,  got {mode}."
  
      if isinstance(self.scoring_function, Gaussian):
          assert (
              mode == "continuous"
          ), f"Expected continuous, got {mode}."
  
          # Set variance for Gaussian
          self.scoring_function.var = expectations.var()
  
          # Move entire distribution to the positive axis
          shift = np.abs(expectations.min()) + np.abs(outcomes.min())
          outcomes = outcomes + shift
          expectations = expectations + shift
  
      if isinstance(self.scoring_function, Poisson):
          modes = ["binary", "ordinal"]
          assert (
              mode in modes
          ), f"Expected one of {modes} for Poisson,  got {mode}."
  
      # initialize
      best_subset = {}
      best_score = -1e10
      best_scores = []
      for i in range(num_iters):
          # flags indicates that the method has optimized over subsets for a given attribute.
          # The iteration ends when it cannot further increase score by optimizing over
          # subsets of any attribute, i.e., when all flags are 1.
          flags = np.empty(len(coordinates.columns))
          flags.fill(0)
  
          # Starting subset. Note that we start with all values for the first iteration
          # and random values for succeeding iterations.
          current_subset = (
              get_entire_subset()
              if (i == 0)
              else get_random_subset(
                  coordinates, np.random.rand(1).item(), 10
              )
          )
  
          # score the entire population
          current_score = self.score_current_subset(
              coordinates=coordinates,
              expectations=expectations,
              outcomes=outcomes,
              penalty=penalty,
              current_subset=current_subset,
          )
  
          while flags.sum() < len(coordinates.columns):
              # choose random attribute that we haven't scanned yet
              attribute_number_to_scan = np.random.choice(
                  len(coordinates.columns)
              )
              while flags[attribute_number_to_scan]:
                  attribute_number_to_scan = np.random.choice(
                      len(coordinates.columns)
                  )
              attribute_to_scan = coordinates.columns.values[
                  attribute_number_to_scan
              ]
  
              # clear current subset of attribute values for that subset
              if attribute_to_scan in current_subset:
                  del current_subset[attribute_to_scan]
  
              # call get_aggregates and choose_aggregates to find best subset of attribute values
              (
                  aggregates,
                  thresholds,
                  all_observed_sum,
                  all_expectations,
              ) = self.get_aggregates(
                  coordinates=coordinates,
                  outcomes=outcomes,
                  expectations=expectations,
                  current_subset=current_subset,
                  column_name=attribute_to_scan,
                  penalty=penalty,
              )
  
              temp_names, temp_score = self.choose_aggregates(
                  aggregates=aggregates,
                  thresholds=thresholds,
                  penalty=penalty,
                  all_observed_sum=all_observed_sum,
                  all_expectations=all_expectations,
              )
  
              temp_subset = current_subset.copy()
              # if temp_names is not empty (or null)
              if temp_names:
                  temp_subset[attribute_to_scan] = temp_names
  
              # Note that this call to score_current_subset ensures that
              # we are penalizing complexity for all attribute values.
              # The value of temp_score computed by choose_aggregates
              # above includes only the penalty for the current attribute.
              temp_score = self.score_current_subset(
                  coordinates=coordinates,
                  expectations=expectations,
                  outcomes=outcomes,
                  penalty=penalty,
                  current_subset=temp_subset,
              )
  
              # reset flags to 0 if we have improved score
              if temp_score > current_score + 1e-6:
                  flags.fill(0)
  
              # sanity check to make sure score has not decreased
              # sanity check may not apply to Gaussian in penalized mode (TODO: to check Maths again)
              if (
                  not isinstance(self.scoring_function, Gaussian)
                  and penalty > 0
              ):
                  assert temp_score >= current_score - 1e-6, (
                      "WARNING SCORE HAS DECREASED from %.6f to %.6f"
                      % (
                          current_score,
                          temp_score,
                      )
                  )
  
              flags[attribute_number_to_scan] = 1
              current_subset = temp_subset
              current_score = temp_score
  
          # print out results for current iteration
          if verbose:
              print(
                  "Subset found on iteration",
                  i + 1,
                  "of",
                  num_iters,
                  "with score",
                  current_score,
                  ":",
              )
              print(current_subset)
  
          # update best_score and best_subset if necessary
          if current_score > best_score:
              best_subset = current_subset.copy()
              best_score = current_score
  
              if verbose:
                  print("Best score is now", best_score)
  
          elif verbose:
              print(
                  "Current score of",
                  current_score,
                  "does not beat best score of",
                  best_score,
              )
          best_scores.append(best_score)
      return best_subset, best_score
  
file_path: AIF360/aif360/detectors/mdss/MDSS.py
imports: from aif360.detectors.mdss.ScoringFunctions.ScoringFunction import ScoringFunction
  from aif360.detectors.mdss.generator import get_entire_subset, get_random_subset
  from aif360.detectors.mdss.ScoringFunctions import Bernoulli, BerkJones, Gaussian, ScoringFunction, Poisson
  import pandas
  import numpy
label_desc: Use image node_id 5 for calling the MDSS obj's underlying member method code with example usage: obj.scan(coordinates, expectations, outcomes, penalty, num_iters, verbose, seed, mode) and returns: best_subset,  best_score