class_docstr: DropHighPSIFeatures drops features which Population Stability Index (PSI) value is
  above a given threshold. The PSI of a numerical feature is an indication of the
  shift in its distribution; a feature with high PSI could therefore be considered
  unstable.
  
  A bigger PSI value indicates a bigger shift in the feature distribution.
  
  Different thresholds can be used to assess the magnitude of the distribution shift
  according to the PSI value. The most commonly used thresholds are:
  
  - Below 10%, the variable has not experienced a significant shift.
  - Above 25%, the variable has experienced a major shift.
  - Between those two values, the shift is intermediate.
  
  To compute the PSI the DropHighPSIFeatures splits the dataset in two:
  
  First and foremost, the user should enter one variable which will be used to guide
  the data split. This variable can be of any data type. If the user does not enter a
  variable name, DropHighPSIFeatures will use the dataframe index.
  
  Second, the user has the option to specify a proportion of observations to put in
  each data set, or alternatively, provide a cut-off value.
  
  If the user specifies a proportion through the `split_frac` parameter, the data will
  be sorted to accommodate that proportion. If `split_frac` is 0.5, 50% of the
  observations will go to either basis or test sets. If `split_frac` is 0.6, 60% of
  the samples will go to the basis data set and the remaining 40% to the test set.
  
  If `split_distinct` is True, the data will be sorted considering unique values in
  the selected variables. Check the parameter below for more details.
  
  If the user defines a numeric cut-off value or a specific date using the `cut_off`
  parameter, the observations with value <= cut-off will go to the basis data set and
  the remaining ones to the test set. For categorical values this means they are
  sorted alphabetically and cut accordingly.
  
  If the user passes a list of values in the `cut-off`, the observations with the
  values in the list, will go to the basis set, and the remaining ones to the test
  set.
  
  More details in the :ref:`User Guide <psi_selection>`.
  
  Parameters
  ----------
  split_col: string or int, default=None.
      The variable that will be used to split the dataset into the basis and test
      sets. If None, the dataframe index will be used. `split_col` can be a numerical,
      categorical or datetime variable. If `split_col` is a categorical variable, and
      the splitting criteria is given by `split_frac`, it will be assumed that the
      labels of the variable are sorted alphabetically.
  
  split_frac: float, default=0.5.
      The proportion of observations in each of the basis and test dataframes. If
      `split_frac` is 0.6, 60% of the observations will be put in the basis data set.
  
      If `split_distinct` is True, the indicated fraction may not be achieved exactly.
      See parameter `split_distinct` for more details.
  
      If `cut_off` is not None, `split_frac` will be ignored and the data split based
      on the `cut_off` value.
  
  split_distinct: boolean, default=False.
      If True, `split_frac` is applied to the vector of unique values in `split_col`
      instead of being applied to the whole vector of values. For example, if the
      values in `split_col` are [1, 1, 1, 1, 2, 2, 3, 4] and `split_frac` is
      0.5, we have the following:
      - `split_distinct=False` splits the vector in two equally sized parts:
      [1, 1, 1, 1] and [2, 2, 3, 4]. This involves that 2 dataframes with 4
      observations each are used for the PSI calculations.
      - `split_distinct=True` computes the vector of unique values in `split_col`
      ([1, 2, 3, 4]) and splits that vector in two equal parts: [1, 2] and [3, 4].
      The number of observations in the two dataframes used for the PSI calculations
      is respectively 6 ([1, 1, 1, 1, 2, 2]) and 2 ([3, 4]).
  
  cut_off: int, float, date or list, default=None
      Threshold to split the dataset based on the `split_col` variable. If int, float
      or date, observations where the `split_col` values are <= threshold will
      go to the basis data set and the rest to the test set. If `cut_off` is a list,
      the observations where the `split_col` values are within the list will go to the
      basis data set and the remaining observations to the test set. If `cut_off` is
      not None, this parameter will be used to split the data and `split_frac` will be
      ignored.
  
  switch: boolean, default=False.
      If True, the order of the 2 dataframes used to determine the PSI (basis and
      test) will be switched. This is important because the PSI is not symmetric,
      i.e., PSI(a, b) != PSI(b, a)).
  
  threshold: float, str, default = 0.25.
      The threshold to drop a feature. If the PSI for a feature is >= threshold, the
      feature will be dropped. The most common threshold values are 0.25 (large shift)
      and 0.10 (medium shift).
      If 'auto', the threshold will be calculated based on the size of the base and
      target dataset and the number of bins.
  
  bins: int, default = 10
      Number of bins or intervals. For continuous features with good value spread, 10
      bins is commonly used. For features with lower cardinality or highly skewed
      distributions, lower values may be required.
  
  strategy: string, default='equal_frequency'
      If the intervals into which the features should be discretized are of equal
      size or equal number of observations. Takes values "equal_width" for equally
      spaced bins or "equal_frequency" for bins based on quantiles, that is, bins
      with similar number of observations.
  
  min_pct_empty_bins: float, default = 0.0001
      Value to add to empty bins or intervals. If after sorting the variable
      values into bins, a bin is empty, the PSI cannot be determined. By adding a
      small number to empty bins, we can avoid this issue. Note, that if the value
      added is too large, it may disturb the PSI calculation.
  
  missing_values: str, default='raise'
      Whether to perform the PSI feature selection on a dataframe with missing values.
      Takes values 'raise' or 'ignore'. If 'ignore', missing values will be dropped
      when determining the PSI for that particular feature. If 'raise' the transformer
      will raise an error and features will not be selected.
  
  {variables}
  
  {confirm_variables}
  
  Attributes
  ----------
  features_to_drop_:
      List with the features that will be dropped.
  
  {variables_}
  
  psi_values_:
      Dictionary containing the PSI value per feature.
  
  cut_off_:
      Value used to split the dataframe into basis and test.
      This value is computed when not given as parameter.
  
  {feature_names_in_}
  
  {n_features_in_}
  
  Methods
  -------
  fit:
      Find features with high PSI values.
  
  {fit_transform}
  
  {get_support}
  
  transform:
      Remove features with high PSI values.
  
  See Also
  --------
  feature_engine.discretisation.EqualFrequencyDiscretiser
  feature_engine.discretisation.EqualWidthDiscretiser
  
  References
  ----------
  .. [1] Yurdakul B. "Statistical properties of population stability index".
     Western Michigan University, 2018.
     https://scholarworks.wmich.edu/dissertations/3208/
