code: def get_canonical_data(
      df: pd.DataFrame,
      time_col: str = TIME_COL,
      value_col: str = VALUE_COL,
      freq: str = None,
      date_format: str = None,
      tz: str = None,
      train_end_date: Optional[Union[str, datetime.datetime]] = None,
      regressor_cols: List[str] = None,
      lagged_regressor_cols: List[str] = None,
      anomaly_info: Optional[Union[Dict, List[Dict]]] = None,
  ):
      """Loads data to internal representation. Parses date column,
      sets timezone aware index.
      Checks for irregularities and raises an error if input is invalid.
      Adjusts for anomalies according to ``anomaly_info``.
  
      Parameters
      ----------
      df : `pandas.DataFrame`
          Input timeseries. A data frame which includes the timestamp column
          as well as the value column.
      time_col : `str`
          The column name in ``df`` representing time for the time series data.
          The time column can be anything that can be parsed by pandas DatetimeIndex.
      value_col: `str`
          The column name which has the value of interest to be forecasted.
      freq : `str` or None, default None
          Timeseries frequency, DateOffset alias, If None automatically inferred.
      date_format : `str` or None, default None
          strftime format to parse time column, eg ``%m/%d/%Y``.
          Note that ``%f`` will parse all the way up to nanoseconds.
          If None (recommended), inferred by `pandas.to_datetime`.
      tz : `str` or pytz.timezone object or None, default None
          Passed to `pandas.tz_localize` to localize the timestamp.
      train_end_date : `str` or `datetime.datetime` or None, default None
          Last date to use for fitting the model. Forecasts are generated after this date.
          If None, it is set to the minimum of ``self.last_date_for_val`` and
          ``self.last_date_for_reg``.
      regressor_cols: `list` [`str`] or None, default None
          A list of regressor columns used in the training and prediction DataFrames.
          If None, no regressor columns are used.
          Regressor columns that are unavailable in ``df`` are dropped.git
      lagged_regressor_cols: `list` [`str`] or None, default None
          A list of additional columns needed for lagged regressors in thggede training and prediction DataFrames.
          This list can have overlap with ``regressor_cols``.
          If None, no additional columns are added to the DataFrame.
          Lagged regressor columns that are unavailable in ``df`` are dropped.
      anomaly_info : `dict` or `list` [`dict`] or None, default None
          Anomaly adjustment info. Anomalies in ``df``
          are corrected before any forecasting is done.
  
          If None, no adjustments are made.
  
          A dictionary containing the parameters to
          `~greykite.common.features.adjust_anomalous_data.adjust_anomalous_data`.
          See that function for details.
          The possible keys are:
  
              ``"value_col"`` : `str`
                  The name of the column in ``df`` to adjust. You may adjust the value
                  to forecast as well as any numeric regressors.
              ``"anomaly_df"`` : `pandas.DataFrame`
                  Adjustments to correct the anomalies.
              ``"start_time_col"``: `str`, default START_TIME_COL
                  Start date column in ``anomaly_df``.
              ``"end_time_col"``: `str`, default END_TIME_COL
                  End date column in ``anomaly_df``.
              ``"adjustment_delta_col"``: `str` or None, default None
                  Impact column in ``anomaly_df``.
              ``"filter_by_dict"``: `dict` or None, default None
                  Used to filter ``anomaly_df`` to the relevant anomalies for
                  the ``value_col`` in this dictionary.
                  Key specifies the column name, value specifies the filter value.
              ``"filter_by_value_col""``: `str` or None, default None
                  Adds ``{filter_by_value_col: value_col}`` to ``filter_by_dict``
                  if not None, for the ``value_col`` in this dictionary.
              ``"adjustment_method"`` : `str` ("add" or "subtract"), default "add"
                  How to make the adjustment, if ``adjustment_delta_col`` is provided.
  
          Accepts a list of such dictionaries to adjust multiple columns in ``df``.
  
      Returns
      -------
      canonical_data_dict : `dict`
          Dictionary containing the dataset in canonical form, and information such as
          train end date. Keys:
  
              ``"df"`` : `pandas.DataFrame`
                  Data frame containing timestamp and value, with standardized column names for internal use
                  (TIME_COL, VALUE_COL). Rows are sorted by time index, and missing gaps between dates are filled
                  in so that dates are spaced at regular intervals. Values are adjusted for anomalies
                  according to ``anomaly_info``.
                  The index can be timezone aware (but TIME_COL is not).
              ``"df_before_adjustment"`` : `pandas.DataFrame` or None
                  ``df`` before adjustment by ``anomaly_info``.
                  If ``anomaly_info`` is None, this is None.
              ``"fit_df"`` : `pandas.DataFrame`
                  A subset of the returned ``df``, with data up until ``train_end_date``.
              ``"freq"`` : `pandas.DataFrame`
                  timeseries frequency, inferred if not provided
              ``"time_stats"`` : `dict`
                  Information about the time column:
  
                      ``"gaps"``: missing_dates
                      ``"added_timepoints"``: added_timepoints
                      ``"dropped_timepoints"``: dropped_timepoints
  
              ``"regressor_cols"`` : `list` [`str`]
                  A list of regressor columns.
               ``"lagged_regressor_cols"`` : `list` [`str`]
                  A list of lagged regressor columns.
              ``"fit_cols"`` : `list` [`str`]
                  Names of time column, value column, regressor columns, and lagged regressor columns.
              ``"train_end_date"`` : `datetime.datetime`
                  Last date or timestamp for training. It is always less than or equal to
                  minimum non-null values of ``last_date_for_val`` and ``last_date_for_reg``.
              ``"last_date_for_val"`` : `datetime.datetime`
                  Date or timestamp corresponding  to last non-null value in ``df[value_col]``.
              ``"last_date_for_reg"`` : `datetime.datetime` or None
                  Date or timestamp corresponding to last non-null value in ``df[regressor_cols]``.
                  If ``regressor_cols`` is None, ``last_date_for_reg`` is None.
              ``"last_date_for_lag_reg"`` : `datetime.datetime` or None
                  Date or timestamp corresponding to last non-null value in ``df[lagged_regressor_cols]``.
                  If ``lagged_regressor_cols`` is None, ``last_date_for_lag_reg`` is None.
      """
      if time_col not in df.columns:
          raise ValueError(f"{time_col} column is not in input data")
      if value_col not in df.columns:
          raise ValueError(f"{value_col} column is not in input data")
      if df.shape[0] <= 2:
          raise ValueError(
              f"Time series has < 3 observations. More data are needed for forecasting."
          )
  
      # Standardizes the time column name.
      # `value_col` is standardized after anomalies are adjusted.
      df_standardized = df.rename(
          {
              time_col: TIME_COL,
          },
          axis=1,
      )
      df_standardized[TIME_COL] = pd.to_datetime(
          df_standardized[TIME_COL],
          format=date_format,
          infer_datetime_format=True,
      )
      # Drops data points from duplicate time stamps
      df_standardized.drop_duplicates(
          subset=[TIME_COL], keep="first", inplace=True
      )
      if df.shape[0] > df_standardized.shape[0]:
          warnings.warn(
              f"Duplicate timestamps have been removed.", UserWarning
          )
      df = df_standardized.sort_values(by=TIME_COL)
      # Infers data frequency
      inferred_freq = infer_freq(df, TIME_COL)
      if freq is None:
          freq = inferred_freq
      elif inferred_freq is not None and freq != inferred_freq:
          warnings.warn(
              f"Provided frequency '{freq}' does not match inferred frequency '{inferred_freq}'."
              f" Using '{freq}'.",
              UserWarning,
          )  # NB: with missing data, it's better to provide freq
      # Handles gaps in time series
      missing_dates = find_missing_dates(df[TIME_COL])
      df, added_timepoints, dropped_timepoints = fill_missing_dates(
          df, time_col=TIME_COL, freq=freq
      )
      time_stats = {
          "gaps": missing_dates,
          "added_timepoints": added_timepoints,
          "dropped_timepoints": dropped_timepoints,
      }
      # Creates index with localized timestamp
      df.index = df[TIME_COL]
      df.index.name = None
      if tz is not None:
          df = df.tz_localize(tz)
  
      # Replaces infinity values in `value_col` by `np.nan`
      df[value_col].replace([np.inf, -np.inf], np.nan, inplace=True)
  
      # Saves values before adjustment.
      df_original_value_col = df.copy()
      # Standardizes `value_col` name.
      df.rename({value_col: VALUE_COL}, axis=1, inplace=True)
  
      # Finds date of last available value.
      # - `last_date_for_val` is the last timestamp with non-null values in `VALUE_COL`.
      # - `last_date_for_reg` is the last timestamp with non-null values in `regressor_cols`.
      # - `max_train_end_date` is inferred as the minimum of the above two.
      # `max_train_end_date` will be used to determine `train_end_date` when the latter is not provided.
      last_date_available = df[TIME_COL].max()
      last_date_for_val = df[df[VALUE_COL].notnull()][TIME_COL].max()
      last_date_for_reg = None
      if regressor_cols:
          available_regressor_cols = [
              col
              for col in df.columns
              if col not in [TIME_COL, VALUE_COL]
          ]
          cols_not_selected = set(regressor_cols) - set(
              available_regressor_cols
          )
          regressor_cols = [
              col
              for col in regressor_cols
              if col in available_regressor_cols
          ]
          if cols_not_selected:
              warnings.warn(
                  f"The following columns are not available to use as "
                  f"regressors: {sorted(cols_not_selected)}"
              )
          last_date_for_reg = df[
              df[regressor_cols].notnull().any(axis=1)
          ][TIME_COL].max()
          max_train_end_date = min(last_date_for_val, last_date_for_reg)
      else:
          regressor_cols = []
          max_train_end_date = last_date_for_val
  
      # Chooses appropriate `train_end_date`.
      # Case 1: if not provided, the last timestamp with a non-null value (`max_train_end_date`) is used.
      # Case 2: if it is out of the range of the data, raises an error since it should not be allowed.
      # Case 3: otherwise, we respect the user's input `train_end_date`. NAs are kept and can be imputed in the pipeline.
      train_end_date = pd.to_datetime(train_end_date)
      if train_end_date is None:
          train_end_date = max_train_end_date
          warnings.warn(
              f"`train_end_date` is not provided, or {value_col} column of the provided time series contains "
              f"null values at the end, or the input `train_end_date` is beyond the last timestamp available. "
              f"Setting `train_end_date` to the last timestamp with a non-null value ({train_end_date}).",
              UserWarning,
          )
      elif train_end_date > last_date_available:
          # TODO: replace the warning with a `ValueError` and bump the version since it changes the behavior.
          train_end_date = max_train_end_date
          warnings.warn(
              f"{value_col} column of the provided time series contains "
              f"null values at the end, or the input `train_end_date` is beyond the last timestamp available. "
              f"Setting `train_end_date` to the last timestamp with a non-null value ({train_end_date}).",
              UserWarning,
          )
      elif train_end_date > max_train_end_date:
          # Does not modify the user-input `train_end_date`, but raises a warning.
          warnings.warn(
              f"{value_col} column of the provided time series contains trailing NAs. "
              f"These NA values will be imputed in the pipeline.",
              UserWarning,
          )
  
      df_before_adjustment = None
      if anomaly_info is not None:
          # Saves values before adjustment.
          df_before_adjustment = df_original_value_col.copy()
          # Adjusts columns in df (e.g. `value_col`, `regressor_cols`)
          # using the anomaly info. One dictionary of parameters
          # for `adjust_anomalous_data` is provided for each column to adjust.
          if not isinstance(anomaly_info, (list, tuple)):
              anomaly_info = [anomaly_info]
          for single_anomaly_info in anomaly_info:
              adjusted_df_dict = adjust_anomalous_data(
                  df=df_original_value_col,
                  time_col=TIME_COL,
                  **single_anomaly_info,
              )
              # `self.df` with values for single_anomaly_info["value_col"] adjusted.
              df_original_value_col = adjusted_df_dict["adjusted_df"]
          # Standardizes `value_col` name.
          df_before_adjustment.rename(
              {value_col: VALUE_COL}, axis=1, inplace=True
          )
      # Standardizes `value_col` name.
      df = df_original_value_col.rename(
          {value_col: VALUE_COL}, axis=1, inplace=False
      )
  
      # Processes lagged regressors.
      last_date_for_lag_reg = None
      if lagged_regressor_cols:
          available_regressor_cols = [
              col
              for col in df.columns
              if col not in [TIME_COL, VALUE_COL]
          ]
          cols_not_selected = set(lagged_regressor_cols) - set(
              available_regressor_cols
          )
          lagged_regressor_cols = [
              col
              for col in lagged_regressor_cols
              if col in available_regressor_cols
          ]
          if cols_not_selected:
              warnings.warn(
                  f"The following columns are not available to use as "
                  f"lagged regressors: {sorted(cols_not_selected)}"
              )
          last_date_for_lag_reg = df[
              df[lagged_regressor_cols].notnull().any(axis=1)
          ][TIME_COL].max()
      else:
          lagged_regressor_cols = []
  
      extra_reg_cols = [
          col
          for col in df.columns
          if col not in regressor_cols and col in lagged_regressor_cols
      ]
      fit_cols = [TIME_COL, VALUE_COL] + regressor_cols + extra_reg_cols
      fit_df = df[df[TIME_COL] <= train_end_date][fit_cols]
  
      return {
          "df": df,
          "df_before_adjustment": df_before_adjustment,
          "fit_df": fit_df,
          "freq": freq,
          "time_stats": time_stats,
          "regressor_cols": regressor_cols,
          "lagged_regressor_cols": lagged_regressor_cols,
          "fit_cols": fit_cols,
          "train_end_date": train_end_date,
          "last_date_for_val": last_date_for_val,
          "last_date_for_reg": last_date_for_reg,
          "last_date_for_lag_reg": last_date_for_lag_reg,
      }
  
file_path: greykite/greykite/common/time_properties.py
imports: import datetime
  import warnings
  from typing import Dict
  from typing import List
  from typing import Optional
  from typing import Union
  import numpy
  import pandas
  from greykite.common.constants import TIME_COL
  from greykite.common.constants import VALUE_COL
  from greykite.common.features.adjust_anomalous_data import adjust_anomalous_data
  from greykite.common.logging import LoggingLevelEnum
  from greykite.common.logging import log_message
label_desc: Use image node_id 5 for calling a global function with example usage: get_canonical_data(df, time_col, value_col, freq, date_format, tz, train_end_date, regressor_cols, lagged_regressor_cols, anomaly_info) and returns: dict