code: def __str__(self):
      summary_string = "Evaluated"
      if self.mechanism_performances is not None:
          summary_string += " the performance of the causal mechanisms"
      if self.pnl_assumptions is not None:
          summary_string += " and the invertibility assumption of the causal mechanisms"
      if self.overall_kl_divergence is not None:
          summary_string += " and the overall average KL divergence between generated and observed distribution"
      if self.graph_falsification is not None:
          summary_string += " and the graph structure"
  
      summary_string += ". The results are as follows:"
      summary_strings = [summary_string]
  
      if self.mechanism_performances is not None:
          summary_strings.append(
              "\n==== Evaluation of Causal Mechanisms ===="
          )
          summary_strings.append(
              "The used evaluation metrics are:\n"
              "- KL divergence (only for root-nodes): Evaluates the divergence between the generated and the observed distribution.\n"
              "- Mean Squared Error (MSE): Evaluates the average squared differences between the observed values and the conditional expectation of the causal mechanisms.\n"
              "- Normalized MSE (NMSE): The MSE normalized by the standard deviation for better comparison.\n"
              "- R2 coefficient: Indicates how much variance is explained by the conditional expectations of the mechanisms. Note, however, that this can be misleading for nonlinear relationships.\n"
              "- F1 score (only for categorical non-root nodes): The harmonic mean of the precision and recall indicating the goodness of the underlying classifier model.\n"
              "- (normalized) Continuous Ranked Probability Score (CRPS): The CRPS generalizes the Mean Absolute Percentage Error to probabilistic predictions. This gives insights into the accuracy and calibration of the causal mechanisms.\n"
              "NOTE: Every metric focuses on different aspects and they might not consistently indicate a good or bad performance.\n"
              "We will mostly utilize the CRPS for comparing and interpreting the performance of the mechanisms, since this captures the most important properties for the causal model."
          )
  
          for (
              mechanism_performance
          ) in self.mechanism_performances.values():
              summary_strings.append(
                  "\n--- Node %s" % mechanism_performance.node_name
              )
              if mechanism_performance.kl_divergence is not None:
                  summary_strings.append(
                      "- The KL divergence between generated and observed distribution is %s."
                      % mechanism_performance.kl_divergence
                  )
                  summary_strings.append(
                      _get_kl_divergence_interpretation_string(
                          mechanism_performance.kl_divergence
                      )
                  )
  
              if mechanism_performance.mse is not None:
                  summary_strings.append(
                      "- The MSE is %s." % mechanism_performance.mse
                  )
  
              if mechanism_performance.nmse is not None:
                  summary_strings.append(
                      "- The NMSE is %s." % mechanism_performance.nmse
                  )
  
              if mechanism_performance.r2 is not None:
                  summary_strings.append(
                      "- The R2 coefficient is %s."
                      % mechanism_performance.r2
                  )
  
              if mechanism_performance.f1 is not None:
                  summary_strings.append(
                      "- The F1 score is %s." % mechanism_performance.f1
                  )
  
              if mechanism_performance.crps is not None:
                  summary_strings.append(
                      "- The normalized CRPS is %s."
                      % mechanism_performance.crps
                  )
                  summary_strings.append(
                      _get_crps_interpretation_string(
                          mechanism_performance.crps
                      )
                  )
  
              if mechanism_performance.total_number_baselines > 0:
                  summary_strings.append(
                      _get_baseline_model_interpretation_string(
                          mechanism_performance.count_better_performance,
                          mechanism_performance.total_number_baselines,
                          mechanism_performance.best_baseline_model,
                          mechanism_performance.best_baseline_performance,
                      )
                  )
  
      if self.pnl_assumptions is not None:
          summary_strings.append(
              "\n==== Evaluation of Invertible Functional Causal Model Assumption ===="
          )
          if len(self.pnl_assumptions) == 0:
              summary_strings.append(
                  "The causal model has no invertible causal models."
              )
          else:
              for node in self.pnl_assumptions:
                  rejected = (
                      "rejected"
                      if self.pnl_assumptions[node][1]
                      else "not rejected"
                  )
                  summary_strings.append(
                      "\n--- The model assumption for node %s is %s with a p-value of %s "
                      "(after potential adjustment) and a significance level of %s."
                      % (
                          node,
                          rejected,
                          self.pnl_assumptions[node][0],
                          self.pnl_assumptions[node][2],
                      )
                  )
                  if not self.pnl_assumptions[node][1]:
                      summary_strings.append(
                          "This implies that the model assumption might be valid."
                      )
                  else:
                      summary_strings.append(
                          "This implies that the model assumption might not be valid. This is, "
                          "the relationship cannot be represent with this type of mechanism or "
                          "there is a hidden confounder between the node and its parents."
                      )
  
              summary_strings.append(
                  "\nNote that these results are based on statistical independence tests, and the "
                  "fact that the assumption was not rejected does not necessarily imply that it "
                  "is correct. There is just no evidence against it."
              )
  
      if self.overall_kl_divergence is not None:
          summary_strings.append(
              "\n==== Evaluation of Generated Distribution ===="
          )
          summary_strings.append(
              "The overall average KL divergence between the generated and observed distribution is %s"
              % self.overall_kl_divergence
          )
          summary_strings.append(
              _get_kl_divergence_interpretation_string(
                  self.overall_kl_divergence
              )
          )
  
      if self.graph_falsification is not None:
          summary_strings.append(
              "\n==== Evaluation of the Causal Graph Structure ===="
          )
          summary_strings.append(str(self.graph_falsification))
          if self.plot_falsification_histogram:
              plot_evaluation_results(self.graph_falsification)
  
      summary_strings.append("\n==== NOTE ====")
      summary_strings.append(
          "Always double check the made model assumptions with respect to the graph structure and "
          "choice of causal mechanisms."
      )
      summary_strings.append(
          "All these evaluations give some insight into the goodness of the causal model, but should not be "
          "overinterpreted, since some causal relationships can be intrinsically hard to model. Furthermore, many "
          "algorithms are fairly robust against misspecifications or poor performances of causal mechanisms."
      )
  
      return "\n".join(summary_strings)
  
