docstr: Apply :attr:`f` to `datasets` to produce updated datasets. If `f` requests any
  merged/coupled datasets (as reflected by :attr:`params`), conversions to accommodate those
  requests will take place here
  
  Parameters
  ----------
  **datasets: DFDict
      Original dict of datasets, containing all datasets, some of which may be superfluous, or
      may require additional processing to resolve merged/coupled datasets
  
  Returns
  -------
  new_datasets: DFDict
      Dict of datasets, which have been updated by :attr:`f`. Any datasets that may have been
      merged prior to being given to :attr:`f` have been split back into the original
      datasets, with the updates made by :attr:`f`
code: def __call__(self, **datasets: DFDict) -> DFDict:
      """Apply :attr:`f` to `datasets` to produce updated datasets. If `f` requests any
      merged/coupled datasets (as reflected by :attr:`params`), conversions to accommodate those
      requests will take place here
  
      Parameters
      ----------
      **datasets: DFDict
          Original dict of datasets, containing all datasets, some of which may be superfluous, or
          may require additional processing to resolve merged/coupled datasets
  
      Returns
      -------
      new_datasets: DFDict
          Dict of datasets, which have been updated by :attr:`f`. Any datasets that may have been
          merged prior to being given to :attr:`f` have been split back into the original
          datasets, with the updates made by :attr:`f`"""
      if self.do_validate:
          self.original_hashes = hash_datasets(datasets)
  
      datasets_for_f = self.get_datasets_for_f(datasets)
      step_result = self.f(**datasets_for_f)
      step_result = (
          (step_result,)
          if not isinstance(step_result, tuple)
          else step_result
      )
  
      if len(step_result) == len(self.params) + 1:
          self.inversion, step_result = (
              step_result[-1],
              step_result[:-1],
          )
  
      new_datasets = dict(zip(self.params, step_result))
      for dataset_name, dataset_value in new_datasets.items():
          if dataset_name in self.merged_datasets:
              new_datasets = dict(
                  new_datasets, **split_merged_df(dataset_value)
              )
      new_datasets = dict(datasets, **new_datasets)
  
      if self.do_validate:
          self.updated_hashes = hash_datasets(new_datasets)
      return new_datasets
  
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 2 for calling the EngineerStep obj's underlying member method code with example usage: obj.__call__() and returns: new_datasets