code: def attribute_anomaly_scores(
      anomaly_samples: np.ndarray,
      distribution_samples: np.ndarray,
      anomaly_scoring_func: Callable[[np.ndarray], np.ndarray],
      attribute_mean_deviation: bool,
      shapley_config: Optional[ShapleyConfig] = None,
  ) -> np.ndarray:
      """Estimates the contributions of the features for each sample in anomaly_samples to the anomaly score obtained
      by the anomaly_scoring_func. If attribute_mean_deviation is set to False, the anomaly score is based on the
      information theoretic (IT) score -log(P(g(X) >= g(x))), where g is the anomaly_scoring_func, X samples from the
      marginal distribution of the target_node and x an observation of the target_node in anomaly_samples. If
      attribute_mean_deviation is set to True, the contribution to g(x) - E[g(X)] is estimated instead, i.e. the
      feature relevance for the given scorer.
  
      Note that the anomaly scoring function needs to handle the dimension and modality of the data. An example for a
      function for multidimensional continues data would be:
          density_estimator = GaussianMixtureDensityEstimator()
          density_estimator.fit(original_observations)
          anomaly_scoring_func = lambda x, y: estimate_inverse_density_score(x, y, density_estimator)
  
      Related paper:
      Janzing, D., Budhathoki, K., Minorics, L., & Bloebaum, P. (2022).
      Causal structure based root cause analysis of outliers
      https://arxiv.org/abs/1912.02724
  
      :param anomaly_samples: Samples x for which the contributions are estimated. The dimensionality of these samples
                              doesn't matter as long as the anomaly_scoring_func supports it.
      :param distribution_samples: Samples from the (non-anomalous) distribution X.
      :param anomaly_scoring_func: A function g that takes a sample from X as input and returns an anomaly score.
      :param attribute_mean_deviation: If set to False, the contribution is estimated based on the IT score and if it is
                                       set to True, the contribution is based on the feature relevance with respect to the
                                       given scoring function.
      :param shapley_config: :class:`~dowhy.gcm.shapley.ShapleyConfig` for the Shapley estimator.
      :return: A numpy array with the feature contributions to the anomaly score for each sample in anomaly_samples.
      """
      if attribute_mean_deviation:
          expectation_of_score = np.mean(
              anomaly_scoring_func(distribution_samples)
          )
      else:
          anomaly_scores = anomaly_scoring_func(anomaly_samples)
  
      def set_function(subset: np.ndarray) -> Union[np.ndarray, float]:
          feature_samples = permute_features(
              distribution_samples,
              np.arange(0, subset.shape[0])[subset == 0],
              True,
          )
  
          result = np.zeros(anomaly_samples.shape[0])
          for i in range(anomaly_samples.shape[0]):
              feature_samples[:, subset == 1] = anomaly_samples[
                  i, subset == 1
              ]
  
              if attribute_mean_deviation:
                  # Usual feature relevance using the mean deviation as set function, i.e. g(x) - E[g(X)]
                  result[i] = (
                      np.mean(anomaly_scoring_func(feature_samples))
                      - expectation_of_score
                  )
              else:
                  result[i] = np.log(
                      _relative_frequency(
                          anomaly_scoring_func(feature_samples)
                          >= anomaly_scores[i]
                      )
                  )
  
          return result
  
      return estimate_shapley_values(
          set_function, anomaly_samples.shape[1], shapley_config
      )
  
file_path: dowhy/dowhy/gcm/anomaly.py
imports: from typing import Any, Callable, Dict, Optional, Union
  import numpy
  import pandas
  from numpy.matlib import repmat
  from tqdm import tqdm
  from dowhy.gcm import config
  from dowhy.gcm._noise import compute_noise_from_data, get_noise_dependent_function, noise_samples_of_ancestors
  from dowhy.gcm.anomaly_scorer import AnomalyScorer
  from dowhy.gcm.anomaly_scorers import MedianCDFQuantileScorer, RescaledMedianCDFQuantileScorer
  from dowhy.gcm.causal_mechanisms import ConditionalStochasticModel
  from dowhy.gcm.causal_models import InvertibleStructuralCausalModel, ProbabilisticCausalModel, validate_causal_dag
  from dowhy.gcm.shapley import ShapleyConfig, estimate_shapley_values
  from dowhy.gcm.stats import permute_features
  from dowhy.gcm.util.general import shape_into_2d
  from dowhy.graph import get_ordered_predecessors, is_root_node
label_desc: Use image node_id 4 for calling a global function with example usage: attribute_anomaly_scores(anomaly_samples, distribution_samples, anomaly_scoring_func, attribute_mean_deviation, shapley_config) and returns: estimate_shapley_values, result