code: def estimated_probability_density(
      x: tf.Tensor,
      boundaries: Optional[Union[tf.Tensor, int]] = None,
      categorical: bool = False,
      name: Optional[str] = None,
  ) -> tf.Tensor:
      """Computes an approximate probability density at each x, given the bins.
  
      Using this type of fixed-interval method has several benefits compared to
        bucketization, although may not always be preferred.
        1. Quantiles does not work on categorical data.
        2. The quantiles algorithm does not currently operate on multiple features
        jointly, only independently.
  
      Ex: Outlier detection in a multi-modal or arbitrary distribution.
        Imagine a value x where a simple model is highly predictive of a target y
        within certain densely populated ranges. Outside these ranges, we may want
        to treat the data differently, but there are too few samples for the model
        to detect them by case-by-case treatment.
        One option would be to use the density estimate for this purpose:
  
        outputs['x_density'] = tft.estimated_prob(inputs['x'], bins=100)
        outputs['outlier_x'] = tf.where(outputs['x_density'] < OUTLIER_THRESHOLD,
                                        tf.constant([1]), tf.constant([0]))
  
        This exercise uses a single variable for illustration, but a direct density
        metric would become more useful with higher dimensions.
  
      Note that we normalize by average bin_width to arrive at a probability density
      estimate. The result resembles a pdf, not the probability that a value falls
      in the bucket (except in the categorical case).
  
      Args:
        x: A `Tensor`.
        boundaries: (Optional) A `Tensor` or int used to approximate the density.
            If possible provide boundaries as a Tensor of multiple sorted values.
            Will default to 10 intervals over the 0-1 range, or find the min/max
            if an int is provided (not recommended because multi-phase analysis is
            inefficient). If the boundaries are known as potentially arbitrary
            interval boundaries, sizes are assumed to be equal. If the sizes are
            unequal, density may be inaccurate. Ignored if `categorical` is true.
        categorical: (Optional) A `bool` that will treat x as categorical if true.
        name: (Optional) A name for this operation.
  
      Returns:
        A `Tensor` the same shape as x, the probability density estimate at x (or
        probability mass estimate if `categorical` is True).
  
      Raises:
        NotImplementedError: If `x` is CompositeTensor.
      """
      with tf.compat.v1.name_scope(
          name, "estimated_probability_density"
      ):
          if isinstance(x, (tf.SparseTensor, tf.RaggedTensor)):
              raise NotImplementedError(
                  "estimated probability density does not support Composite Tensors"
              )
          if x.get_shape().ndims > 1 and x.shape[-1] > 1:
              raise NotImplementedError(
                  "estimated probability density does not support multiple dimensions"
              )
  
          counts, boundaries = analyzers.histogram(
              x, boundaries=boundaries, categorical=categorical
          )
  
          xdims = x.get_shape().ndims
          counts = tf.cast(counts, tf.float32)
          probabilities = counts / tf.reduce_sum(counts)
  
          x = tf.reshape(x, [-1])
  
          if categorical:
              bucket_indices = tf_utils.lookup_key(x, boundaries)
              bucket_densities = probabilities
          else:
              # We need to compute the bin width so that density does not depend on
              # number of intervals.
              bin_width = tf.cast(
                  boundaries[0, -1] - boundaries[0, 0], tf.float32
              ) / (tf.cast(tf.size(probabilities), tf.float32))
              bucket_densities = probabilities / bin_width
  
              bucket_indices = tf_utils.assign_buckets(
                  tf.cast(x, tf.float32),
                  analyzers.remove_leftmost_boundary(boundaries),
              )
          bucket_indices = tf_utils._align_dims(
              bucket_indices, xdims
          )  # pylint: disable=protected-access
  
          # In the categorical case, when keys are missing, the indices may be -1,
          # therefore we replace those with 0 in order to use tf.gather.
          adjusted_bucket_indices = tf.where(
              bucket_indices < 0,
              _fill_shape(0, tf.shape(bucket_indices), tf.int64),
              bucket_indices,
          )
          bucket_densities = tf.gather(
              bucket_densities, adjusted_bucket_indices
          )
          return tf.where(
              bucket_indices < 0,
              _fill_shape(0, tf.shape(bucket_indices), tf.float32),
              bucket_densities,
          )
  
imports: import os
  from typing import Any, Callable, Iterable, Optional, Tuple, Union
  import tensorflow
  from tensorflow_transform import analyzers
  from tensorflow_transform import common
  from tensorflow_transform import common_types
  from tensorflow_transform import gaussianization
  from tensorflow_transform import schema_inference
  from tensorflow_transform import tf_utils
  from tfx_bsl.types import tfx_namedtuple
label_desc: Use image node_id 38 for calling a global function with example usage: estimated_probability_density(x, boundaries, categorical, name) and returns: tf