docstr: Class to organize feature engineering step callables `steps` (:class:`EngineerStep`
  instances) and the datasets that the steps request and return.
  
  Parameters
  ----------
  steps: List, or None, default=None
      List of arbitrary length, containing any of the following values:
  
          1. :class:`EngineerStep` instance,
          2. Function to provide as input to :class:`EngineerStep`, or
          3. :class:`~hyperparameter_hunter.space.dimensions.Categorical`, with `categories`
             comprising a selection of the previous two `steps` values (optimization only)
  
      The third value can only be used during optimization. The `feature_engineer` provided to
      :class:`~hyperparameter_hunter.experiments.CVExperiment`, for example, may only contain
      the first two values. To search a space optionally including an `EngineerStep`, use the
      `optional` kwarg of :class:`~hyperparameter_hunter.space.dimensions.Categorical`.
  
      See :class:`EngineerStep` for information on properly formatted `EngineerStep`
      functions. Additional engineering steps may be added via :meth:`add_step`
  do_validate: Boolean, or "strict", default=False
      ... Experimental...
      Whether to validate the datasets resulting from feature engineering steps. If True,
      hashes of the new datasets will be compared to those of the originals to ensure they
      were actually modified. Results will be logged. If `do_validate` = "strict", an
      exception will be raised if any anomalies are found, rather than logging a message. If
      `do_validate` = False, no validation will be performed
  **datasets: DFDict
      This is not expected to be provided on initialization and is offered primarily for
      debugging/testing. Mapping of datasets necessary to perform feature engineering steps
  
  See Also
  --------
  :class:`EngineerStep`
      For proper formatting of non-`Categorical` values of `steps`
  
  Notes
  -----
  If `steps` does include any instances of
  :class:`hyperparameter_hunter.space.dimensions.Categorical`, this `FeatureEngineer` instance
  will not be usable by Experiments. It can only be used by Optimization Protocols.
  Furthermore, the `FeatureEngineer` that the Optimization Protocol actually ends up using
  will not pass identity checks against the original `FeatureEngineer` that contained
  `Categorical` steps
  
  Examples
  --------
  >>> from sklearn.preprocessing import StandardScaler, MinMaxScaler, QuantileTransformer
  >>> # Define some engineer step functions to play with
  >>> def s_scale(train_inputs, non_train_inputs):
  ...     s = StandardScaler()
  ...     train_inputs[train_inputs.columns] = s.fit_transform(train_inputs.values)
  ...     non_train_inputs[train_inputs.columns] = s.transform(non_train_inputs.values)
  ...     return train_inputs, non_train_inputs
  >>> def mm_scale(train_inputs, non_train_inputs):
  ...     s = MinMaxScaler()
  ...     train_inputs[train_inputs.columns] = s.fit_transform(train_inputs.values)
  ...     non_train_inputs[train_inputs.columns] = s.transform(non_train_inputs.values)
  ...     return train_inputs, non_train_inputs
  >>> def q_transform(train_targets, non_train_targets):
  ...     t = QuantileTransformer(output_distribution="normal")
  ...     train_targets[train_targets.columns] = t.fit_transform(train_targets.values)
  ...     non_train_targets[train_targets.columns] = t.transform(non_train_targets.values)
  ...     return train_targets, non_train_targets, t
  >>> def sqr_sum(all_inputs):
  ...     all_inputs["square_sum"] = all_inputs.agg(
  ...         lambda row: np.sqrt(np.sum([np.square(_) for _ in row])), axis="columns"
  ...     )
  ...     return all_inputs
  
  *FeatureEngineer steps wrapped by `EngineerStep` == raw function steps - as long as the
  `EngineerStep` is using the default parameters*
  
  >>> # FeatureEngineer steps wrapped by `EngineerStep` == raw function steps
  >>> #   ... As long as the `EngineerStep` is using the default parameters
  >>> fe_0 = FeatureEngineer([sqr_sum, s_scale])
  >>> fe_1 = FeatureEngineer([EngineerStep(sqr_sum), EngineerStep(s_scale)])
  >>> fe_0.steps == fe_1.steps
  True
  >>> fe_2 = FeatureEngineer([sqr_sum, EngineerStep(s_scale), q_transform])
  
  *`Categorical` can be used during optimization and placed anywhere in `steps`. `Categorical`
  can also handle either `EngineerStep` categories or raw functions. Use the `optional` kwarg
  of `Categorical` to test some questionable steps*
  
  >>> fe_3 = FeatureEngineer([sqr_sum, Categorical([s_scale, mm_scale]), q_transform])
  >>> fe_4 = FeatureEngineer([Categorical([sqr_sum], optional=True), s_scale, q_transform])
  >>> fe_5 = FeatureEngineer([
  ...     Categorical([sqr_sum], optional=True),
  ...     Categorical([EngineerStep(s_scale), mm_scale]),
  ...     q_transform
  ... ])
