class_docstr: Initializes a Bayesian Network.
  A models stores nodes and edges with conditional probability
  distribution (cpd) and other attributes.
  
  models hold directed edges.  Self loops are not allowed neither
  multiple (parallel) edges.
  
  Nodes can be any hashable python object.
  
  Edges are represented as links between nodes.
  
  Parameters
  ----------
  ebunch: input graph
      Data to initialize graph.  If ebunch=None (default) an empty
      graph is created.  The ebunch can be an edge list, or any
      NetworkX graph object.
  
  latents: list, array-like
      List of variables which are latent (i.e. unobserved) in the model.
  
  Examples
  --------
  Create an empty Bayesian Network with no nodes and no edges.
  
  >>> from pgmpy.models import BayesianNetwork
  >>> G = BayesianNetwork()
  
  G can be grown in several ways.
  
  **Nodes:**
  
  Add one node at a time:
  
  >>> G.add_node('a')
  
  Add the nodes from any container (a list, set or tuple or the nodes
  from another graph).
  
  >>> G.add_nodes_from(['a', 'b'])
  
  **Edges:**
  
  G can also be grown by adding edges.
  
  Add one edge,
  
  >>> G.add_edge('a', 'b')
  
  a list of edges,
  
  >>> G.add_edges_from([('a', 'b'), ('b', 'c')])
  
  If some edges connect nodes not yet in the model, the nodes
  are added automatically.  There are no errors when adding
  nodes or edges that already exist.
  
  **Shortcuts:**
  
  Many common graph features allow python syntax for speed reporting.
  
  >>> 'a' in G     # check if node in graph
  True
  >>> len(G)  # number of nodes in graph
  3
class_docstr_tok: ["Initializes","a","Bayesian","Network",".","A","models","stores","nodes","and","edges","with","conditional","probability","distribution","(","cpd",")","and","other","attributes",".","models","hold","directed","edges",".","Self","loops","are","not","allowed","neither","multiple","(","parallel",")","edges",".","Nodes","can","be","any","hashable","python","object",".","Edges","are","represented","as","links","between","nodes",".","Parameters","--","--","--","--","--","ebunch",":","input","graph","Data","to","initialize","graph",".","If","ebunch=None","(","default",")","an","empty","graph","is","created",".","The","ebunch","can","be","an","edge","list",",","or","any","NetworkX","graph","object",".","latents",":","list",",","array-like","List","of","variables","which","are","latent","(","i.e",".","unobserved",")","in","the","model",".","Examples","--","--","--","--","Create","an","empty","Bayesian","Network","with","no","nodes","and","no","edges",".",">",">",">","from","pgmpy.models","import","BayesianNetwork",">",">",">","G","=","BayesianNetwork","(",")","G","can","be","grown","in","several","ways",".","*","*","Nodes",":","*","*","Add","one","node","at","a","time",":",">",">",">","G.add_node","(","'","a","'",")","Add","the","nodes","from","any","container","(","a","list",",","set","or","tuple","or","the","nodes","from","another","graph",")",".",">",">",">","G.add_nodes_from","(","[","'","a","'",",","'","b","'","]",")","*","*","Edges",":","*","*","G","can","also","be","grown","by","adding","edges",".","Add","one","edge",",",">",">",">","G.add_edge","(","'","a","'",",","'","b","'",")","a","list","of","edges",",",">",">",">","G.add_edges_from","(","[","(","'","a","'",",","'","b","'",")",",","(","'","b","'",",","'","c","'",")","]",")","If","some","edges","connect","nodes","not","yet","in","the","model",",","the","nodes","are","added","automatically",".","There","are","no","errors","when","adding","nodes","or","edges","that","already","exist",".","*","*","Shortcuts",":","*","*","Many","common","graph","features","allow","python","syntax","for","speed","reporting",".",">",">",">","'","a","'","in","G","#","check","if","node","in","graph","True",">",">",">","len","(","G",")","#","number","of","nodes","in","graph","3"]
docstr: Estimates the CPD for each variable based on a given data set.
  
  Parameters
  ----------
  data: pandas DataFrame object
      DataFrame object with column names identical to the variable names of the network.
      (If some values in the data are missing the data cells should be set to `numpy.NaN`.
      Note that pandas converts each column containing `numpy.NaN`s to dtype `float`.)
  
  estimator: Estimator class
      One of:
      - MaximumLikelihoodEstimator (default)
      - BayesianEstimator: In this case, pass 'prior_type' and either 'pseudo_counts'
      or 'equivalent_sample_size' as additional keyword arguments.
      See `BayesianEstimator.get_parameters()` for usage.
      - ExpectationMaximization
  
  state_names: dict (optional)
      A dict indicating, for each variable, the discrete set of states
      that the variable can take. If unspecified, the observed values
      in the data set are taken to be the only possible states.
  
  n_jobs: int (default: 1)
      Number of threads/processes to use for estimation. Using n_jobs > 1
      for small models or datasets might be slower.
  
  Returns
  -------
  Fitted Model: None
      Modifies the network inplace and adds the `cpds` property.
  
  Examples
  --------
  >>> import pandas as pd
  >>> from pgmpy.models import BayesianNetwork
  >>> from pgmpy.estimators import MaximumLikelihoodEstimator
  >>> data = pd.DataFrame(data={'A': [0, 0, 1], 'B': [0, 1, 0], 'C': [1, 1, 0]})
  >>> model = BayesianNetwork([('A', 'C'), ('B', 'C')])
  >>> model.fit(data)
  >>> model.get_cpds()
  [<TabularCPD representing P(A:2) at 0x7fb98a7d50f0>,
  <TabularCPD representing P(B:2) at 0x7fb98a7d5588>,
  <TabularCPD representing P(C:2 | A:2, B:2) at 0x7fb98a7b1f98>]
