code: def unify_columns(
      X,
      requests,
      feature_names_in,
      feature_types=None,
      min_unique_continuous=0,
      go_fast=False,
  ):
      # If the requests paramter contains a categories dictionary, then that same categories object is guaranteed to
      # be yielded back to the caller.  This guarantee can be used to rapidly identify which request is being
      # yielded by using the id(categories) along with the feature_idx
  
      if isinstance(X, np.ndarray):  # this includes ma.masked_array
          if issubclass(X.dtype.type, np.complexfloating):
              msg = "Complex data not supported"
              _log.error(msg)
              raise TypeError(msg)
          elif issubclass(X.dtype.type, np.void):
              msg = "np.void data not supported"
              _log.error(msg)
              raise TypeError(msg)
  
          # TODO: in the future special case this to make single samples faster at predict time
  
          n_cols = X.shape[1]
          col_map = None
          if n_cols != len(feature_names_in):
              # during fit time unify_feature_names would only allow us to get here if this was legal, which requires
              # feature_types to not be None.  During predict time feature_types_in cannot be None, but we need
              # to check for legality on the dimensions of X
              keep_cols = np.fromiter(
                  (val != "ignore" for val in feature_types),
                  np.bool_,
                  count=len(feature_types),
              )
              if n_cols != keep_cols.sum():
                  # called under: predict
                  msg = f"The model has {len(feature_types)} features, but X has {n_cols} columns"
                  _log.error(msg)
                  raise ValueError(msg)
              col_map = np.empty(len(feature_types), np.int64)
              np.place(
                  col_map,
                  keep_cols,
                  np.arange(len(feature_types), dtype=np.int64),
              )
  
          # TODO: I'm not sure that simply checking X.flags.c_contiguous handles all the situations that we'd want
          # to know about some data.  If we recieved a transposed array that was C ordered how would that look?
          # so read up on this more
          # https://numpy.org/doc/stable/reference/arrays.ndarray.html#internal-memory-layout-of-an-ndarray
          # https://numpy.org/doc/stable/reference/arrays.interface.html
          # memoryview
  
          # TODO: create a C++ transposer that takes the stride length between items, so we can pass in 1 for bytes
          # 2 for int16, 4 for int32, 8 for int64 and special case those sizes to be fast.  We can then also transpose
          # np.object_ and np.unicode by passing in whatever lengths those are, which we can get from numpy reliably
          # Inisde C++ we can use a templated function that takes the stride length or 0, so we'll get compiled
          # versions that specialize the 1,2,4,8 sizes, and use memcpy to make the cell copies.  memcpy is an
          # intrinsic that'll optimize down to avoid loops when possible, so that should give us fast results.
          #
          # For some reason numpy really sucks at transposing data and asfortranarray makes it slower, so let's do it ourselves.
          # Allocate an empty fortran array here in python and have C++ fill it.  Then we can keep all the
          # rest of the code below the same since it'll just be accessed internally more efficiently.
          # if go_fast and X.flags.c_contiguous:
          #    # called under: predict
          #    # during predict we don't care as much about memory consumption, so speed it by transposing everything
          #    X = np.asfortranarray(X)
  
          for feature_idx, categories in requests:
              col_idx = (
                  feature_idx
                  if col_map is None
                  else col_map[feature_idx]
              )
              X_col = X[:, col_idx]
              feature_type = (
                  None
                  if feature_types is None
                  else feature_types[feature_idx]
              )
              (
                  feature_type_in,
                  X_col,
                  categories,
                  bad,
              ) = _process_numpy_column(
                  X_col, categories, feature_type, min_unique_continuous
              )
              yield feature_type_in, X_col, categories, bad
      elif _pandas_installed and isinstance(X, pd.DataFrame):
          names_original = X.columns
          names_dict = dict(zip(map(str, names_original), count()))
          n_cols = len(names_original)
          if len(names_dict) != n_cols:
              # this can happen if for instance one column is "0" and annother is int(0)
              # Pandas also allows duplicate labels by default:
              # https://pandas.pydata.org/docs/user_guide/duplicates.html#duplicates-disallow
              # we can tollerate duplicate labels here, provided none of them are being used by our model
              for name, n_count in Counter(
                  map(str, names_original)
              ).items():
                  if n_count != 1:
                      del names_dict[name]
  
          if feature_types is None:
              for feature_name_in in feature_names_in:
                  if feature_name_in not in names_dict:
                      names_dict = None
                      break
          else:
              for feature_name_in, feature_type in zip(
                  feature_names_in, feature_types
              ):
                  if (
                      feature_type != "ignore"
                      and feature_name_in not in names_dict
                  ):
                      names_dict = None
                      break
  
          if names_dict is None:
              if n_cols == len(feature_names_in):
                  names_dict = dict(zip(feature_names_in, count()))
              else:
                  # during fit time unify_feature_names would only allow us to get here if this was legal, which requires
                  # feature_types to not be None.  During predict time feature_types_in cannot be None, but we need
                  # to check for legality on the dimensions of X
                  names_dict = dict(
                      zip(
                          (
                              feature_name_in
                              for feature_name_in, feature_type in zip(
                                  feature_names_in, feature_types
                              )
                              if feature_type != "ignore"
                          ),
                          count(),
                      )
                  )
                  if n_cols != len(names_dict):
                      msg = f"The model has {len(feature_types)} features, but X has {n_cols} columns"
                      _log.error(msg)
                      raise ValueError(msg)
  
          # Pandas also sometimes uses a dense 2D ndarray instead of per column 1D ndarrays, which would benefit from
          # transposing, but accessing the BlockManager is currently unsupported behavior. They are also planning to eliminate
          # the BlockManager in Pandas2, so not much benefit in special casing this while they move in that direction
          # https://uwekorn.com/2020/05/24/the-one-pandas-internal.html
  
          for feature_idx, categories in requests:
              col_idx = names_dict[feature_names_in[feature_idx]]
              X_col = X.iloc[:, col_idx]
              feature_type = (
                  None
                  if feature_types is None
                  else feature_types[feature_idx]
              )
              (
                  feature_type_in,
                  X_col,
                  categories,
                  bad,
              ) = _process_pandas_column(
                  X_col, categories, feature_type, min_unique_continuous
              )
              yield feature_type_in, X_col, categories, bad
      elif _scipy_installed and isinstance(X, sp.sparse.spmatrix):
          n_cols = X.shape[1]
  
          col_map = None
          if n_cols != len(feature_names_in):
              # during fit time unify_feature_names would only allow us to get here if this was legal, which requires
              # feature_types to not be None.  During predict time feature_types_in cannot be None, but we need
              # to check for legality on the dimensions of X
              keep_cols = np.fromiter(
                  (val != "ignore" for val in feature_types),
                  np.bool_,
                  count=len(feature_types),
              )
              if n_cols != keep_cols.sum():
                  msg = f"The model has {len(feature_types)} features, but X has {n_cols} columns"
                  _log.error(msg)
                  raise ValueError(msg)
              col_map = np.empty(len(feature_types), np.int64)
              np.place(
                  col_map,
                  keep_cols,
                  np.arange(len(feature_types), dtype=np.int64),
              )
  
          for feature_idx, categories in requests:
              col_idx = (
                  feature_idx
                  if col_map is None
                  else col_map[feature_idx]
              )
              X_col = X.getcol(col_idx)
              feature_type = (
                  None
                  if feature_types is None
                  else feature_types[feature_idx]
              )
              (
                  feature_type_in,
                  X_col,
                  categories,
                  bad,
              ) = _process_scipy_column(
                  X_col, categories, feature_type, min_unique_continuous
              )
              yield feature_type_in, X_col, categories, bad
      elif _pandas_installed and isinstance(X, pd.Series):
          # TODO: handle as a single feature model
          msg = "X as pandas.Series is unsupported"
          _log.error(msg)
          raise ValueError(msg)
      elif isinstance(X, dict):
          for feature_idx, categories in requests:
              X_col = X[feature_names_in[feature_idx]]
              feature_type = (
                  None
                  if feature_types is None
                  else feature_types[feature_idx]
              )
              (
                  feature_type_in,
                  X_col,
                  categories,
                  bad,
              ) = _process_dict_column(
                  X_col, categories, feature_type, min_unique_continuous
              )
              yield feature_type_in, X_col, categories, bad
      else:
          msg = "internal error"
          _log.error(msg)
          raise ValueError(msg)
  
imports: from collections import Counter
  from itertools import count
  import numpy
  import numpy.ma
  import logging
label_desc: Use image node_id 13 for calling a global function with example usage: unify_columns(X, requests, feature_names_in, feature_types, min_unique_continuous, go_fast) without return types