docstr: Find features with high PSI values.
  
  Parameters
  ----------
  X : pandas dataframe of shape = [n_samples, n_features]
      The training dataset.
  
  y : pandas series. Default = None
      y is not needed in this transformer. You can pass y or None.
docstr_tok: ["Find","features","with","high","PSI","values",".","Parameters","--","--","--","--","--","X",":","pandas","dataframe","of","shape","=","[","n_samples",",","n_features","]","The","training","dataset",".","y",":","pandas","series",".","Default","=","None","y","is","not","needed","in","this","transformer",".","You","can","pass","y","or","None","."]
code: def fit(self, X: pd.DataFrame, y: pd.Series = None):
      """
      Find features with high PSI values.
  
      Parameters
      ----------
      X : pandas dataframe of shape = [n_samples, n_features]
          The training dataset.
  
      y : pandas series. Default = None
          y is not needed in this transformer. You can pass y or None.
      """
      # check input dataframe
      X = check_X(X)
  
      # If required exclude variables that are not in the input dataframe
      self._confirm_variables(X)
  
      # find numerical variables or check those entered are present in the dataframe
      self.variables_ = _find_or_check_numerical_variables(
          X, self.variables_
      )
  
      # Remove the split_col from the variables list. It might be added if the
      # variables are not defined at initialization.
      if self.split_col in self.variables_:
          self.variables_.remove(self.split_col)
  
      if self.missing_values == "raise":
          # check if dataset contains na or inf
          _check_contains_na(X, self.variables_)
          _check_contains_inf(X, self.variables_)
  
      # Split the dataframe into basis and test.
      basis_df, test_df = self._split_dataframe(X)
  
      # Check the shape of the returned dataframes for PSI calculations.
      # The number of observations must be at least equal to the
      # number of bins.
      if min(basis_df.shape[0], test_df.shape[0]) < self.bins:
          raise ValueError(
              "The number of rows in the basis and test datasets that will be used "
              f"in the PSI calculations must be at least larger than {self.bins}. "
              "After slitting the original dataset based on the given cut_off or"
              f"split_frac we have {basis_df.shape[0]} samples in the basis set, "
              f"and {test_df.shape[0]} samples in the test set. "
              "Please adjust the value of the cut_off or split_frac."
          )
  
      # Switch basis and test dataframes if required.
      if self.switch:
          test_df, basis_df = basis_df, test_df
  
      if self.threshold == "auto":
          threshold = self._calculate_auto_threshold(
              basis_df.shape[0], test_df.shape[0]
          )
      else:
          threshold = self.threshold
  
      # set up the discretizer
      if self.strategy == "equal_width":
          bucketer = EqualWidthDiscretiser(bins=self.bins)
      else:
          bucketer = EqualFrequencyDiscretiser(q=self.bins)
  
      # Compute the PSI by looping over the features
      self.psi_values_ = {}
      self.features_to_drop_ = []
  
      for feature in self.variables_:
          # Discretize the features.
  
          basis_discrete = bucketer.fit_transform(
              basis_df[[feature]].dropna()
          )
          test_discrete = bucketer.transform(
              test_df[[feature]].dropna()
          )
  
          # Determine percentage of observations per bin
          (
              basis_distrib,
              test_distrib,
          ) = self._observation_frequency_per_bin(
              basis_discrete, test_discrete
          )
  
          # Calculate the PSI value
          self.psi_values_[feature] = np.sum(
              (test_distrib - basis_distrib)
              * np.log(test_distrib / basis_distrib)
          )
          # Assess if feature should be dropped
          if self.psi_values_[feature] > threshold:
              self.features_to_drop_.append(feature)
  
      # save input features
      self._get_feature_names_in(X)
  
      return self
  
imports: import datetime
  from typing import List, Union
  import numpy
  import pandas
  import scipy.stats
  from pandas.api.types import is_numeric_dtype
  from feature_engine._docstrings.fit_attributes import _feature_names_in_docstring, _n_features_in_docstring
  from feature_engine._docstrings.methods import _fit_transform_docstring
  from feature_engine._docstrings.substitute import Substitution
  from feature_engine._variable_handling.init_parameter_checks import _check_init_parameter_variables
  from feature_engine._variable_handling.variable_type_selection import _find_or_check_numerical_variables
  from feature_engine.dataframe_checks import _check_contains_inf, _check_contains_na, check_X
  from feature_engine.discretisation import EqualFrequencyDiscretiser, EqualWidthDiscretiser
  from feature_engine.selection._docstring import _get_support_docstring, _variables_attribute_docstring, _variables_numerical_docstring
  from feature_engine.selection.base_selector import BaseSelector
label_desc: Use image node_id 2 for calling the DropHighPSIFeatures obj's underlying member method code with example usage: obj.fit(X, y) and returns: self