class_docstr: Base class for a forecaster model.
  
  .. note::
  
      If your model depends on an evenly spaced time series, make sure to
  
      1. Call `ForecasterBase.train_pre_process` in `ForecasterBase.train`
      2. Call `ForecasterBase.resample_time_stamps` at the start of
         `ForecasterBase.forecast` to get a set of resampled time stamps, and
         call ``time_series.align(reference=time_stamps)`` to align the forecast
         with the original time stamps.
class_docstr_tok: ["Base","class","for","a","forecaster","model",".","..","note",":",":","If","your","model","depends","on","an","evenly","spaced","time","series",",","make","sure","to","1",".","Call","`","ForecasterBase.train_pre_process","`","in","`","ForecasterBase.train","`","2",".","Call","`","ForecasterBase.resample_time_stamps","`","at","the","start","of","`","ForecasterBase.forecast","`","to","get","a","set","of","resampled","time","stamps",",","and","call","``","time_series.align","(","reference=time_stamps",")","``","to","align","the","forecast","with","the","original","time","stamps","."]
code: def _process_forecast(
      self,
      forecast,
      err,
      time_series_prev=None,
      return_prev=False,
      return_iqr=False,
  ):
      forecast = (
          forecast.to_pd()
          if isinstance(forecast, TimeSeries)
          else forecast
      )
      if return_prev and time_series_prev is not None:
          forecast = pd.concat((time_series_prev, forecast))
  
      # Obtain negative & positive error bars which are appropriately padded
      if err is not None:
          err = (err,) if not isinstance(err, tuple) else err
          assert isinstance(err, tuple) and len(err) in (1, 2)
          assert all(
              isinstance(e, (pd.DataFrame, TimeSeries)) for e in err
          )
          new_err = []
          for e in err:
              e = e.to_pd() if isinstance(e, TimeSeries) else e
              n, d = len(forecast) - len(e), e.shape[1]
              if n > 0:
                  e = pd.concat(
                      (
                          pd.DataFrame(
                              np.zeros((n, d)),
                              index=forecast.index[:n],
                              columns=e.columns,
                          ),
                          e,
                      )
                  )
              e.columns = [f"{c}_err" for c in forecast.columns]
              new_err.append(e.abs())
          e_neg, e_pos = (
              new_err if len(new_err) == 2 else (new_err[0], new_err[0])
          )
      else:
          e_neg = e_pos = None
  
      # Compute upper/lower bounds for the (potentially inverted) forecast.
      # Only do this if returning the IQR or inverting the transform.
      if (
          (return_iqr or self.invert_transform)
          and e_neg is not None
          and e_pos is not None
      ):
          lb = TimeSeries.from_pd(
              (
                  forecast
                  + e_neg.values
                  * (norm.ppf(0.25) if return_iqr else -1)
              )
          )
          ub = TimeSeries.from_pd(
              (
                  forecast
                  + e_pos.values * (norm.ppf(0.75) if return_iqr else 1)
              )
          )
          if self.invert_transform:
              lb = self.transform.invert(
                  lb, retain_inversion_state=True
              )
              ub = self.transform.invert(
                  ub, retain_inversion_state=True
              )
      else:
          lb = ub = None
  
      # Convert the forecast to TimeSeries and invert the transform on it if desired
      forecast = TimeSeries.from_pd(forecast)
      if self.invert_transform:
          forecast = self.transform.invert(
              forecast, retain_inversion_state=True
          )
  
      # Return the IQR if desired
      if return_iqr:
          if lb is None or ub is None:
              logger.warning(
                  "Model returned err = None, so returning IQR = (None, None)"
              )
          else:
              lb, ub = lb.rename(lambda c: f"{c}_lower"), ub.rename(
                  lambda c: f"{c}_upper"
              )
          return forecast, lb, ub
  
      # Otherwise, either compute the stderr from the upper/lower bounds (if relevant), or just use the error
      if lb is not None and ub is not None:
          err = TimeSeries.from_pd(
              (ub.to_pd() - lb.to_pd().values)
              .rename(columns=lambda c: f"{c}_err")
              .abs()
              / 2
          )
      elif e_neg is not None and e_pos is not None:
          err = TimeSeries.from_pd(
              e_pos if e_neg is e_pos else (e_neg + e_pos) / 2
          )
      else:
          err = None
      return forecast, err
  
imports: from abc import abstractmethod
  import copy
  import logging
  from typing import List, Optional, Tuple, Union
  import numpy
  import pandas
  from scipy.stats import norm
  from merlion.models.base import Config, ModelBase
  from merlion.plot import Figure
  from merlion.transform.base import TransformBase
  from merlion.transform.factory import TransformFactory
  from merlion.transform.normalize import MeanVarNormalize
  from merlion.utils.time_series import to_pd_datetime, to_timestamp, TimeSeries, AggregationPolicy, MissingValuePolicy
label_desc: Use image node_id 15 for calling the ForecasterBase obj's underlying member method code with example usage: obj._process_forecast(forecast, err, time_series_prev, return_prev, return_iqr) and returns: forecast,  err, forecast,  lb,  ub