docstr: Performs time-aware recursive feature elimination based on permutation feature importance. While time remains, compute feature importance
  score for as many features as possible over at least min_fi_samples validation datapoints, discard features whose score is less than or
  equal to prune_threshold, fit the model on those features and keep the feature subset if its validation score is better, and repeat.
  
  Parameters
  ----------
  X : pd.DataFrame
      Training data features
  y : pd.Series
      Training data labels
  X_val : pd.DataFrame, default None
      Validation data features. Can be None.
  y_val : pd.Series, default None
      Validation data labels. Can be None.
  n_train_subsample : int, default 50000
      If the training dataset has more than this amount of datapoints, sample this many datapoints without replacement and use
      them as feature selection model training data. If None, do not use subsampling.
  n_fi_subsample : int, default 10000
      Sample this many data points and shuffle them when computing permutation feature importance. If this number is higher than
      the number of feature importance data points, set n_fi_subsample = number of feature importance datapoints.
  prune_threshold : Tuple[float,str], default 'noise'
      Consider features whose feature importance scores are below this threshold for pruning. Can be one of ['noise', 'none', float]. If set
      to 'noise', a synthetic columns whose values come from standard normal distribution will be appended to the original dataset, and any
      features whose feature importance score is lower than the synthetic feature's score will be considered for pruning. If set to 'none',
      all features will be considered for pruning (where up to prune_ratio features are pruned at a time). If set to float, any feature whose
      feature importance is lower than that threshold will be considered for pruning.
  prune_ratio : float, default 0.05
      Prune up to prune_ratio * number of current features at once whose feature importance scores are below prune_threshold when
      generating new feature subset candidates. At least one feature is always removed if there are any feature whose importance
      score is below prune_threshold.
  stopping_round : int, default 10
      If the validation scores of models fit on pruned data do not improve for stopping_round feature pruning rounds, end the pruning procedure.
      If None, continue feature pruning until time is up.
  min_improvement : int, default = 1e-6
      The newly fitted model's validation score must be >= (1 + min_improvement) * best validation score seen so far for its input
      feature subset to be considered to be superior to the previous feature subset.
  max_fits : int, default None
      If this many models have been fitted during feature pruning, exit the feature pruning loop. Can potentially prevent overfitting.
      We refit the model using the remaining features after each round of feature pruning. If None, continue feature pruning until time is up.
  
  Returns
  -------
  candidate_features : List[str]
      Optimal feature subset selected by this method. Set to original features if no features are below pruning threshold or we run
      out of time before finding a better feature subset.