code: def fit(
      self,
      data,
      estimator=None,
      state_names=[],
      n_jobs=1,
      **kwargs,
  ):
      """
      Estimates the CPD for each variable based on a given data set.
  
      Parameters
      ----------
      data: pandas DataFrame object
          DataFrame object with column names identical to the variable names of the network.
          (If some values in the data are missing the data cells should be set to `numpy.NaN`.
          Note that pandas converts each column containing `numpy.NaN`s to dtype `float`.)
  
      estimator: Estimator class
          One of:
          - MaximumLikelihoodEstimator (default)
          - BayesianEstimator: In this case, pass 'prior_type' and either 'pseudo_counts'
          or 'equivalent_sample_size' as additional keyword arguments.
          See `BayesianEstimator.get_parameters()` for usage.
          - ExpectationMaximization
  
      state_names: dict (optional)
          A dict indicating, for each variable, the discrete set of states
          that the variable can take. If unspecified, the observed values
          in the data set are taken to be the only possible states.
  
      n_jobs: int (default: 1)
          Number of threads/processes to use for estimation. Using n_jobs > 1
          for small models or datasets might be slower.
  
      Returns
      -------
      Fitted Model: None
          Modifies the network inplace and adds the `cpds` property.
  
      Examples
      --------
      >>> import pandas as pd
      >>> from pgmpy.models import BayesianNetwork
      >>> from pgmpy.estimators import MaximumLikelihoodEstimator
      >>> data = pd.DataFrame(data={'A': [0, 0, 1], 'B': [0, 1, 0], 'C': [1, 1, 0]})
      >>> model = BayesianNetwork([('A', 'C'), ('B', 'C')])
      >>> model.fit(data)
      >>> model.get_cpds()
      [<TabularCPD representing P(A:2) at 0x7fb98a7d50f0>,
      <TabularCPD representing P(B:2) at 0x7fb98a7d5588>,
      <TabularCPD representing P(C:2 | A:2, B:2) at 0x7fb98a7b1f98>]
      """
      from pgmpy.estimators import (
          BaseEstimator,
          MaximumLikelihoodEstimator,
      )
  
      if estimator is None:
          estimator = MaximumLikelihoodEstimator
      else:
          if not issubclass(estimator, BaseEstimator):
              raise TypeError(
                  "Estimator object should be a valid pgmpy estimator."
              )
  
      _estimator = estimator(
          self,
          data,
          state_names=state_names,
      )
      cpds_list = _estimator.get_parameters(n_jobs=n_jobs, **kwargs)
      self.add_cpds(*cpds_list)
  
imports: import itertools
  from collections import defaultdict
  from functools import reduce
  from operator import mul
  import networkx
  import numpy
  import pandas
  from joblib import Parallel, delayed
  from tqdm.auto import tqdm
  from pgmpy.base import DAG
  from pgmpy.factors.continuous import ContinuousFactor
  from pgmpy.factors.discrete import DiscreteFactor, JointProbabilityDistribution, TabularCPD
  from pgmpy.global_vars import logger
  from pgmpy.models.MarkovNetwork import MarkovNetwork
  from pgmpy.utils import compat_fns
label_desc: Use image node_id 13 for calling the BayesianNetwork obj's underlying member method code with example usage: obj.fit(data, estimator, state_names, n_jobs) without return types