code: def get_he_preprocessor(
      X: np.ndarray,
      feature_names: List[str],
      category_map: Dict[int, List[str]],
      feature_types: Optional[Dict[str, type]] = None,
  ) -> Tuple[
      Callable[[np.ndarray], np.ndarray],
      Callable[[np.ndarray], np.ndarray],
  ]:
      """
      Heterogeneous dataset preprocessor. The numerical features are standardized and the categorical features
      are one-hot encoded.
  
      Parameters
      ----------
      X
          Data to fit.
      feature_names
          List of feature names. This should be provided by the dataset.
      category_map
          Dictionary of category mapping. The keys are column indexes and the values are lists containing the
          possible feature values. This should be provided by the dataset.
      feature_types
          Dictionary of type for the numerical features.
  
      Returns
      -------
      preprocessor
          Data preprocessor.
      inv_preprocessor
          Inverse data preprocessor (e.g., `inv_preprocessor(preprocessor(x)) = x` )
      """
      if feature_types is None:
          feature_types = dict()
  
      # Separate columns in numerical and categorical
      categorical_ids = list(category_map.keys())
      numerical_ids = [
          i
          for i in range(len(feature_names))
          if i not in category_map.keys()
      ]
  
      # Define standard scaler and one-hot encoding transformations
      num_transf = StandardScaler()
      cat_transf = OneHotEncoder(
          categories=[range(len(x)) for x in category_map.values()],
          handle_unknown="ignore",
      )
  
      # Define preprocessor
      preprocessor = ColumnTransformer(
          transformers=[
              ("num", num_transf, numerical_ids),
              ("cat", cat_transf, categorical_ids),
          ],
          sparse_threshold=0,
      )
      preprocessor.fit(X)
  
      num_feat_ohe = len(numerical_ids)  # number of numerical columns
      cat_feat_ohe = sum(
          [len(v) for v in category_map.values()]
      )  # number of categorical columns
  
      # Define inverse preprocessor
      def get_inv_preprocessor(X_ohe: np.ndarray):
          X_inv = []
  
          if "num" in preprocessor.named_transformers_ and len(
              numerical_ids
          ):
              num_transf = preprocessor.named_transformers_["num"]
              X_ohe_num = (
                  X_ohe[:, :num_feat_ohe]
                  if preprocessor.transformers[0][0] == "num"
                  else X_ohe[:, -num_feat_ohe:]
              )
              X_inv.append(num_transf.inverse_transform(X_ohe_num))
  
          if "cat" in preprocessor.named_transformers_ and len(
              categorical_ids
          ):
              cat_transf = preprocessor.named_transformers_["cat"]
              X_ohe_cat = (
                  X_ohe[:, :cat_feat_ohe]
                  if preprocessor.transformers[0][0] == "cat"
                  else X_ohe[:, -cat_feat_ohe:]
              )
              X_inv.append(cat_transf.inverse_transform(X_ohe_cat))
  
          # Concatenate all columns. At this point the columns are not ordered correctly
          np_X_inv = np.concatenate(X_inv, axis=1)
  
          # Construct permutation to order the columns correctly
          perm = [
              i
              for i in range(len(feature_names))
              if i not in category_map.keys()
          ]
          perm += [
              i
              for i in range(len(feature_names))
              if i in category_map.keys()
          ]
  
          inv_perm = [0] * len(perm)
          for i in range(len(perm)):
              inv_perm[perm[i]] = i
  
          # Permute columns and cast to object
          np_X_inv = np_X_inv[:, inv_perm].astype(object)
  
          # Cast numerical features to desired data types
          for i, fn in enumerate(feature_names):
              if i in numerical_ids:
                  ft_type = (
                      feature_types[fn]
                      if fn in feature_types
                      else float
                  )
  
                  # Round `int` type features to the closest integer number to avoid rounding error when casting to `int`.
                  # The casting to `np.float32` is due to previous casting to `object` which raises an error when
                  # applying `np.rint` (i.e., 'TypeError: loop of ufunc does not support argument 0 of type float which
                  # has no callable rint method')
                  if ft_type == int:
                      np_X_inv[:, i] = np.rint(
                          np_X_inv[:, i].astype(np.float32)
                      )
  
              else:
                  ft_type = int  # for categorical features
  
              np_X_inv[:, i] = np_X_inv[:, i].astype(ft_type)
  
          return np_X_inv
  
      return preprocessor.transform, get_inv_preprocessor
  
imports: from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, Union
  import numpy
  import pandas
  from scipy.special import softmax
  from sklearn.compose import ColumnTransformer
  from sklearn.preprocessing import OneHotEncoder, StandardScaler
label_desc: Use image node_id 9 for calling a global function with example usage: get_he_preprocessor(X, feature_names, category_map, feature_types) and returns: preprocessor,  get_inv_preprocessor, np_X_inv