class_docstr: Double Ensemble Model
docstr: the FS module of Double Ensemble
  :param df_train: the shape is NxF
  :param loss_values: the shape is N
  the loss of the current ensemble on the i-th sample.
  :return: res_feat: in the form of pandas.Index
docstr_tok: ["the","FS","module","of","Double","Ensemble",":","param","df_train",":","the","shape","is","NxF",":","param","loss_values",":","the","shape","is","N","the","loss","of","the","current","ensemble","on","the","i-th","sample",".",":","return",":","res_feat",":","in","the","form","of","pandas.Index"]
code: def feature_selection(self, df_train, loss_values):
      """
      the FS module of Double Ensemble
      :param df_train: the shape is NxF
      :param loss_values: the shape is N
      the loss of the current ensemble on the i-th sample.
      :return: res_feat: in the form of pandas.Index
  
      """
      x_train, y_train = df_train["feature"], df_train["label"]
      features = x_train.columns
      N, F = x_train.shape
      g = pd.DataFrame({"g_value": np.zeros(F, dtype=float)})
      M = len(self.ensemble)
  
      # shuffle specific columns and calculate g-value for each feature
      x_train_tmp = x_train.copy()
      for i_f, feat in enumerate(features):
          x_train_tmp.loc[:, feat] = np.random.permutation(
              x_train_tmp.loc[:, feat].values
          )
          pred = pd.Series(np.zeros(N), index=x_train_tmp.index)
          for i_s, submodel in enumerate(self.ensemble):
              pred += (
                  pd.Series(
                      submodel.predict(
                          x_train_tmp.loc[
                              :, self.sub_features[i_s]
                          ].values
                      ),
                      index=x_train_tmp.index,
                  )
                  / M
              )
          loss_feat = self.get_loss(
              y_train.values.squeeze(), pred.values
          )
          g.loc[i_f, "g_value"] = np.mean(loss_feat - loss_values) / (
              np.std(loss_feat - loss_values) + 1e-7
          )
          x_train_tmp.loc[:, feat] = x_train.loc[:, feat].copy()
  
      # one column in train features is all-nan # if g['g_value'].isna().any()
      g["g_value"].replace(np.nan, 0, inplace=True)
  
      # divide features into bins_fs bins
      g["bins"] = pd.cut(g["g_value"], self.bins_fs)
  
      # randomly sample features from bins to construct the new features
      res_feat = []
      sorted_bins = sorted(g["bins"].unique(), reverse=True)
      for i_b, b in enumerate(sorted_bins):
          b_feat = features[g["bins"] == b]
          num_feat = int(np.ceil(self.sample_ratios[i_b] * len(b_feat)))
          res_feat = (
              res_feat
              + np.random.choice(
                  b_feat, size=num_feat, replace=False
              ).tolist()
          )
      return pd.Index(set(res_feat))
  
imports: import lightgbm
  import numpy
  import pandas
  from typing import Text, Union
  from ...model.base import Model
  from ...data.dataset import DatasetH
  from ...data.dataset.handler import DataHandlerLP
  from ...model.interpret.base import FeatureInt
  from ...log import get_module_logger
label_desc: Use image node_id 6 for calling the DEnsembleModel obj's underlying member method code with example usage: obj.feature_selection(df_train, loss_values) and returns: pd