code: def __init__(self, steps=None, do_validate=False, **datasets: DFDict):
      """Class to organize feature engineering step callables `steps` (:class:`EngineerStep`
      instances) and the datasets that the steps request and return.
  
      Parameters
      ----------
      steps: List, or None, default=None
          List of arbitrary length, containing any of the following values:
  
              1. :class:`EngineerStep` instance,
              2. Function to provide as input to :class:`EngineerStep`, or
              3. :class:`~hyperparameter_hunter.space.dimensions.Categorical`, with `categories`
                 comprising a selection of the previous two `steps` values (optimization only)
  
          The third value can only be used during optimization. The `feature_engineer` provided to
          :class:`~hyperparameter_hunter.experiments.CVExperiment`, for example, may only contain
          the first two values. To search a space optionally including an `EngineerStep`, use the
          `optional` kwarg of :class:`~hyperparameter_hunter.space.dimensions.Categorical`.
  
          See :class:`EngineerStep` for information on properly formatted `EngineerStep`
          functions. Additional engineering steps may be added via :meth:`add_step`
      do_validate: Boolean, or "strict", default=False
          ... Experimental...
          Whether to validate the datasets resulting from feature engineering steps. If True,
          hashes of the new datasets will be compared to those of the originals to ensure they
          were actually modified. Results will be logged. If `do_validate` = "strict", an
          exception will be raised if any anomalies are found, rather than logging a message. If
          `do_validate` = False, no validation will be performed
      **datasets: DFDict
          This is not expected to be provided on initialization and is offered primarily for
          debugging/testing. Mapping of datasets necessary to perform feature engineering steps
  
      See Also
      --------
      :class:`EngineerStep`
          For proper formatting of non-`Categorical` values of `steps`
  
      Notes
      -----
      If `steps` does include any instances of
      :class:`hyperparameter_hunter.space.dimensions.Categorical`, this `FeatureEngineer` instance
      will not be usable by Experiments. It can only be used by Optimization Protocols.
      Furthermore, the `FeatureEngineer` that the Optimization Protocol actually ends up using
      will not pass identity checks against the original `FeatureEngineer` that contained
      `Categorical` steps
  
      Examples
      --------
      >>> from sklearn.preprocessing import StandardScaler, MinMaxScaler, QuantileTransformer
      >>> # Define some engineer step functions to play with
      >>> def s_scale(train_inputs, non_train_inputs):
      ...     s = StandardScaler()
      ...     train_inputs[train_inputs.columns] = s.fit_transform(train_inputs.values)
      ...     non_train_inputs[train_inputs.columns] = s.transform(non_train_inputs.values)
      ...     return train_inputs, non_train_inputs
      >>> def mm_scale(train_inputs, non_train_inputs):
      ...     s = MinMaxScaler()
      ...     train_inputs[train_inputs.columns] = s.fit_transform(train_inputs.values)
      ...     non_train_inputs[train_inputs.columns] = s.transform(non_train_inputs.values)
      ...     return train_inputs, non_train_inputs
      >>> def q_transform(train_targets, non_train_targets):
      ...     t = QuantileTransformer(output_distribution="normal")
      ...     train_targets[train_targets.columns] = t.fit_transform(train_targets.values)
      ...     non_train_targets[train_targets.columns] = t.transform(non_train_targets.values)
      ...     return train_targets, non_train_targets, t
      >>> def sqr_sum(all_inputs):
      ...     all_inputs["square_sum"] = all_inputs.agg(
      ...         lambda row: np.sqrt(np.sum([np.square(_) for _ in row])), axis="columns"
      ...     )
      ...     return all_inputs
  
      *FeatureEngineer steps wrapped by `EngineerStep` == raw function steps - as long as the
      `EngineerStep` is using the default parameters*
  
      >>> # FeatureEngineer steps wrapped by `EngineerStep` == raw function steps
      >>> #   ... As long as the `EngineerStep` is using the default parameters
      >>> fe_0 = FeatureEngineer([sqr_sum, s_scale])
      >>> fe_1 = FeatureEngineer([EngineerStep(sqr_sum), EngineerStep(s_scale)])
      >>> fe_0.steps == fe_1.steps
      True
      >>> fe_2 = FeatureEngineer([sqr_sum, EngineerStep(s_scale), q_transform])
  
      *`Categorical` can be used during optimization and placed anywhere in `steps`. `Categorical`
      can also handle either `EngineerStep` categories or raw functions. Use the `optional` kwarg
      of `Categorical` to test some questionable steps*
  
      >>> fe_3 = FeatureEngineer([sqr_sum, Categorical([s_scale, mm_scale]), q_transform])
      >>> fe_4 = FeatureEngineer([Categorical([sqr_sum], optional=True), s_scale, q_transform])
      >>> fe_5 = FeatureEngineer([
      ...     Categorical([sqr_sum], optional=True),
      ...     Categorical([EngineerStep(s_scale), mm_scale]),
      ...     q_transform
      ... ])
      """
      self.steps = []
      self.do_validate = do_validate
      self.datasets = datasets or {}
  
      for step in steps or []:
          self.add_step(step)
  
imports: from hyperparameter_hunter.keys.hashing import make_hash_sha256
  from hyperparameter_hunter.space.dimensions import Categorical, RejectedOptional
  from hyperparameter_hunter.utils.boltons_utils import remap, default_visit, default_enter
  from hyperparameter_hunter.utils.general_utils import subdict
  import ast
  from contextlib import suppress
  from inspect import getsource
  import numpy
  import pandas
  from typing import List, Callable, Dict, Union, Tuple
label_desc: Use image node_id 1 to create a new FeatureEngineer object  with example: obj = FeatureEngineer(steps, do_validate)