imports: from dataclasses import dataclass
  from functools import partial
  from typing import Any, Callable, Dict, List, Optional, Tuple
  import networkx
  import numpy
  import pandas
  from joblib import Parallel, delayed
  from numpy.matlib import repmat
  from scipy.stats import mode
  from sklearn.metrics import f1_score, mean_squared_error, r2_score
  from sklearn.model_selection import KFold
  from statsmodels.stats.multitest import multipletests
  from tqdm import tqdm
  from dowhy.gcm import ClassifierFCM, PostNonlinearModel, PredictionModel, ProbabilisticCausalModel, config, draw_samples, kernel_based
  from dowhy.gcm.causal_mechanisms import AdditiveNoiseModel, ConditionalStochasticModel, InvertibleFunctionalCausalModel
  from dowhy.gcm.divergence import auto_estimate_kl_divergence
  from dowhy.gcm.falsify import EvaluationResult, falsify_graph, plot_evaluation_results
  from dowhy.gcm.ml import SklearnRegressionModel, create_hist_gradient_boost_classifier, create_hist_gradient_boost_regressor, create_lasso_regressor, create_linear_regressor, create_logistic_regression_classifier, create_random_forest_classifier, create_random_forest_regressor, create_ridge_regressor
  from dowhy.gcm.ml.classification import create_ada_boost_classifier, create_gaussian_nb_classifier, create_knn_classifier, create_polynom_logistic_regression_classifier
  from dowhy.gcm.ml.regression import create_ada_boost_regressor, create_extra_trees_regressor, create_polynom_regressor
  from dowhy.gcm.stats import quantile_based_fwer
  from dowhy.gcm.util.general import is_categorical, set_random_seed, shape_into_2d
  from dowhy.graph import get_ordered_predecessors, is_root_node
label_desc: Use image node_id 1 for calling the CausalModelEvaluationResult obj's underlying member method code with example usage: obj.__str__() and returns: str