class_docstr: The Multistage Forecast Estimator class.
  Implements the Multistage forecast method.
  
  The Multistage forecast method allows users to fit multiple stages of
  models with each stage in the following fashions:
  
      (1) subseting: take a subset of data from the end of training data;
      (2) aggregation: aggregate the subset of data into desired frequency;
      (3) training: train a model with the desired estimator and parameters.
  
  Users can just use one stage model to train on a subset/aggregation of the original data,
  or can specify multiple stages, where the later stages will be trained on the fitted
  residuals of the previous stages.
  
  This can significantly speed up the training process if the original data is long
  and in fine granularity.
  
  Notes
  -----
  The following assumptions or special implementations are made in this class:
  
      - The actual ``fit_length``, the length of data where the fitted values are calculated,
        is the longer of ``train_length`` and ``fit_length``. The reason is that there is no
        benefit of calculating a shorter period of fitted values. The fitted values are already
        available during training (in Silverkite) so there is no loss to calculate fitted values
        on a super set of the training data.
      - The estimator sorts the ``model_configs`` according to the ``train_length`` in descending order.
        The corresponding aggregation frequency, aggregation function, fit length,
        estimator and parameters will be sorted accordingly.
        This is to ensure that we have enough data to use from the previous model
        when we fit the next model.
      - When calculating the length of training data, the length of past df, etc,
        the actual length used may include 1 more period to avoid missing timestamps.
        For example, for an AR order of 5, you may see the length of ``past_df`` to be 6;
        or for a train length of "365D", you may see the actual length to be 366.
        This is expected, just to avoid potential missing timestamps after dropping
        incomplete aggregation periods.
      - Since the models in each stage may not fit on the entire training data,
        there could be periods where fitted values are not calculated.
        Leading fitted values in the training period may be NA.
        These values are ignored when computing evaluation metrics.
  
  Attributes
  ----------
  model_configs : `list` [`MultistageForecastModelConfig`]
      A list of model configs for Multistage Forecast estimator,
      representing the stages in the model.
  forecast_horizon : `int`
      The forecast horizon on the original data frequency.
  freq : `str` or None
      The frequency of the original data.
  train_lengths : `list` [`str`] or None
      A list of training data lengths for the models.
  fit_lengths : `list` [`str`] or None
      A list of fitting data lengths for the models.
  agg_funcs : `list` [`str` or Callable] or None
      A list of aggregation functions for the models.
  agg_freqs : `list` [`str`] or None
      A list of aggregation frequencies for the models.
  estimators : `list` [`BaseForecastEstimator`] or None
      A list of estimators used in the models.
  estimator_params : `list` [`dict` or None] or None
      A list of estimator parameters for the estimators.
  train_lengths_in_seconds : `list` [`int`] or None
      The list of training lengths in seconds.
  fit_lengths_in_seconds: : `list` [`int`] or None
      The list of fitting lengths in seconds.
      If the original ``fit_length`` is None or is shorter than the corresponding
      ``train_length``, it will be replaced by the corresponding ``train_length``.
  max_ar_orders : `list` [`int`] or None
      A list of maximum AR orders in the models.
  data_freq_in_seconds : `int` or None
      The data frequency in seconds.
  num_points_per_agg_freqs : `list` [`int`] or None
      Number of data points in each aggregation frequency.
  models : `list` [`BaseForecastEstimator`]
      The list of model instances.
  fit_df : `pandas.DataFrame` or None
      The prediction df.
  train_end : `pandas.Timestamp` or None
      The train end timestamp.
  forecast_horizons : `list` [`int`]
      The list of forecast horizons for all models in terms of the aggregated frequencies.
