code: def build_autoreg_df(
      value_col,
      lag_dict=None,
      agg_lag_dict=None,
      series_na_fill_func=lambda s: s.bfill().ffill(),
  ):
      """This function generates a function ("build_lags_func" in the returned dict)
           which when called builds a lag data frame and an aggregated lag data frame using
           "build_lag_df" and "build_agg_lag_df" functions.
           Note: In case of training, validation and testing (e.g. cross-validation)
           for forecasting, this function needs to be applied after the data split is done.
           This is especially important if "series_na_fill_func" is using future values
           in interpolation - that is the case for the default which is
           lambda s: s.bfill().ffill()
  
      :param value_col: str
           the column name for the values of interest
      :param lag_dict: Optional[dict]
           A dictionary which encapsulates the needed params to be passed to the
           function "build_lag_df"
           Expected items are:
  
               - "max_order": Optional[int]
                   the max_order for creating lags
               - "orders": Optional[List[int]]
                   the orders for which lag is needed
  
       :param agg_lag_dict: Optional[dict]
           A dictionary encapsulating the needed params to be passed to the function
           "build_agg_lag_df"
           Expected items are:
  
               - "orders_list": List[List[int]]
                   A list of list of integers.
                   Each int list is to be used as order of lags to be aggregated
                   See build_lag_df arguments for more details
               - "interval_list": List[tuple]
                   A list of tuples each of length 2.
                   Each tuple is used to construct an aggregated lag using all orders within that range
                   See build_agg_lag_df arguments for more details
               - "agg_func": "mean" or func (pd.Dataframe -> pd.Dataframe)
                   The function used for aggregation in "build_agg_lag_df"
                   If this key is not passed, the default of "build_agg_lag_df" will be used.
                   If "mean", uses `pandas.DataFrame.mean`.
  
       :param series_na_fill_func: (pd.Series -> pd.Series)
           default: lambda s: s.bfill.ffill()
           This function is used to fill in the missing data
           The default works by first back-filling and then forward-filling
           This function should not be applied to data before CV split is done.
       :return: dict
           a dictionary with following items
  
               - "build_lags_func": func
                   pd.Daframe -> dict(lag_df=pd.DataFrame, agg_lag_df=pd.DataFrame)
                   A function which takes a df (need to have value_col) as input
                   calculates the lag_df and agg_lag_df and returns them
               - "lag_col_names": Optional[List[str]]
                   The list of generated column names for the returned lag_df
                   when "build_lags_func" is applied
               - "agg_lag_col_names": Optional[List[str]]
                   The list of generated column names for returned agg_lag_df when
                   "build_lags_func" is applied
               - "max_order": int
                   the maximum lag order needed in the calculation of "build_lags_func"
               - "min_order": int
                   the minimum lag order needed in the calculation of "build_lags_func"
  
      """
      # building arguments for passing to build_lag_df
      # when lag_dict is not None
      build_lag_df_args = None
      if lag_dict is not None:
          build_lag_df_args = {"value_col": value_col}
          build_lag_df_args.update(lag_dict)
  
      # building arguments for passing to build_agg_lag_df
      # when agg_lag_dict is not None
      build_agg_lag_df_args = None
      if agg_lag_dict is not None:
          build_agg_lag_df_args = {"value_col": value_col}
          build_agg_lag_df_args.update(agg_lag_dict)
  
      # we get the col_names for lag_df
      lag_col_names = None
      if lag_dict is not None:
          lag_info = build_lag_df(df=None, **build_lag_df_args)
          lag_col_names = lag_info["col_names"]
  
      # we get col_names for agg_lag_df
      agg_lag_col_names = None
      if agg_lag_dict is not None:
          agg_lag_info = build_agg_lag_df(
              df=None, **build_agg_lag_df_args
          )
          agg_lag_col_names = agg_lag_info["col_names"]
  
      # we find out the max_order needed
      # outside the internal function: build_lags_func
      min_max_order = min_max_lag_order(
          lag_dict=build_lag_df_args, agg_lag_dict=build_agg_lag_df_args
      )
      max_order = min_max_order["max_order"]
      min_order = min_max_order["min_order"]
  
      def build_lags_func(df, past_df=None):
          """A function which uses:
              df (pd.Dataframe), past_df (pd.DataFrame)
              and returns lag_df and agg_lag_df for df.
              This function infers some parameters
              e.g. value_col, max_order, series_na_fill_func from its environment.
          :param df: pd.DataFrame
               The input dataframe which is expected to have value_col as a column
               The returned lag_df and agg_lag_df are generated for df
               (past_df will be used in calculation if provided)
          :param past_df: Optional[pd.DataFrame]
              When provided it will be appended to df (from left)
              in order for the lags to be calculated.
              past_df is considered to include the past values for the time series
              leading up to df values. So if df values start at time t0:
                  Y(t0), ..., Y(t0 + len(df))
              past_df will include these values
                  Y(t-len(past_df)), ..., Y(t0-1)
              Note that the last value in past_df is the immediate value in time
              before t0 which is the first time in df.
              Also note that we do not require a timestamp column in df and past_df
              as that is not needed in the logic.
              If past_df is None, and series_na_fill_func is also None.
              Therefore lag_df and agg_lag_df will include NULLs at the beginning
              depending on how many lags are calculated
          :return: dict
              A dictionary including lag_df and agg_lag_df for the input df
              The dictionary includes following items:
                  - "lag_df": Optional[pd.DataFrame]
                  - "agg_lag_df": Optional[pd.DataFrame]
          """
          df = df[[value_col]].reset_index(drop=True)
  
          # if past_df is None, we create one with np.nan
          # also if it is shorter than max_order we expand it with np.nan
          if past_df is None:
              past_df = pd.DataFrame({value_col: [np.nan] * max_order})
          else:
              if value_col not in list(past_df.columns):
                  raise ValueError(
                      f"{value_col} must appear in past_df if past_df is not None"
                  )
              past_df = past_df[[value_col]].reset_index(drop=True)
              # if past_df length (number of rows) is smaller than max_order
              # we expand it to avoid NULLs
              if past_df.shape[0] < max_order:
                  past_df_list = [
                      pd.DataFrame(
                          {
                              value_col: [np.nan]
                              * (max_order - past_df.shape[0])
                          }
                      ),
                      past_df,
                  ]
                  past_df = pd.concat(past_df_list)
  
          # df is expanded by adding past_df as the past data for df
          # this will help in avoiding NULLs to appear in lag_df and agg_lag_df
          # as long as past_df has data in it or expanded df is interpolated
          df_expanded = pd.concat([past_df, df])
          if series_na_fill_func is not None:
              df_expanded[value_col] = series_na_fill_func(
                  df_expanded[value_col]
              )
  
          # we get the col_names for lag_df
          lag_df = None
          if lag_dict is not None:
              lag_info = build_lag_df(
                  df=df_expanded, **build_lag_df_args
              )
              # since the lag calculation is done on expanded dataframe (`df_expanded`)
              # we need to pick only the relevant rows which match original
              # dataframe (via `iloc`)
              lag_df = (
                  lag_info["lag_df"]
                  .iloc[-(df.shape[0]) :]
                  .reset_index(drop=True)
              )
              # cast dtype to 'bool' if original dtype is 'bool', need to make sure there is no NaN
              # there is an edge case where ``df[value_col]`` is NaN but ``past_df[value_col]`` is not
              # e.g., in predict phase with autoregression, ``df_fut[value_col]`` would be NaN
              if (
                  df[value_col].dtype == "bool"
                  or past_df[value_col].dtype == "bool"
              ) and lag_df.isna().sum().sum() == 0:
                  lag_df = lag_df.astype("bool")
  
          # we get col_names for agg_lag_df
          agg_lag_df = None
          if agg_lag_dict is not None:
              agg_lag_info = build_agg_lag_df(
                  df=df_expanded, **build_agg_lag_df_args
              )
              # since the lag calculation is done on expanded dataframe (`df_expanded`)
              # we need to pick only the relevant rows which match original
              # dataframe (via `iloc`)
              agg_lag_df = (
                  agg_lag_info["agg_lag_df"]
                  .iloc[-(df.shape[0]) :]
                  .reset_index(drop=True)
              )
  
          return {"lag_df": lag_df, "agg_lag_df": agg_lag_df}
  
      return {
          "build_lags_func": build_lags_func,
          "lag_col_names": lag_col_names,
          "agg_lag_col_names": agg_lag_col_names,
          "min_order": min_order,
          "max_order": max_order,
      }
  
imports: import numpy
  import pandas
  from greykite.common import constants
label_desc: Use image node_id 4 for calling a global function with example usage: build_autoreg_df(value_col, lag_dict, agg_lag_dict, series_na_fill_func) and returns: dict, dict