class_docstr: Nested Multinomial Logit with Random Utility 2 parameterization
  
  
  Parameters
  ----------
  endog : ndarray
      not used in this part
  exog : dict_like
      dictionary access to data where keys correspond to branch and leaf
      names. The values are the data arrays for the exog in that node.
  tree : nested tuples and lists
      each branch, tree or subtree, is defined by a tuple
      (branch_name, [subtree1, subtree2, ..., subtreek])
      Bottom branches have as subtrees the list of leaf names.
  paramsind : dictionary
      dictionary that maps branch and leaf names to the names of parameters,
      the coefficients for exogs)
  
  Methods
  -------
  get_probs
  
  Attributes
  ----------
  branches
  leaves
  paramsnames
  parinddict
  
  Notes
  -----
  endog needs to be encoded so it is consistent with self.leaves, which
  defines the columns for the probability array. The ordering in leaves is
  determined by the ordering of the tree.
  In the dummy encoding of endog, the columns of endog need to have the
  same order as self.leaves. In the integer encoding, the integer for a
  choice has to correspond to the index in self.leaves.
  (This could be made more robust, by handling the endog encoding internally
  by leaf names, if endog is defined as categorical variable with
  associated category level names.)
docstr: walking a tree bottom-up based on dictionary
          
code: def calc_prob(self, tree, parent=None):
      """walking a tree bottom-up based on dictionary"""
  
      # 0.5#2 #placeholder for now
      # should be tau=self.taus[name] but as part of params for optimization
      endog = self.endog
      datadict = self.datadict
      paramsind = self.paramsind
      branchsum = self.branchsum
  
      if isinstance(
          tree, tuple
      ):  # assumes leaves are int for choice index
          name, subtree = tree
          self.branchleaves[name] = []  # register branch in dictionary
  
          tau = self.recursionparams[self.paramsidx["tau_" + name]]
          if DEBUG:
              print("----------- starting next branch-----------")
              print(name, datadict[name], "tau=", tau)
              print("subtree", subtree)
          branchvalue = []
          if testxb == 2:
              branchsum = 0
          elif testxb == 1:
              branchsum = datadict[name]
          else:
              branchsum = name
          for b in subtree:
              if DEBUG:
                  print(b)
              bv = self.calc_prob(b, name)
              bv = np.exp(
                  bv / tau
              )  # this should not be here, when adding branch data
              branchvalue.append(bv)
              branchsum = branchsum + bv
          self.branchvalues[
              name
          ] = branchvalue  # keep track what was returned
  
          if DEBUG:
              print("----------- returning to branch-----------")
              print(name)
              print("branchsum in branch", name, branchsum)
  
          if parent:
              if DEBUG:
                  print("parent", parent)
              self.branchleaves[parent].extend(self.branchleaves[name])
          if 0:  # not name == 'top':  # not used anymore !!! ???
              # if not name == 'top':
              # TODO: do I need this only on the lowest branches ?
              tmpsum = 0
              for k in self.branchleaves[name]:
                  # similar to this is now also in return branch values
                  # depends on what will be returned
                  tmpsum += self.probs[k]
                  iv = np.log(tmpsum)
  
              for k in self.branchleaves[name]:
                  self.probstxt[k] = self.probstxt[k] + [
                      "*"
                      + name
                      + "-prob"
                      + "(%s)" % ", ".join(self.paramsind[name])
                  ]
  
                  # TODO: does this use the denominator twice now
                  self.probs[k] = self.probs[k] / tmpsum
                  if np.size(self.datadict[name]) > 0:
                      # not used yet, might have to move one indentation level
                      # self.probs[k] = self.probs[k] / tmpsum
                      ##                            np.exp(-self.datadict[name] *
                      ##                             np.sum(self.recursionparams[self.parinddict[name]]))
                      if DEBUG:
                          print("self.datadict[name], self.probs[k]")
                          print(self.datadict[name], self.probs[k])
                  # if not name == 'top':
                  #    self.probs[k] = self.probs[k] * np.exp( iv)
  
          # walk one level down again to add branch probs to instance.probs
          self.bprobs[name] = []
          for bidx, b in enumerate(subtree):
              if DEBUG:
                  print("repr(b)", repr(b), bidx)
              # if len(b) == 1: #TODO: skip leaves, check this
              if not isinstance(b, tuple):  # isinstance(b, str):
                  # TODO: replace this with a check for branch (tuple) instead
                  # this implies name is a bottom branch,
                  # possible to add special things here
                  self.bprobs[name].append(self.probs[b])
                  # TODO: need tau possibly here
                  self.probs[b] = self.probs[b] / branchsum
                  if DEBUG:
                      print(
                          "*********** branchsum at bottom branch",
                          branchsum,
                      )
                  # self.bprobs[name].append(self.probs[b])
              else:
                  bname = b[0]
                  branchsum2 = sum(self.branchvalues[name])
                  assert np.abs(branchsum - branchsum2).sum() < 1e-8
                  bprob = branchvalue[bidx] / branchsum
                  self.bprobs[name].append(bprob)
  
                  for k in self.branchleaves[bname]:
                      if DEBUG:
                          print(
                              "branchprob", bname, k, bprob, branchsum
                          )
                      # temporary hack with maximum to avoid zeros
                      self.probs[k] = self.probs[k] * np.maximum(
                          bprob, 1e-4
                      )
  
          if DEBUG:
              print("working on branch", tree, branchsum)
          if testxb < 2:
              return branchsum
          else:  # this is the relevant part
              self.branchsums[name] = branchsum
              if np.size(self.datadict[name]) > 0:
                  branchxb = np.sum(
                      self.datadict[name]
                      * self.recursionparams[self.parinddict[name]]
                  )
              else:
                  branchxb = 0
              if not name == "top":
                  tau = self.recursionparams[
                      self.paramsidx["tau_" + name]
                  ]
              else:
                  tau = 1
              iv = (
                  branchxb + tau * branchsum
              )  # which tau: name or parent???
              return branchxb + tau * np.log(branchsum)  # iv
              # branchsum is now IV, TODO: add effect of branch variables
  
      else:
          tau = self.recursionparams[self.paramsidx["tau_" + parent]]
          if DEBUG:
              print("parent", parent)
          self.branchleaves[parent].append(
              tree
          )  # register leave with parent
          self.probstxt[tree] = [
              tree + "-prob" + "(%s)" % ", ".join(self.paramsind[tree])
          ]
          # this is not yet a prob, not normalized to 1, it is exp(x*b)
          leafprob = np.exp(
              np.sum(
                  self.datadict[tree]
                  * self.recursionparams[self.parinddict[tree]]
              )
              / tau
          )  # fake tau for now, wrong spot ???
          # it seems I get the same answer with and without tau here
          self.probs[tree] = leafprob  # = 1 #try initialization only
          # TODO: where  should I add tau in the leaves
  
          if testxb == 2:
              return np.log(leafprob)
          elif testxb == 1:
              leavessum = np.array(
                  datadict[tree]
              )  # sum((datadict[bi] for bi in datadict[tree]))
              if DEBUG:
                  print(
                      "final branch with",
                      tree,
                      "".join(tree),
                      leavessum,
                  )  # sum(tree)
              return leavessum  # sum(xb[tree])
          elif testxb == 0:
              return "".join(tree)
  
file_path: statsmodels/statsmodels/sandbox/regression/treewalkerclass.py
imports: from statsmodels.compat.python import lrange
  from pprint import pprint
  import numpy
label_desc: Use image node_id 3 for calling the RU2NMNL obj's underlying member method code with example usage: obj.calc_prob(tree, parent) and returns: branchsum, unknown, np, leavessum, str