class_docstr: Aggregates hierarchical data, fit forecasters and make predictions.
  
  Can apply different univariate forecaster either on different
  level of aggregation or on different hierarchical nodes.
  
  `HierarchyEnsembleForecaster` is passed forecaster/level or
  forecaster/node pairs. Level can only be int >= 0 with 0
  signifying the topmost level of aggregation.
  Node can only be a tuple of strings or list of tuples.
  
  Behaviour in `fit`, `predict`:
  For level pairs f_i, l_i passed, applies forecaster f_i to level l_i.
  For node pairs f_i, n_i passed, applies forecaster f_i on each node of n_i.
  if "default" argument passed, applies "default" forecaster on the
  remaining levels/nodes which are not mentioned in argument 'forecasters'.
  `predict` results are concatenated to one container with same columns as in `fit`.
  
  
  Parameters
  ----------
  forecasters : sktime forecaster, or list of tuples
              (str, estimator, int or list of tuple/s)
      if forecaster, clones of forecaster are applied to all aggregated levels.
      if list of tuples, with name = str, estimator is forecaster, level/node
      as int/tuples respectively.
      all levels/nodes must be present in 'forecasters' attribute if 'default'
      attribute is None
  
  by : {'node', 'level', default='level'}
      if 'level', applies a univariate forecaster on all the hierarchical
      nodes within a level of aggregation
      if 'node', applies separate univariate forecaster for each
      hierarchical node.
  
  default : sktime forecaster {default = None}
      if passed, applies 'default' forecaster on the nodes/levels
      not mentioned in the 'forecaster' argument.
  
  Examples
  --------
  >>> from sktime.forecasting.compose import HierarchyEnsembleForecaster
  >>> from sktime.forecasting.naive import NaiveForecaster
  >>> from sktime.forecasting.trend import PolynomialTrendForecaster, TrendForecaster
  >>> from sktime.utils._testing.hierarchical import _bottom_hier_datagen
  >>> y = _bottom_hier_datagen(
  ...         no_bottom_nodes=7,
  ...         no_levels=2,
  ...         random_seed=123
  ... )
  
  >>> # Example of by = 'level'
  >>> forecasters = [
  ...     ('naive', NaiveForecaster(), 0),
  ...     ('trend', TrendForecaster(), 1)
  ... ]
  >>> forecaster = HierarchyEnsembleForecaster(
  ...                 forecasters=forecasters,
  ...                 by='level', default = PolynomialTrendForecaster(degree=2)
  ... )
  >>> forecaster.fit(y, fh=[1, 2, 3])
  HierarchyEnsembleForecaster(...)
  >>> y_pred = forecaster.predict()
  
  >>> # Example of by = 'node'
  >>> forecasters = [
  ...     ('trend', TrendForecaster(), [("__total", "__total")]),
  ...     ('poly', PolynomialTrendForecaster(degree=2), [('l2_node01', 'l1_node01')]),
  ... ]
  >>> forecaster = HierarchyEnsembleForecaster(
  ...                 forecasters=forecasters,
  ...                 by='node', default=NaiveForecaster()
  ... )
  >>> forecaster.fit(y, fh=[1, 2, 3])
  HierarchyEnsembleForecaster(...)
  >>> y_pred = forecaster.predict()
