code: def private_categorical_binning(
      X_col, sample_weight, noise_scale, max_bins, rng=None
  ):
      native = Native.get_native_singleton()
      # Initialize estimate
      X_col = X_col.astype("U")
      uniq_vals, uniq_idxs = np.unique(X_col, return_inverse=True)
      weights = np.bincount(
          uniq_idxs, weights=sample_weight, minlength=len(uniq_vals)
      )
  
      weights = weights + native.generate_gaussian_random(
          rng=rng, stddev=noise_scale, count=weights.shape[0]
      )
  
      # Postprocess to ensure realistic bin values (min=0)
      weights = np.clip(weights, 0, None)
  
      # Collapse bins until target_weight is achieved.
      sample_weight_total = (
          len(X_col) if sample_weight is None else np.sum(sample_weight)
      )
      target_weight = sample_weight_total / max_bins
      small_bins = np.where(weights < target_weight)[0]
      if len(small_bins) > 0:
          other_weight = np.sum(weights[small_bins])
          mask = np.ones(weights.shape, dtype=bool)
          mask[small_bins] = False
  
          # Collapse all small bins into "DPOther"
          uniq_vals = np.append(uniq_vals[mask], "DPOther")
          weights = np.append(weights[mask], other_weight)
  
          if other_weight < target_weight:
              if len(weights) == 1:
                  # since we're adding unbounded random noise, it's possible that the total weight is less than the
                  # threshold required for a single bin.  It could in theory even be negative.
                  # clip to the target_weight
                  weights[0] = target_weight
              else:
                  # If "DPOther" bin is too small, absorb 1 more bin (guaranteed above threshold)
                  collapse_bin = np.argmin(weights[:-1])
                  mask = np.ones(weights.shape, dtype=bool)
                  mask[collapse_bin] = False
  
                  # Pack data into the final "DPOther" bin
                  weights[-1] += weights[collapse_bin]
  
                  # Delete absorbed bin
                  uniq_vals = uniq_vals[mask]
                  weights = weights[mask]
  
      return uniq_vals, weights
  
file_path: interpret/python/interpret-core/interpret/utils/_privacy.py
imports: import numpy
  from scipy.stats import norm
  from scipy.optimize import brentq
  from ._native import Native
  import logging
label_desc: Use image node_id 6 for calling a global function with example usage: private_categorical_binning(X_col, sample_weight, noise_scale, max_bins, rng) and returns: uniq_vals,  weights