class_docstr_tok: ["The","Multistage","Forecast","Estimator","class",".","Implements","the","Multistage","forecast","method",".","The","Multistage","forecast","method","allows","users","to","fit","multiple","stages","of","models","with","each","stage","in","the","following","fashions",":","(","1",")","subseting",":","take","a","subset","of","data","from","the","end","of","training","data",";","(","2",")","aggregation",":","aggregate","the","subset","of","data","into","desired","frequency",";","(","3",")","training",":","train","a","model","with","the","desired","estimator","and","parameters",".","Users","can","just","use","one","stage","model","to","train","on","a","subset\/aggregation","of","the","original","data",",","or","can","specify","multiple","stages",",","where","the","later","stages","will","be","trained","on","the","fitted","residuals","of","the","previous","stages",".","This","can","significantly","speed","up","the","training","process","if","the","original","data","is","long","and","in","fine","granularity",".","Notes","--","--","-","The","following","assumptions","or","special","implementations","are","made","in","this","class",":","-","The","actual","``","fit_length","``",",","the","length","of","data","where","the","fitted","values","are","calculated",",","is","the","longer","of","``","train_length","``","and","``","fit_length","``",".","The","reason","is","that","there","is","no","benefit","of","calculating","a","shorter","period","of","fitted","values",".","The","fitted","values","are","already","available","during","training","(","in","Silverkite",")","so","there","is","no","loss","to","calculate","fitted","values","on","a","super","set","of","the","training","data",".","-","The","estimator","sorts","the","``","model_configs","``","according","to","the","``","train_length","``","in","descending","order",".","The","corresponding","aggregation","frequency",",","aggregation","function",",","fit","length",",","estimator","and","parameters","will","be","sorted","accordingly",".","This","is","to","ensure","that","we","have","enough","data","to","use","from","the","previous","model","when","we","fit","the","next","model",".","-","When","calculating","the","length","of","training","data",",","the","length","of","past","df",",","etc",",","the","actual","length","used","may","include","1","more","period","to","avoid","missing","timestamps",".","For","example",",","for","an","AR","order","of","5",",","you","may","see","the","length","of","``","past_df","``","to","be","6",";","or","for","a","train","length","of","``","365D","''",",","you","may","see","the","actual","length","to","be","366",".","This","is","expected",",","just","to","avoid","potential","missing","timestamps","after","dropping","incomplete","aggregation","periods",".","-","Since","the","models","in","each","stage","may","not","fit","on","the","entire","training","data",",","there","could","be","periods","where","fitted","values","are","not","calculated",".","Leading","fitted","values","in","the","training","period","may","be","NA",".","These","values","are","ignored","when","computing","evaluation","metrics",".","Attributes","--","--","--","--","--","model_configs",":","`","list","`","[","`","MultistageForecastModelConfig","`","]","A","list","of","model","configs","for","Multistage","Forecast","estimator",",","representing","the","stages","in","the","model",".","forecast_horizon",":","`","int","`","The","forecast","horizon","on","the","original","data","frequency",".","freq",":","`","str","`","or","None","The","frequency","of","the","original","data",".","train_lengths",":","`","list","`","[","`","str","`","]","or","None","A","list","of","training","data","lengths","for","the","models",".","fit_lengths",":","`","list","`","[","`","str","`","]","or","None","A","list","of","fitting","data","lengths","for","the","models",".","agg_funcs",":","`","list","`","[","`","str","`","or","Callable","]","or","None","A","list","of","aggregation","functions","for","the","models",".","agg_freqs",":","`","list","`","[","`","str","`","]","or","None","A","list","of","aggregation","frequencies","for","the","models",".","estimators",":","`","list","`","[","`","BaseForecastEstimator","`","]","or","None","A","list","of","estimators","used","in","the","models",".","estimator_params",":","`","list","`","[","`","dict","`","or","None","]","or","None","A","list","of","estimator","parameters","for","the","estimators",".","train_lengths_in_seconds",":","`","list","`","[","`","int","`","]","or","None","The","list","of","training","lengths","in","seconds",".","fit_lengths_in_seconds",":",":","`","list","`","[","`","int","`","]","or","None","The","list","of","fitting","lengths","in","seconds",".","If","the","original","``","fit_length","``","is","None","or","is","shorter","than","the","corresponding","``","train_length","``",",","it","will","be","replaced","by","the","corresponding","``","train_length","``",".","max_ar_orders",":","`","list","`","[","`","int","`","]","or","None","A","list","of","maximum","AR","orders","in","the","models",".","data_freq_in_seconds",":","`","int","`","or","None","The","data","frequency","in","seconds",".","num_points_per_agg_freqs",":","`","list","`","[","`","int","`","]","or","None","Number","of","data","points","in","each","aggregation","frequency",".","models",":","`","list","`","[","`","BaseForecastEstimator","`","]","The","list","of","model","instances",".","fit_df",":","`","pandas.DataFrame","`","or","None","The","prediction","df",".","train_end",":","`","pandas.Timestamp","`","or","None","The","train","end","timestamp",".","forecast_horizons",":","`","list","`","[","`","int","`","]","The","list","of","forecast","horizons","for","all","models","in","terms","of","the","aggregated","frequencies","."]
docstr: Trains the Multistage Forecast model with the given configurations.
  
  Parameters
  ----------
  df : `pandas.DataFrame`
      The input dataframe.
  
  Returns
  -------
  fit_df : `pandas.DataFrame`
      The dataframe with aggregated time columns and predictions.
