code: def draw_samples(
      causal_model: ProbabilisticCausalModel, num_samples: int
  ) -> pd.DataFrame:
      """Draws new joint samples from the given graphical causal model. This is done by first generating random samples
      from root nodes and then propagating causal downstream effects through the graph.
  
      :param causal_model: New samples are generated based on the given causal model.
      :param num_samples: Number of samples to draw.
      :return: A pandas data frame where columns correspond to the nodes in the graph and rows to the drawn joint samples.
      """
      validate_causal_dag(causal_model.graph)
  
      sorted_nodes = list(nx.topological_sort(causal_model.graph))
      drawn_samples = pd.DataFrame(
          np.empty((num_samples, len(sorted_nodes))),
          columns=sorted_nodes,
      )
  
      for node in sorted_nodes:
          causal_mechanism = causal_model.causal_mechanism(node)
  
          if is_root_node(causal_model.graph, node):
              drawn_samples[node] = causal_mechanism.draw_samples(
                  num_samples
              ).squeeze()
          else:
              drawn_samples[node] = causal_mechanism.draw_samples(
                  _parent_samples_of(node, causal_model, drawn_samples)
              ).squeeze()
  
      return drawn_samples
  
file_path: dowhy/dowhy/gcm/fitting_sampling.py
imports: from typing import Any
  import networkx
  import numpy
  import pandas
  from tqdm import tqdm
  from dowhy.gcm import config
  from dowhy.gcm.causal_models import PARENTS_DURING_FIT, ProbabilisticCausalModel, validate_causal_dag, validate_causal_model_assignment
  from dowhy.graph import get_ordered_predecessors, is_root_node
label_desc: Use image node_id 3 for calling a global function with example usage: draw_samples(causal_model, num_samples) and returns: drawn_samples