code: def distribution_change(
      causal_model: ProbabilisticCausalModel,
      old_data: pd.DataFrame,
      new_data: pd.DataFrame,
      target_node: Any,
      invariant_nodes: List[Any] = None,
      num_samples: int = 2000,
      difference_estimation_func: Callable[
          [np.ndarray, np.ndarray], float
      ] = auto_estimate_kl_divergence,
      independence_test: Callable[
          [np.ndarray, np.ndarray], float
      ] = kernel_based,
      conditional_independence_test: Callable[
          [np.ndarray, np.ndarray, np.ndarray], float
      ] = kernel_based,
      mechanism_change_test_significance_level: float = 0.05,
      mechanism_change_test_fdr_control_method: Optional[
          str
      ] = "fdr_bh",
      auto_assignment_quality: Optional[AssignmentQuality] = None,
      return_additional_info: bool = False,
      shapley_config: Optional[ShapleyConfig] = None,
      graph_factory: Callable[[Any], DirectedGraph] = nx.DiGraph,
  ) -> Union[
      Dict[Any, float],
      Tuple[
          Dict[Any, float],
          Dict[Any, bool],
          ProbabilisticCausalModel,
          ProbabilisticCausalModel,
      ],
  ]:
      """Attributes the change in the marginal distribution of the target_node to nodes upstream in the causal DAG.
  
      Note that this method creates two copies of the causal DAG. The causal models of one causal DAG are learned from
      old data and those of another DAG are learned from new data.
  
      **Research Paper**:
      Kailash Budhathoki, Dominik Janzing, Patrick Bloebaum, Hoiyi Ng. *Why did the distribution change?*. Proceedings
      of The 24th International Conference on Artificial Intelligence and Statistics, PMLR 130:1666-1674, 2021.
  
      :param causal_model: Reference causal model.
      :param old_data: Joint samples from the 'old' distribution.
      :param new_data: Joint samples from the 'new' distribution.
      :param target_node: Target node of interest for attributing the marginal distribution change.
      :param invariant_nodes: List of nodes where the mechanism is kept constant regardless of changes in the
                              datasets being analyzed.
      :param num_samples: Number of samples used for estimating Shapley values. This can have a significant influence
                          on runtime and accuracy.
      :param difference_estimation_func: Function for quantifying the distribution change. This function should expect
                                         two inputs which represent samples from two different distributions,
                                         e.g. difference in average values.
      :param independence_test: Unconditional independence test. This is used to identify mechanism changes in root nodes.
      :param conditional_independence_test: Conditional independence test. This is used to identify mechanism changes in
                                            non-root nodes.
      :param mechanism_change_test_significance_level: A significance level for rejecting the null hypothesis that the
                                                       causal mechanism of a node has not changed.
      :param mechanism_change_test_fdr_control_method: The false discovery rate control method for mechanism change
                                                       tests. For more options, checkout `statsmodels manual
                                                       <https://www.statsmodels.org/dev/generated/statsmodels.stats.multitest.multipletests.html>`_.
      :param auto_assignment_quality: If set to None, the assigned models from the given causal models are used for the
                                      old and new graph. However, they are re-fitted on the given data.
                                      If set to a valid assignment quality, new models are automatically assigned to the
                                      old and new graph based on the respective data.
      :param return_additional_info: If set to True, three additional items are returned: a dictionary indicating
                                     whether each node's mechanism changed, the causal DAG whose causal models are
                                     learned from old data, and the causal DAG whose causal models are learned from new
                                     data.
      :param shapley_config: Configuration for the Shapley estimator.
      :param graph_factory: Allows customization in case a graph class different than networkx.DiGraph should be used.
                            This function *must* copy nodes and edges. Attributes of nodes will be overridden in the copy,
                            so the algorithm is independent of the attribute copy behavior of this factory.
      :return: By default, if `return_additional_info` is set to False, only the dictionary containing contribution of
               each upstream node is returned. If `return_additional_info` is set to True, three additional items are
               returned: a dictionary indicating whether each node's mechanism changed, the causal DAG whose causal models
               learned from old data, and the causal DAG whose causal models are learned from new data.
      """
      if invariant_nodes is None:
          invariant_nodes = []
      causal_graph_old = graph_factory(
          node_connected_subgraph_view(causal_model.graph, target_node)
      )
      causal_model_old = ProbabilisticCausalModel(causal_graph_old)
  
      if auto_assignment_quality is None:
          clone_causal_models(
              causal_model.graph, causal_model_old.graph
          )
      else:
          assign_causal_mechanisms(
              causal_model_old,
              old_data,
              override_models=True,
              quality=auto_assignment_quality,
          )
      invariant_nodes = list(
          set(invariant_nodes).intersection(set(causal_graph_old.nodes))
      )
      _remove_invariant_nodes(
          invariant_nodes,
          causal_model_old,
          old_data,
          auto_assignment_quality,
      )
  
      causal_graph_new = graph_factory(causal_graph_old)
      causal_model_new = ProbabilisticCausalModel(causal_graph_new)
      if auto_assignment_quality is None:
          clone_causal_models(causal_graph_old, causal_model_new.graph)
      else:
          assign_causal_mechanisms(
              causal_model_new,
              new_data,
              override_models=True,
              quality=auto_assignment_quality,
          )
  
      mechanism_changes = _fit_accounting_for_mechanism_change(
          causal_model_old,
          causal_model_new,
          old_data[list(causal_graph_old.nodes)],
          new_data[list(causal_graph_new.nodes)],
          independence_test,
          conditional_independence_test,
          mechanism_change_test_significance_level,
          mechanism_change_test_fdr_control_method,
          invariant_nodes,
      )
  
      attributions = distribution_change_of_graphs(
          causal_model_old,
          causal_model_new,
          target_node,
          num_samples,
          difference_estimation_func,
          shapley_config,
          graph_factory,
      )
      # set attributions to zero for left out invariant nodes
      for node in invariant_nodes:
          attributions[node] = 0
      if return_additional_info:
          return (
              attributions,
              mechanism_changes,
              causal_model_old,
              causal_model_new,
          )
      else:
          return attributions
  
imports: import logging
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
  import networkx
  import numpy
  import pandas
  from numpy.matlib import repmat
  from statsmodels.stats.multitest import multipletests
  from tqdm import tqdm
  from dowhy.gcm.auto import AssignmentQuality, assign_causal_mechanism_node, assign_causal_mechanisms
  from dowhy.gcm.causal_mechanisms import ConditionalStochasticModel
  from dowhy.gcm.causal_models import PARENTS_DURING_FIT, ProbabilisticCausalModel, clone_causal_models, validate_causal_dag
  from dowhy.gcm.divergence import auto_estimate_kl_divergence
  from dowhy.gcm.fitting_sampling import draw_samples, fit_causal_model_of_target
  from dowhy.gcm.independence_test.kernel import kernel_based
  from dowhy.gcm.shapley import ShapleyConfig, estimate_shapley_values
  from dowhy.gcm.util.general import shape_into_2d
  from dowhy.graph import DirectedGraph, get_ordered_predecessors, is_root_node, node_connected_subgraph_view
label_desc: Use image node_id 2 for calling a global function with example usage: distribution_change(causal_model, old_data, new_data, target_node, invariant_nodes, num_samples, difference_estimation_func, independence_test, conditional_independence_test, mechanism_change_test_significance_level, mechanism_change_test_fdr_control_method, auto_assignment_quality, return_additional_info, shapley_config, graph_factory) and returns: attributions,  mechanism_changes,  causal_model_old,  causal_model_new, attributions