docstr_tok: ["Trains","the","Multistage","Forecast","model","with","the","given","configurations",".","Parameters","--","--","--","--","--","df",":","`","pandas.DataFrame","`","The","input","dataframe",".","Returns","--","--","--","-","fit_df",":","`","pandas.DataFrame","`","The","dataframe","with","aggregated","time","columns","and","predictions","."]
code: def _train(self, df: pd.DataFrame):
      """Trains the Multistage Forecast model with the given configurations.
  
      Parameters
      ----------
      df : `pandas.DataFrame`
          The input dataframe.
  
      Returns
      -------
      fit_df : `pandas.DataFrame`
          The dataframe with aggregated time columns and predictions.
      """
      # Sorts the models by training data length from long to short.
      (
          self.agg_freqs,
          self.agg_funcs,
          self.train_lengths_in_seconds,
          self.fit_lengths_in_seconds,
          self.models,
          self.forecast_horizons,
      ) = zip(
          *sorted(
              zip(
                  self.agg_freqs,
                  self.agg_funcs,
                  self.train_lengths_in_seconds,
                  self.fit_lengths_in_seconds,
                  self.models,
                  self.forecast_horizons,
              ),
              key=lambda x: x[2],  # key is ``train_lengths_in_seconds``
              reverse=True,
          )
      )
  
      # Here we add the ``forecast_horizon`` and ``freq`` attribute regardless of what model it is.
      # This doesn't affect the model if it does not expect the ``forecast_horizon``
      # or ``freq`` attribute before fitting.
      # If the forecast horizon parameter varies due to different periods in the fit
      # and predict input, users can leave them as None and let the estimator automatically fill them.
      # If the entry point is template, it's possible that "forecast_horizon" is a missing parameter.
      # We add it here when it's missing.
      # If either of these parameter is already set, we won't modify it.
      for model, forecast_horizon, agg_freq in zip(
          self.models, self.forecast_horizons, self.agg_freqs
      ):
          if getattr(model, "forecast_horizon", None) is None:
              model.forecast_horizon = forecast_horizon
          if getattr(model, "freq", None) is None:
              model.freq = agg_freq
  
      self.num_points_per_agg_freqs = self._get_num_points_per_agg_freq(
          data_freq=self.freq, agg_freqs=self.agg_freqs
      )
  
      self.max_ar_orders = self._get_silverkite_ar_max_order()
  
      # Adds resampled timestamps and aggregated columns to df.
      # This is done after the sorting, so the index of model steps will be correct.
      df_with_freq = self._add_agg_freq_cols(df=df)
  
      # Makes a copy. This is used to store the results.
      fit_result_df = df_with_freq.copy()
      # Adds a column to track cumulative fitted values.
      # At each stage this column will be subtracted from the original time series
      # to obtain the current residual to be fitted on the next model.
      fit_result_df["cum_fitted_values"] = 0
  
      for index, (
          freq,
          func,
          train_length_in_seconds,
          fit_length_in_seconds,
          model,
          num_points_per_agg_freq,
          max_order,
      ) in enumerate(
          zip(
              self.agg_freqs,
              self.agg_funcs,
              self.train_lengths_in_seconds,
              self.fit_lengths_in_seconds,
              self.models,
              self.num_points_per_agg_freqs,
              self.max_ar_orders,
          )
      ):
          df_with_freq_copy = (
              df_with_freq.copy()
          )  # makes a copy since we want to calculate the residuals.
          df_with_freq_copy[self.value_col_] -= fit_result_df[
              "cum_fitted_values"
          ]
  
          # Gets the train_df and fit_df.
          # The dfs will be subset and aggregated.
          # The fitted values are to be calculated on fit_df.
          # fit_df has length as the maximum of train_length and fit_length.
          agg_dfs = self._get_agg_dfs(
              df=df_with_freq_copy,
              agg_freq=freq,
              agg_func=func,
              train_length_in_seconds=train_length_in_seconds,
              fit_length_in_seconds=fit_length_in_seconds,
              num_points_per_agg_freq=num_points_per_agg_freq,
              max_ar_order=max_order,
              index=index,
          )
          train_df = agg_dfs["train_df"]
          fit_df = agg_dfs["fit_df"]
          # Adds ``past_df`` in case the model expects extra data to calculate autoregression terms.
          if agg_dfs["past_df"] is not None:
              model.past_df = agg_dfs["past_df"]
          if agg_dfs["fit_df_has_incomplete_period"]:
              regressor_cols = getattr(model, "regressor_cols", None)
              if regressor_cols is not None and regressor_cols != []:
                  log_message(
                      message="There are incomplete periods in `fit_df`, thus the regressor "
                      "values are biased after aggregation.",
                      level=LoggingLevelEnum.WARNING,
                  )
          # Adds the actual values to the result df.
          fit_result_df = fit_result_df.merge(
              train_df.rename(
                  columns={
                      self.time_col_: self._get_freq_col(index, freq),
                      self.value_col_: f"{self.value_col_}__{index}__{freq}",
                  }
              ),
              on=self._get_freq_col(index, freq),
              how="left",
          )
  
          # Fits the model.
          model.fit(
              train_df,
              time_col=self.time_col_,
              value_col=self.value_col_,
          )
  
          # Calculates fitted values.
          y_fitted = model.predict(fit_df)[
              [cst.TIME_COL, cst.PREDICTED_COL]
          ].rename(
              columns={
                  cst.TIME_COL: f"{cst.TIME_COL}__{index}__{freq}",
                  cst.PREDICTED_COL: f"{cst.PREDICTED_COL}__{index}__{freq}",
              }
          )
  
          # Joins the fitted values with the original ``fit_result_df``.
          # This is a left join since the fitted values is a subset of the entire period.
          fit_result_df = fit_result_df.merge(
              y_fitted,
              how="left",
              left_on=self._get_freq_col(index, freq),
              right_on=f"{cst.TIME_COL}__{index}__{freq}",
          )
          # Adds the current fitted values to the previous fitted values to get cumulated fitted values.
          fit_result_df["cum_fitted_values"] += fit_result_df[
              f"{cst.PREDICTED_COL}__{index}__{freq}"
          ]
  
      fit_result_df = fit_result_df.rename(
          columns={"cum_fitted_values": cst.PREDICTED_COL}
      )
  
      return fit_result_df
  
imports: from dataclasses import dataclass
  from datetime import timedelta
  from typing import Callable
  from typing import List
  from typing import Optional
  from typing import Type
  from typing import Union
  import pandas
  from dateutil.relativedelta import relativedelta
  from pandas.tseries.frequencies import to_offset
  from sklearn.metrics import mean_squared_error
  from greykite.common import constants
  from greykite.common.aggregation_function_enum import AggregationFunctionEnum
  from greykite.common.logging import LoggingLevelEnum
  from greykite.common.logging import log_message
  from greykite.common.time_properties import infer_freq
  from greykite.sklearn.estimator.base_forecast_estimator import BaseForecastEstimator
  from greykite.sklearn.estimator.simple_silverkite_estimator import SimpleSilverkiteEstimator
label_desc: Use image node_id 15 for calling the MultistageForecastEstimator obj's underlying member method code with example usage: obj._train(df) and returns: fit_result_df