class_docstr_tok: ["Aggregates","hierarchical","data",",","fit","forecasters","and","make","predictions",".","Can","apply","different","univariate","forecaster","either","on","different","level","of","aggregation","or","on","different","hierarchical","nodes",".","`","HierarchyEnsembleForecaster","`","is","passed","forecaster\/level","or","forecaster\/node","pairs",".","Level","can","only","be","int",">","=","0","with","0","signifying","the","topmost","level","of","aggregation",".","Node","can","only","be","a","tuple","of","strings","or","list","of","tuples",".","Behaviour","in","`","fit","`",",","`","predict","`",":","For","level","pairs","f_i",",","l_i","passed",",","applies","forecaster","f_i","to","level","l_i",".","For","node","pairs","f_i",",","n_i","passed",",","applies","forecaster","f_i","on","each","node","of","n_i",".","if","``","default","''","argument","passed",",","applies","``","default","''","forecaster","on","the","remaining","levels\/nodes","which","are","not","mentioned","in","argument","'forecasters","'",".","`","predict","`","results","are","concatenated","to","one","container","with","same","columns","as","in","`","fit","`",".","Parameters","--","--","--","--","--","forecasters",":","sktime","forecaster",",","or","list","of","tuples","(","str",",","estimator",",","int","or","list","of","tuple\/s",")","if","forecaster",",","clones","of","forecaster","are","applied","to","all","aggregated","levels",".","if","list","of","tuples",",","with","name","=","str",",","estimator","is","forecaster",",","level\/node","as","int\/tuples","respectively",".","all","levels\/nodes","must","be","present","in","'forecasters","'","attribute","if","'default'","attribute","is","None","by",":","{","'node","'",",","'level","'",",","default='level","'","}","if","'level","'",",","applies","a","univariate","forecaster","on","all","the","hierarchical","nodes","within","a","level","of","aggregation","if","'node","'",",","applies","separate","univariate","forecaster","for","each","hierarchical","node",".","default",":","sktime","forecaster","{","default","=","None","}","if","passed",",","applies","'default","'","forecaster","on","the","nodes\/levels","not","mentioned","in","the","'forecaster","'","argument",".","Examples","--","--","--","--",">",">",">","from","sktime.forecasting.compose","import","HierarchyEnsembleForecaster",">",">",">","from","sktime.forecasting.naive","import","NaiveForecaster",">",">",">","from","sktime.forecasting.trend","import","PolynomialTrendForecaster",",","TrendForecaster",">",">",">","from","sktime.utils._testing.hierarchical","import","_bottom_hier_datagen",">",">",">","y","=","_bottom_hier_datagen","(","...","no_bottom_nodes=7",",","...","no_levels=2",",","...","random_seed=123","...",")",">",">",">","#","Example","of","by","=","'level'",">",">",">","forecasters","=","[","...","(","'naive","'",",","NaiveForecaster","(",")",",","0",")",",","...","(","'trend","'",",","TrendForecaster","(",")",",","1",")","...","]",">",">",">","forecaster","=","HierarchyEnsembleForecaster","(","...","forecasters=forecasters",",","...","by='level","'",",","default","=","PolynomialTrendForecaster","(","degree=2",")","...",")",">",">",">","forecaster.fit","(","y",",","fh=","[","1",",","2",",","3","]",")","HierarchyEnsembleForecaster","(","...",")",">",">",">","y_pred","=","forecaster.predict","(",")",">",">",">","#","Example","of","by","=","'node'",">",">",">","forecasters","=","[","...","(","'trend","'",",","TrendForecaster","(",")",",","[","(","``","__total","''",",","``","__total","''",")","]",")",",","...","(","'poly","'",",","PolynomialTrendForecaster","(","degree=2",")",",","[","(","'l2_node01","'",",","'l1_node01","'",")","]",")",",","...","]",">",">",">","forecaster","=","HierarchyEnsembleForecaster","(","...","forecasters=forecasters",",","...","by='node","'",",","default=NaiveForecaster","(",")","...",")",">",">",">","forecaster.fit","(","y",",","fh=","[","1",",","2",",","3","]",")","HierarchyEnsembleForecaster","(","...",")",">",">",">","y_pred","=","forecaster.predict","(",")"]
docstr: Raise error if BY is not defined correctly.
docstr_tok: ["Raise","error","if","BY","is","not","defined","correctly","."]
code: def _check_forecasters(self, y, z):
      """Raise error if BY is not defined correctly."""
      if self.by not in self.BY_LIST:
          raise ValueError(f"""BY must be one of {self.BY_LIST}.""")
  
      if y.index.nlevels < 1:
          raise ValueError(
              "Data should have multiindex with levels greater than or equal to 1."
          )
  
      forecasters = self.forecasters
  
      # if a single estimator is passed, replicate across levels
      if isinstance(forecasters, BaseForecaster):
          if self.by == "level":
              lvlrange = range(y.index.nlevels)
              lvls = [str(lvl) for lvl in lvlrange]
              forecaster_list = [forecasters.clone() for _ in lvlrange]
              return list(zip(lvls, forecaster_list, lvlrange))
          else:
              if z.index.nlevels > 1:
                  node = z.index.droplevel(-1).unique().tolist()
              else:
                  node = z.index.tolist()
              name = "forecasters"
              return [(name, forecasters, node)]
  
      if (
          forecasters is None
          or len(forecasters) == 0
          or not isinstance(forecasters, list)
      ):
          raise ValueError(
              "Invalid 'forecasters' attribute, 'forecasters' should be either a "
              "Baseforecaster class or a list of (name, estimator, int/list) tuples."
          )
  
      if (
          not isinstance(self.default, BaseForecaster)
          and self.default is not None
      ):
          raise ValueError(
              "Invalid 'default' attribute, 'default' should be a BaseForecaster"
          )
  
      for i in range(len(forecasters)):
          if not isinstance(forecasters[i], tuple):
              raise ValueError(
                  "Invalid 'forecasters' attribute, 'forecasters' should "
                  "be either a BaseForecaster class or a list of tuples: "
                  " [(name, estimator, int/list)]."
              )
          if self.by == "node":
              if not isinstance(forecasters[i][2], list):
                  raise ValueError(
                      "Incorrect format of 'forecasters' attribute being passed."
                      "The 'Nodes' should be a list of tuple/tuples."
                  )
  
      _, forecasters_, level_nd = zip(*forecasters)
  
      for forecaster in forecasters_:
          if not isinstance(forecaster, BaseForecaster):
              raise ValueError(
                  f"The estimator {forecaster.__class__.__name__} should be a "
                  f"BaseForecaster class."
              )
  
      if y.index.nlevels == 1:
          return self.forecasters
  
      if self.by == "level":
          level_flat = flatten(level_nd)
          level_set = set(level_flat)
          not_in_z_idx = level_set.difference(range(z.index.nlevels))
          z_lvls_not_found = set(range(z.index.nlevels)).difference(
              level_set
          )
          zlvls_nf = [str(lvl) for lvl in z_lvls_not_found]
  
          if len(not_in_z_idx) > 0:
              raise ValueError(
                  f"Level identifier must be integers within "
                  f"the range of the total number of levels, "
                  f"but found level identifiers that are not: {list(not_in_z_idx)}"
              )
  
          if len(level_set) != len(level_flat):
              raise ValueError(
                  f"Only one estimator per level required. Found {len(level_flat)} "
                  f" level names in forecasters arg, required: {len(level_set)}"
              )
          if self.default is None and len(z_lvls_not_found) > 0:
              raise ValueError(
                  f"One estimator per level required. Following level/levels of "
                  f"data are missing estimator : {list(z_lvls_not_found)}"
              )
  
          if self.default:
              forecaster_list = [
                  self.default.clone() for _ in z_lvls_not_found
              ]
              return forecasters + list(
                  zip(zlvls_nf, forecaster_list, z_lvls_not_found)
              )
      else:
          nodes_t = []
          for nodes in level_nd:
              if len(nodes) == 0:
                  raise ValueError("Nodes cannot be empty.")
              if z.index.nlevels == 2:
                  nodes_ix = pd.Index(nodes)
                  nodes_t += nodes
              else:
                  nodes_l = []
                  for i in range(len(nodes)):
                      if (
                          isinstance(nodes[i], tuple)
                          and len(nodes[i]) > z.index.nlevels - 1
                      ):
                          raise ValueError(
                              "Ideally, length of individual node should be "
                              "equal to N-1 (where N is number of levels in "
                              "multi-index) and must not exceed N-1."
                          )
                      elif (
                          isinstance(nodes[i], tuple)
                          and len(nodes[i]) < z.index.nlevels - 1
                      ) or isinstance(nodes[i], str):
                          zindex = z.index.droplevel(-1).unique()
                          flag = 0
                          inds = []
                          if isinstance(nodes[i], tuple):
                              for ind in zindex:
                                  if ind[: len(nodes[i])] == nodes[i]:
                                      inds.append(ind)
                                      flag = 1
                          else:
                              for ind in zindex:
                                  if ind[0] == nodes[i]:
                                      inds.append(ind)
                                      flag = 1
                          if flag == 0:
                              raise ValueError(
                                  "Node value must lie within "
                                  "multi-index of aggregated data"
                              )
                          else:
                              nodes_l += inds
                              warn(
                                  f"Ideally, length of individual node "
                                  f"in HierarchyEnsembleForecaster should be "
                                  f"equal to N-1 (where N is number of levels in "
                                  f"multi-index) and must not exceed N-1. The "
                                  f"forecaster will now be fitted to the "
                                  f"following nodes : {list(inds)}",
                                  obj=self,
                              )
                      elif (
                          isinstance(nodes[i], tuple)
                          and len(nodes[i]) == z.index.nlevels - 1
                      ):
                          nodes_l.append(nodes[i])
                      else:
                          raise RuntimeError(
                              "Unreachable condition. Check the format of nodes "
                              "being passed."
                          )
  
                  nodes_ix = pd.MultiIndex.from_tuples(
                      nodes_l, names=z.index.names[:-1]
                  )
                  nodes_t += nodes_l
              nodes_m = z.index.droplevel(-1).unique()[
                  z.index.droplevel(-1).unique().isin(nodes_ix)
              ]
              nodes_nm = nodes_ix.difference(nodes_m)
  
              if len(nodes_nm) > 0:
                  raise ValueError(
                      f"Individual node value must be a tuple of "
                      f"index/indices within the multi-index of aggregated "
                      f"dataframe and must not include timepoint index. "
                      f"Following node/nodes are not present in"
                      f"the multi-index of aggregated data: {nodes_nm.to_list()}"
                  )
          nodes_set = set(nodes_t)
          if len(nodes_set) != len(nodes_t):
              raise ValueError(
                  f"Duplicate nodes found in 'forecasters' attribute. "
                  f"Only one estimator per node required. Found {len(nodes_t)} "
                  f"nodes , required: {len(nodes_set)}."
              )
          if z.index.nlevels == 2:
              nodes_tx = pd.Index(nodes_t)
          else:
              nodes_tx = pd.MultiIndex.from_tuples(
                  nodes_t, names=z.index.names[:-1]
              )
  
          z_nds_not_found = (
              z.index.droplevel(-1).unique().difference(nodes_tx)
          )
          if self.default is None and len(z_nds_not_found) > 0:
              raise ValueError(
                  f"One estimator per node required. Following nodes of "
                  f"data are missing estimator : {list(z_nds_not_found)}"
              )
  
      return forecasters
  
file_path: sktime/sktime/forecasting/compose/_hierarchy_ensemble.py
imports: import pandas
  from sktime.base._meta import flatten
  from sktime.forecasting.base._base import BaseForecaster
  from sktime.forecasting.base._meta import _HeterogenousEnsembleForecaster
  from sktime.transformations.hierarchical.aggregate import _check_index_no_total
  from sktime.utils.warnings import warn
label_desc: Use image node_id 10 for calling the HierarchyEnsembleForecaster obj's underlying member method code with example usage: obj._check_forecasters(y, z) and returns: forecasters, self, list, list, unknown