docstr_tok: ["Performs","time-aware","recursive","feature","elimination","based","on","permutation","feature","importance",".","While","time","remains",",","compute","feature","importance","score","for","as","many","features","as","possible","over","at","least","min_fi_samples","validation","datapoints",",","discard","features","whose","score","is","less","than","or","equal","to","prune_threshold",",","fit","the","model","on","those","features","and","keep","the","feature","subset","if","its","validation","score","is","better",",","and","repeat",".","Parameters","--","--","--","--","--","X",":","pd.DataFrame","Training","data","features","y",":","pd.Series","Training","data","labels","X_val",":","pd.DataFrame",",","default","None","Validation","data","features",".","Can","be","None",".","y_val",":","pd.Series",",","default","None","Validation","data","labels",".","Can","be","None",".","n_train_subsample",":","int",",","default","50000","If","the","training","dataset","has","more","than","this","amount","of","datapoints",",","sample","this","many","datapoints","without","replacement","and","use","them","as","feature","selection","model","training","data",".","If","None",",","do","not","use","subsampling",".","n_fi_subsample",":","int",",","default","10000","Sample","this","many","data","points","and","shuffle","them","when","computing","permutation","feature","importance",".","If","this","number","is","higher","than","the","number","of","feature","importance","data","points",",","set","n_fi_subsample","=","number","of","feature","importance","datapoints",".","prune_threshold",":","Tuple","[","float",",","str","]",",","default","'noise'","Consider","features","whose","feature","importance","scores","are","below","this","threshold","for","pruning",".","Can","be","one","of","[","'noise","'",",","'none","'",",","float","]",".","If","set","to","'noise","'",",","a","synthetic","columns","whose","values","come","from","standard","normal","distribution","will","be","appended","to","the","original","dataset",",","and","any","features","whose","feature","importance","score","is","lower","than","the","synthetic","feature","'s","score","will","be","considered","for","pruning",".","If","set","to","'none","'",",","all","features","will","be","considered","for","pruning","(","where","up","to","prune_ratio","features","are","pruned","at","a","time",")",".","If","set","to","float",",","any","feature","whose","feature","importance","is","lower","than","that","threshold","will","be","considered","for","pruning",".","prune_ratio",":","float",",","default","0.05","Prune","up","to","prune_ratio","*","number","of","current","features","at","once","whose","feature","importance","scores","are","below","prune_threshold","when","generating","new","feature","subset","candidates",".","At","least","one","feature","is","always","removed","if","there","are","any","feature","whose","importance","score","is","below","prune_threshold",".","stopping_round",":","int",",","default","10","If","the","validation","scores","of","models","fit","on","pruned","data","do","not","improve","for","stopping_round","feature","pruning","rounds",",","end","the","pruning","procedure",".","If","None",",","continue","feature","pruning","until","time","is","up",".","min_improvement",":","int",",","default","=","1e-6","The","newly","fitted","model","'s","validation","score","must","be",">","=","(","1","+","min_improvement",")","*","best","validation","score","seen","so","far","for","its","input","feature","subset","to","be","considered","to","be","superior","to","the","previous","feature","subset",".","max_fits",":","int",",","default","None","If","this","many","models","have","been","fitted","during","feature","pruning",",","exit","the","feature","pruning","loop",".","Can","potentially","prevent","overfitting",".","We","refit","the","model","using","the","remaining","features","after","each","round","of","feature","pruning",".","If","None",",","continue","feature","pruning","until","time","is","up",".","Returns","--","--","--","-","candidate_features",":","List","[","str","]","Optimal","feature","subset","selected","by","this","method",".","Set","to","original","features","if","no","features","are","below","pruning","threshold","or","we","run","out","of","time","before","finding","a","better","feature","subset","."]
code: def select_features(
      self,
      X: pd.DataFrame,
      y: pd.Series,
      X_val: pd.DataFrame = None,
      y_val: pd.Series = None,
      n_train_subsample: int = 50000,
      n_fi_subsample: int = 10000,
      prune_threshold: float = "noise",
      prune_ratio: float = 0.05,
      stopping_round: int = 10,
      min_improvement: float = 1e-6,
      max_fits: int = None,
      **kwargs,
  ) -> List[str]:
      """
      Performs time-aware recursive feature elimination based on permutation feature importance. While time remains, compute feature importance
      score for as many features as possible over at least min_fi_samples validation datapoints, discard features whose score is less than or
      equal to prune_threshold, fit the model on those features and keep the feature subset if its validation score is better, and repeat.
  
      Parameters
      ----------
      X : pd.DataFrame
          Training data features
      y : pd.Series
          Training data labels
      X_val : pd.DataFrame, default None
          Validation data features. Can be None.
      y_val : pd.Series, default None
          Validation data labels. Can be None.
      n_train_subsample : int, default 50000
          If the training dataset has more than this amount of datapoints, sample this many datapoints without replacement and use
          them as feature selection model training data. If None, do not use subsampling.
      n_fi_subsample : int, default 10000
          Sample this many data points and shuffle them when computing permutation feature importance. If this number is higher than
          the number of feature importance data points, set n_fi_subsample = number of feature importance datapoints.
      prune_threshold : Tuple[float,str], default 'noise'
          Consider features whose feature importance scores are below this threshold for pruning. Can be one of ['noise', 'none', float]. If set
          to 'noise', a synthetic columns whose values come from standard normal distribution will be appended to the original dataset, and any
          features whose feature importance score is lower than the synthetic feature's score will be considered for pruning. If set to 'none',
          all features will be considered for pruning (where up to prune_ratio features are pruned at a time). If set to float, any feature whose
          feature importance is lower than that threshold will be considered for pruning.
      prune_ratio : float, default 0.05
          Prune up to prune_ratio * number of current features at once whose feature importance scores are below prune_threshold when
          generating new feature subset candidates. At least one feature is always removed if there are any feature whose importance
          score is below prune_threshold.
      stopping_round : int, default 10
          If the validation scores of models fit on pruned data do not improve for stopping_round feature pruning rounds, end the pruning procedure.
          If None, continue feature pruning until time is up.
      min_improvement : int, default = 1e-6
          The newly fitted model's validation score must be >= (1 + min_improvement) * best validation score seen so far for its input
          feature subset to be considered to be superior to the previous feature subset.
      max_fits : int, default None
          If this many models have been fitted during feature pruning, exit the feature pruning loop. Can potentially prevent overfitting.
          We refit the model using the remaining features after each round of feature pruning. If None, continue feature pruning until time is up.
  
      Returns
      -------
      candidate_features : List[str]
          Optimal feature subset selected by this method. Set to original features if no features are below pruning threshold or we run
          out of time before finding a better feature subset.
      """
      logger.log(
          20,
          f"Performing feature pruning with model: {self.model_name}, total time limit: {round(self.time_limit, 2)}s, "
          f"stop threshold: {stopping_round}, prune ratio: {prune_ratio}, prune threshold: {prune_threshold}.",
      )
      original_features = X.columns.tolist()
      if len(original_features) <= 1:
          logger.log(
              20,
              f"\tSkipping feature pruning since there is less than 2 features in the dataset.",
          )
          return original_features
      (
          X,
          y,
          X_val,
          y_val,
          X_fi,
          y_fi,
          prune_threshold,
          noise_columns,
          feature_metadata,
      ) = self.setup(
          X=X,
          y=y,
          X_val=X_val,
          y_val=y_val,
          n_train_subsample=n_train_subsample,
          prune_threshold=prune_threshold,
          **kwargs,
      )
      kwargs["feature_metadata"] = feature_metadata
      try:
          index = 1
          candidate_features = X.columns.tolist()
          best_info = {
              "features": candidate_features,
              "index": 1,
              "model": None,
              "score": None,
          }
          curr_model, score, fit_score_time = self.fit_score_model(
              X,
              y,
              X_val,
              y_val,
              candidate_features,
              f"{self.model_name}_1",
              **kwargs,
          )
          best_info["model"], best_info["score"] = curr_model, score
  
          time_budget_fi = self.compute_time_budget_fi(
              X_fi=X_fi, n_subsample=n_fi_subsample, **kwargs
          )
          logger.log(
              20,
              f"\tExpected model fit time: {round(fit_score_time, 2)}s, and expected candidate generation time: {round(time_budget_fi, 2)}s.",
          )
          logger.log(
              20,
              f"\tRound 1 of feature pruning model fit ({round(fit_score_time, 2)}s):\n"
              f"\t\tValidation score of the model fit on original features is ({round(best_info['score'], 4)}).",
          )
          time_remaining = self.time_limit - (
              time.time() - self.time_start
          )
          if time_remaining < self.fit_score_time + time_budget_fi:
              logger.warning(
                  f"\tNo time to perform the next pruning round (remaining: {time_remaining}, needed: {self.fit_score_time + time_budget_fi})."
              )
              raise TimeLimitExceeded
  
          importance_df = None
          while True:
              index = index + 1
              model_name = f"{self.model_name}_{index}"
              prioritize_fi = set(noise_columns)
              fn_args = {
                  "X": X_fi,
                  "y": y_fi,
                  "model": best_info["model"],
                  "time_budget": time_budget_fi,
                  "features": best_info["features"],
                  "n_subsample": n_fi_subsample,
                  "prune_threshold": prune_threshold,
                  "prune_ratio": prune_ratio,
                  "prioritized": prioritize_fi,
              }
              fn_args.update(self.get_extra_fn_args(**kwargs))
              (
                  candidate_features,
                  importance_df,
                  success,
                  prune_time,
              ) = self.compute_next_candidate(
                  fn_args=fn_args,
                  round_time_budget=time_budget_fi,
                  prev_best_features=best_info["features"],
                  prev_importance_df=importance_df,
              )
              if not success:
                  logger.log(
                      20,
                      f"\tTime is up while computing feature importance or there are no more features to prune. Ending...",
                  )
                  break
              curr_model, score, fit_score_time = self.fit_score_model(
                  X,
                  y,
                  X_val,
                  y_val,
                  candidate_features,
                  model_name,
                  **kwargs,
              )
  
              new_feature_count = len(candidate_features) - (
                  1 if len(noise_columns) > 0 else 0
              )
              prev_feature_count = len(best_info["features"]) - (
                  1 if len(noise_columns) > 0 else 0
              )
              if score >= best_info["score"] * (1 + min_improvement):
                  logger.log(
                      20,
                      f"\tRound {index} of feature pruning model fit ({round(fit_score_time, 2)}s):\n"
                      f"\t\tValidation score of the current model fit on {new_feature_count} features ({round(score, 4)}) is better than "
                      f"validation score of the best model fit on {prev_feature_count} features ({round(best_info['score'], 4)}). Updating model.",
                  )
                  best_info["model"].delete_from_disk(silent=True)
                  best_info = {
                      "model": curr_model,
                      "features": candidate_features,
                      "score": score,
                      "index": index,
                  }
              else:
                  logger.log(
                      20,
                      f"\tRound {index} of feature pruning model fit ({round(fit_score_time, 2)}s):\n"
                      f"\t\tValidation score of the current model fit on {new_feature_count} features ({round(score, 4)}) is not better than "
                      f"validation score of the best model fit on {prev_feature_count} features ({round(best_info['score'], 4)}). Retrying.",
                  )
                  curr_model.delete_from_disk(silent=True)
  
              time_remaining = self.time_limit - (
                  time.time() - self.time_start
              )
              if max_fits is not None and index >= max_fits:
                  logger.log(
                      20,
                      f"\tReached maximum number of allowed fits, {max_fits}, during feature selection. Ending...",
                  )
                  break
              if (
                  stopping_round is not None
                  and index - best_info["index"] >= stopping_round
              ):
                  logger.log(
                      20,
                      f"\tScore has not improved for {stopping_round} feature pruning rounds. Ending...",
                  )
                  break
              if time_remaining <= self.fit_score_time + prune_time:
                  logger.log(
                      20,
                      f"\tInsufficient time to finish next pruning round. Ending...",
                  )
                  break
      except TimeLimitExceeded:
          logger.log(
              20,
              f"\tTime limit exceeded while pruning features. Ending...",
          )
      except Exception as e:
          logger.error(traceback.format_exc())
          logger.error(
              f"\tERROR: Exception raised during feature pruning. Reason: {e}. Ending..."
          )
          if self.raise_exception:
              raise e
  
      if len(noise_columns) > 0:
          best_info["features"] = [
              feature
              for feature in best_info["features"]
              if feature not in noise_columns
          ]
      if isinstance(best_info["model"], AbstractModel):
          best_info["model"].delete_from_disk(silent=True)
      logger.log(
          20,
          f"\tSuccessfully ended prune loop after {index} feature pruning rounds ({round(time.time() - self.time_start, 2)}s).",
      )
      logger.log(
          20,
          f"\tFeature count before/after feature pruning: {len(original_features)} -> {len(best_info['features'])}.",
      )
      return best_info["features"]
  
imports: import logging
  import time
  import traceback
  import uuid
  from copy import deepcopy
  from typing import List, Set, Tuple, Union
  import numpy
  import pandas
  from autogluon.common.features.types import R_FLOAT
  from ..models.abstract.abstract_model import AbstractModel
  from ..models.ensemble.bagged_ensemble_model import BaggedEnsembleModel
  from ..utils.exceptions import TimeLimitExceeded
  from ..utils.utils import generate_train_test_split, unevaluated_fi_df_template
label_desc: Use image node_id 2 for calling the FeatureSelector obj's underlying member method code with example usage: obj.select_features(X, y, X_val, y_val, n_train_subsample, n_fi_subsample, prune_threshold, prune_ratio, stopping_round, min_improvement, max_fits) and returns: best_info, original_features