code: def convert_categorical_to_continuous(categories):
      # we do automagic detection of feature types by default, and sometimes a feature which
      # was really continuous might have most of it's data as one or two values.  An example would
      # be a feature that we have "0" and "1" in the training data, but "-0.1" and "3.1" are also
      # possible.  If during prediction we see a "3.1" we can magically convert our categories
      # into a continuous range with a cut point at 0.5.  Now "-0.1" goes into the [-inf, 0.5) bin
      # and 3.1 goes into the [0.5, +inf] bin.
      #
      # We can't convert a continuous feature that has cuts back into categoricals
      # since the categorical value could have been anything between the cuts that we know about.
  
      clusters = dict()
      non_float_idxs = set()
  
      old_min = np.nan
      old_max = np.nan
      for category, idx in categories.items():
          try:
              # this strips leading and trailing spaces
              val = float(category)
          except ValueError:
              non_float_idxs.add(idx)
              continue
  
          if isnan(val) or isinf(val):
              continue
  
          if isnan(old_min) or val < old_min:
              old_min = val
          if isnan(old_max) or old_max < val:
              old_max = val
  
          cluster_list = clusters.get(idx)
          if cluster_list is None:
              clusters[idx] = [val]
          else:
              cluster_list.append(val)
  
      # there's a super fringe case where two category strings map to the same bin, but
      # one of them is a float and the other is a non-float.  Normally, we'd include the
      # non-float categorical in the unknowns, but in this case we'd need to include
      # a part of a bin.  Handling this just adds too much complexity for the benefit
      # and you could argue that the evidence from the other models is indicating that
      # the string should be closer to zero of the weight from the floating point bin
      # so we take the simple route of putting all the weight into the float and none on the
      # non-float.  We still need to remove any indexes though that map to both a float
      # and a non-float, so this line handles that
      non_float_idxs = [
          idx for idx in non_float_idxs if idx not in clusters
      ]
      non_float_idxs.append(max(categories.values()) + 1)
  
      if len(clusters) <= 1:
          return np.empty(0, np.float64)
  
      cluster_bounds = []
      for cluster_list in clusters.values():
          cluster_list.sort()
          cluster_bounds.append((cluster_list[0], cluster_list[-1]))
  
      # TODO: move everything below here into C++ to ensure cross language compatibility
  
      cluster_bounds.sort()
  
      cuts = []
      cluster_iter = iter(cluster_bounds)
      low = next(cluster_iter)[-1]
      for cluster in cluster_iter:
          high = cluster[0]
          if low < high:
              # if they are equal or if low is higher then we can't separate one cluster
              # from another, so we keep joining them until we can get clean separations
  
              half_diff = (high - low) / 2
              if isinf(half_diff):
                  # first try to subtract then divide since that's more accurate but some float64
                  # values will fail eg (max_float - min_float == +inf) so we need to try
                  # a less accurate way of dividing first if we detect this.  Dividing
                  # first will always succeed, even with the most extreme possible values of
                  # max_float / 2 - min_float / 2
                  half_diff = high / 2 - low / 2
  
              # floats have more precision the smaller they are,
              # so use the smaller number as the anchor
              if abs(low) <= abs(high):
                  mid = low + half_diff
              else:
                  mid = high - half_diff
  
              if mid <= low:
                  # this can happen with very small half_diffs that underflow the add/subtract operation
                  # if this happens the numbers must be very close together on the order of a float tick.
                  # We use lower bound inclusive for our cut discretization, so make the mid == high
                  mid = high
  
              cuts.append(mid)
          low = max(low, cluster[-1])
      cuts = np.array(cuts, np.float64)
  
      mapping = [[] for _ in range(len(cuts) + 3)]
      for old_idx, cluster_list in clusters.items():
          # all the items in a cluster should be binned into the same bins
          new_idx = (
              np.searchsorted(cuts, cluster_list[:1], side="right")[0]
              + 1
          )
          mapping[new_idx].append(old_idx)
  
      mapping[0].append(0)
      mapping[-1] = non_float_idxs
  
      return cuts, mapping, old_min, old_max
  
imports: from math import ceil, isnan, isinf, exp, log
  from ...utils._native import Native
  from ._tensor import restore_missing_value_zeros
  import numpy
  import warnings
  from itertools import islice
  import logging
label_desc: Use image node_id 2 for calling a global function with example usage: convert_categorical_to_continuous(categories) and returns: cuts,  mapping,  old_min,  old_max, np