code: def _densify_object_ndarray(X_col):
      # called under: fit or predict
  
      # numpy hierarchy of types
      # https://numpy.org/doc/stable/reference/arrays.scalars.html
  
      # TODO: add special case handling if there is only 1 sample to make that faster
  
      types = set(map(type, X_col))
      if len(types) == 1:
          if str in types:
              return X_col.astype(np.unicode_)
          elif bool in types:
              return X_col.astype(np.bool_)
  
      if all(
          one_type is int or issubclass(one_type, np.integer)
          for one_type in types
      ):
          if all(
              issubclass(one_type, np.unsignedinteger)
              for one_type in types
          ):
              if all(one_type is np.uint8 for one_type in types):
                  return X_col.astype(np.uint8)
              types.discard(np.uint8)
  
              if all(one_type is np.uint16 for one_type in types):
                  return X_col.astype(np.uint16)
              types.discard(np.uint16)
  
              if all(one_type is np.uint32 for one_type in types):
                  return X_col.astype(np.uint32)
  
              return X_col.astype(np.uint64)
  
          if all(one_type is np.int8 for one_type in types):
              return X_col.astype(np.int8)
          types.discard(np.int8)
  
          if all(
              one_type is np.uint8 or one_type is np.int16
              for one_type in types
          ):
              return X_col.astype(np.int16)
          types.discard(np.uint8)
          types.discard(np.int16)
  
          if all(
              one_type is np.uint16 or one_type is np.int32
              for one_type in types
          ):
              return X_col.astype(np.int32)
  
          try:
              return X_col.astype(np.int64)
          except OverflowError:
              # we must have a big number that can only be represented by np.uint64
              # AND also signed integers mixed together if we do X_col.astype(np.uint64),
              # it will silently convert negative integers to unsigned!
  
              # TODO : should this be np.float64 with a check for big integers
              return X_col.astype(np.unicode_)
  
      if all(
          one_type is float or issubclass(one_type, np.floating)
          for one_type in types
      ):
          if all(one_type is np.float16 for one_type in types):
              return X_col.astype(np.float16)
          types.discard(np.float16)
  
          if all(one_type is np.float32 for one_type in types):
              return X_col.astype(np.float32)
  
          return X_col.astype(np.float64)
  
      is_float_conversion = False
      for one_type in types:
          if one_type is str:
              pass  # str objects have __iter__, so special case this to allow
          elif one_type is int:
              pass  # int objects use the default __str__ function, so special case this to allow
          elif one_type is bool:
              pass  # bool objects use the default __str__ function, so special case this to allow
          elif one_type is float:
              is_float_conversion = True  # force to np.float64 to guarantee consistent string formatting
          elif issubclass(one_type, np.generic):
              # numpy objects have __getitem__, so special case this to allow
              if one_type is np.float64:
                  pass  # np.float64 is what we convert to for floats, so no need to convert this
              elif issubclass(one_type, np.floating):
                  is_float_conversion = True  # force to np.float64 to ensure consistent string formatting of floats
          elif one_type in _disallowed_types:
              # list of python types primarily from: https://docs.python.org/3/library/stdtypes.html
              msg = f"X contains the disallowed type {one_type}"
              _log.error(msg)
              raise TypeError(msg)
          elif hasattr(one_type, "__iter__") or hasattr(
              one_type, "__getitem__"
          ):
              # check for __iter__ and __getitem__ to filter out iterables
              # https://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-an-object-is-iterable
              msg = (
                  f"X contains the disallowed iterable type {one_type}"
              )
              _log.error(msg)
              raise TypeError(msg)
          elif hasattr(one_type, "__contains__"):
              msg = f"X contains the disallowed set type {one_type}"
              _log.error(msg)
              raise TypeError(msg)
          elif one_type.__str__ is object.__str__:
              # if any object in our list uses the default object __str__ function then it'll
              # include the id(val) pointer in the string text, which isn't going to be useful as a categorical
  
              # use type(val) instead of val.__str__ to detect inherited __str__ functions per:
              # https://stackoverflow.com/questions/19628421/how-to-check-if-str-is-implemented-by-an-object
  
              msg = f"X contains the type {one_type} which does not define a __str__ function"
              _log.error(msg)
              raise TypeError(msg)
  
      if is_float_conversion:
          # TODO: handle ints here too which need to be checked if they are larger than the safe int max value
  
          X_col = X_col.copy()
          places = np.fromiter(
              (
                  val_type is float or issubclass(val_type, np.floating)
                  for val_type in map(type, X_col)
              ),
              np.bool_,
              count=len(X_col),
          )
          np.place(X_col, places, X_col[places].astype(np.float64))
  
      # TODO: converting object types first to pd.CatigoricalDType is somewhat faster than our code here which converts
      # to unicode.  We should consider either using a CatigoricalDTypes conversion first if pandas is installed, or
      # writing our own cython code that can be more efficient at walking through items in an array.  If we write
      # our own cython there is the added advantage that we can check types in the same loop and therefore eliminate
      # the costly "set(map(type, X_col))" calls above
      return X_col.astype(np.unicode_)
  
imports: from collections import Counter
  from itertools import count
  import numpy
  import numpy.ma
  import logging
label_desc: Use image node_id 1 for calling a global function with example usage: _densify_object_ndarray(X_col) and returns: X_col, X_col, X_col, X_col, X_col, X_col, X_col, X_col, X_col, X_col, X_col, X_col, X_col, X_col, X_col