class_docstr: This class uses Expectation-Maximization to learn parameters of a single latent variable in a bayesian network.
  We do so by also allowing the user to CONSTRAINT the optimisation
  These are elements that help the algorithm find a local optimal point closer to the point we think
  
  The setting is:
  Input:
      - a StructureModel representing the whole network or any sub-graph containing the Markov Blanket of the LV
      - data as a dataframe. The LV must be in the dataframe, with missing values represented by `np.nan`s
      - constraints:
          - Box - A hard constraint; forbids the solution to be outside of certain boundaries
          - Priors - establishes Dirichlet priors to every parameter
  run:
      - using the method `run` or manually alternating over E and M steps)
  Result:
      - CPTs involving the latent variable, learnt by EM, found in the attribute `cpds`
      - CPTs not involving the LV not learned (They must be learned separately by MLE.
      This is faster and the result is the same)
  
  Example:
  >>> em = EMSingleLatentVariable(sm=sm, data=data, lv_name=lv_name, node_states=node_states)
  >>> em.run() # run EM until convergence
  >>> # or run E and M steps separately
  >>> for i in range(10): # Run EM 10 times
  >>>     em.e_step()
  >>>     em.m_step()
docstr: Build lookup table based on an individual data record/instance
  
  Args:
      node: Node name
      record: A data record/instance
  
  Returns:
      List of CPD index-columns-count triplets, and tuple of Markov blanket columns
docstr_tok: ["Build","lookup","table","based","on","an","individual","data","record\/instance","Args",":","node",":","Node","name","record",":","A","data","record\/instance","Returns",":","List","of","CPD","index-columns-count","triplets",",","and","tuple","of","Markov","blanket","columns"]
code: def _build_lookup(
      self,
      node: str,
      record: Dict,
  ) -> Tuple[List[Tuple], Tuple]:
      """
      Build lookup table based on an individual data record/instance
  
      Args:
          node: Node name
          record: A data record/instance
  
      Returns:
          List of CPD index-columns-count triplets, and tuple of Markov blanket columns
      """
      count = record["count"]
      node_value = record[node]
      node_cpd = self.cpds[node]
      lv_states = self.node_states[self.lv_name]
      idx_cols_counts = []
  
      if not np.isnan(record[self.lv_name]):
          mb_cols = None
  
          if node_cpd.shape[1] == 1:
              # if the probability is unconditional (i.e. P(Z)), the column names are [""]
              idx_cols_counts.append((node_value, "", count))
          else:
              cols = tuple(record[j] for j in node_cpd.columns.names)
              idx_cols_counts.append((node_value, cols, count))
      else:
          mb_cols = tuple(
              record[j] for j in self._mb_product.columns.names
          )
  
          if node_cpd.shape[1] == 1:
              # if the probability is unconditional (i.e., P(Z)), the column names are [""]
              for lv_value in lv_states:
                  index = (
                      lv_value if node == self.lv_name else node_value
                  )
                  idx_cols_counts.append((index, "", count))
          else:
              if node == self.lv_name:
                  cols = tuple(
                      record[j] for j in node_cpd.columns.names
                  )
  
                  for lv_value in lv_states:
                      idx_cols_counts.append((lv_value, cols, count))
              else:
                  for lv_value in lv_states:
                      cols = tuple(
                          lv_value if j == self.lv_name else record[j]
                          for j in node_cpd.columns.names
                      )
                      idx_cols_counts.append((node_value, cols, count))
  
      return idx_cols_counts, mb_cols
  
imports: import logging
  import os
  from time import time
  from typing import Dict, List, Tuple, Union
  import numpy
  import pandas
  from joblib import Parallel, delayed
  from causalnex.structure import StructureModel
  from causalnex.utils.data_utils import chunk_data, count_unique_rows
  from causalnex.utils.pgmpy_utils import cpd_multiplication
label_desc: Use image node_id 10 for calling the EMSingleLatentVariable obj's underlying member method code with example usage: obj._build_lookup(node, record) and returns: idx_cols_counts,  mb_cols