class_docstr: A base class for forecast estimators that fit using
  `~greykite.algo.forecast.silverkite.forecast_silverkite.SilverkiteForecast.forecast`.
  
  Notes
  -----
  Allows estimators that fit using
  `~greykite.algo.forecast.silverkite.forecast_silverkite.SilverkiteForecast.forecast`
  to share the same functions for input data validation,
  fit postprocessing, predict, summary, plot_components, etc.
  
  Subclasses should:
  
      - Implement their own ``__init__`` that uses a superset of the parameters here.
      - Implement their own ``fit``, with this sequence of steps:
  
          - calls ``super().fit``
          - calls ``SilverkiteForecast.forecast`` or ``SimpleSilverkiteForecast.forecast_simple`` and stores the result in ``self.model_dict``
          - calls ``super().finish_fit``
  
  Uses ``coverage`` to set prediction band width. Even though
  coverage is not needed by ``forecast_silverkite``, it is included
  in every ``BaseForecastEstimator`` to be used universally for
  forecast evaluation.
  
  Therefore, ``uncertainty_dict`` must be consistent with ``coverage``
  if provided as a dictionary. If ``uncertainty_dict`` is None or
  "auto", an appropriate default value is set, according to ``coverage``.
  
  Parameters
  ----------
  score_func : callable, optional, default mean_squared_error
      See `~greykite.sklearn.estimator.base_forecast_estimator.BaseForecastEstimator`.
  coverage : `float` between [0.0, 1.0] or None, optional
      See `~greykite.sklearn.estimator.base_forecast_estimator.BaseForecastEstimator`.
  null_model_params : `dict`, optional
      Dictionary with arguments to define DummyRegressor null model, default is `None`.
      See `~greykite.sklearn.estimator.base_forecast_estimator.BaseForecastEstimator`.
  uncertainty_dict : `dict` or `str` or None, optional
      How to fit the uncertainty model.
      See `~greykite.algo.forecast.silverkite.forecast_silverkite.SilverkiteForecast.forecast`.
      Note that this is allowed to be "auto". If None or "auto", will be set to
      a default value by ``coverage`` before calling ``forecast_silverkite``.
  
  Attributes
  ----------
  silverkite : Class or a derived class of `~greykite.algo.forecast.silverkite.forecast_silverkite.SilverkiteForecast`
      The silverkite algorithm instance used for forecasting
  model_dict : `dict` or None
      A dict with fitted model and its attributes.
      The output of `~greykite.algo.forecast.silverkite.forecast_silverkite.SilverkiteForecast.forecast`.
  pred_cols : `list` [`str`] or None
      Names of the features used in the model.
  feature_cols : `list` [`str`] or None
      Column names of the patsy design matrix built by
      `~greykite.algo.common.ml_models.design_mat_from_formula`.
  df : `pandas.DataFrame` or None
      The training data used to fit the model.
  coef_ : `pandas.DataFrame` or None
      Estimated coefficient matrix for the model.
      Not available for ``random forest`` and ``gradient boosting`` methods and
      set to the default value `None`.
  _pred_category : `dict` or None
      A dictionary with keys being the predictor category and
      values being the predictors belonging to the category.
      For details, see
      `~greykite.sklearn.estimator.base_silverkite_estimator.BaseSilverkiteEstimator.pred_category`.
  extra_pred_cols : `list` or None
      User provided extra predictor names, for details, see
      `~greykite.sklearn.estimator.simple_silverkite_estimator.SimpleSilverkiteEstimator`
      or
      `~greykite.sklearn.estimator.silverkite_estimator.SilverkiteEstimator`.
  past_df : `pandas.DataFrame` or None
      The extra past data before training data used to generate autoregression terms.
  forecast : `pandas.DataFrame` or None
      Output of ``predict_silverkite``, set by ``self.predict``.
  forecast_x_mat : `pandas.DataFrame` or None
      The design matrix of the model at the predict time.
  model_summary : `class` or `None`
      The `~greykite.algo.common.model_summary.ModelSummary` class.
  
  See Also
  --------
  `~greykite.algo.forecast.silverkite.forecast_silverkite.SilverkiteForecast.forecast`
      Function performing the fit and predict.
  
  Notes
  -----
  The subclasses will pass ``fs_components_df`` to ``forecast_silverkite``. The model terms
  it creates internally are used to generate the component plots.
  
      - `~greykite.common.features.timeseries_features.fourier_series_multi_fcn` uses
        ``fs_components_df["names"]`` (e.g. ``tod``, ``tow``) to build the fourier series
        and to create column names.
  
      - ``fs_components_df["seas_names"]`` (e.g. ``daily``, ``weekly``) is appended
        to the column names, if provided.
  
  `~greykite.sklearn.estimator.base_silverkite_estimator.BaseSilverkiteEstimator.plot_components` relies
  on a regular expression dictionary to group components together.  There are two available in the library, see
  `~greykite.common.constants` for the two definitions
  
      1. "DEFAULT_COMPONENTS_REGEX_DICT"
         Grouped seasonality that is the default
      2. "DETAILED_SEASONALITY_COMPONENTS_REGEX_DICT":
         A detailed seasonality breakdown where the user can view daily/weekly/monthly/quarterly/yearly seasonality
docstr: Class method to plot the components of a ``Silverkite`` model on datasets passed to either
  ``fit`` or ``predict``.
  
  Parameters
  ----------
  grouping_regex_patterns_dict : `dict`, optional, default None
      If None, it is set to `~greykite.common.constants.DEFAULT_COMPONENTS_REGEX_DICT`.
      An alternative dictionary is available that provides a more detailed breakdown of
      seasonality components (e.g., weekly, monthly, quarterly, yearly, etc.), See:
      `~greykite.common.constants.DETAILED_SEASONALITY_COMPONENTS_REGEX_DICT`.
  center_components : `bool`, optional, default True
      It determines if components should be centered at their mean and the mean
      be added to the intercept. More concretely if a component is "x" then it will
      be mapped to "x - mean(x)"; and "mean(x)" will be added to the intercept so
      that the sum of the components remains the same.
      See `~greykite.sklearn.estimator.base_silverkite_estimator.forecast_breakdown`.
  denominator : `str`, optional, default None
      If not None, it will specify a way to divide the components. There are
      two options implemented:
  
      - "abs_y_mean" : `float`
          The absolute value of the observed mean of the response
      - "y_std" : `float`
          The standard deviation of the observed response
      See `~greykite.sklearn.estimator.base_silverkite_estimator.forecast_breakdown`.
  predict_phase: `bool`, optional, default False
      If False, plots the components of the training data and shows three plots: 1) Component
      Plot, 2) Trend Plot + Change points, and 3) Residuals + Smoothed Residuals.
      If set to True, plots the component breakdown of the predicted values. When set to True,
      it only plots one plot, the component plot, as there are no change points or residuals
      in this time frame.
  title: `str`, optional, default None
      Title of the plot.
  
  Returns
  -------
  fig: `plotly.graph_objects.Figure`
      Figure plotting components against appropriate time scale. Plot layout includes:
      - Plot 1, "Component Plot" - breakdown from forecast_breakdown
      - Plot 2, "Trend + Change Points"
      - Plot 3, "Residuals + Smoothed Residuals"; smoothing done using exponentially weighted moving average
code: def plot_components(
      self,
      grouping_regex_patterns_dict=None,
      center_components=True,
      denominator=None,
      predict_phase=False,
      title=None,
  ):
      """Class method to plot the components of a ``Silverkite`` model on datasets passed to either
      ``fit`` or ``predict``.
  
      Parameters
      ----------
      grouping_regex_patterns_dict : `dict`, optional, default None
          If None, it is set to `~greykite.common.constants.DEFAULT_COMPONENTS_REGEX_DICT`.
          An alternative dictionary is available that provides a more detailed breakdown of
          seasonality components (e.g., weekly, monthly, quarterly, yearly, etc.), See:
          `~greykite.common.constants.DETAILED_SEASONALITY_COMPONENTS_REGEX_DICT`.
      center_components : `bool`, optional, default True
          It determines if components should be centered at their mean and the mean
          be added to the intercept. More concretely if a component is "x" then it will
          be mapped to "x - mean(x)"; and "mean(x)" will be added to the intercept so
          that the sum of the components remains the same.
          See `~greykite.sklearn.estimator.base_silverkite_estimator.forecast_breakdown`.
      denominator : `str`, optional, default None
          If not None, it will specify a way to divide the components. There are
          two options implemented:
  
          - "abs_y_mean" : `float`
              The absolute value of the observed mean of the response
          - "y_std" : `float`
              The standard deviation of the observed response
          See `~greykite.sklearn.estimator.base_silverkite_estimator.forecast_breakdown`.
      predict_phase: `bool`, optional, default False
          If False, plots the components of the training data and shows three plots: 1) Component
          Plot, 2) Trend Plot + Change points, and 3) Residuals + Smoothed Residuals.
          If set to True, plots the component breakdown of the predicted values. When set to True,
          it only plots one plot, the component plot, as there are no change points or residuals
          in this time frame.
      title: `str`, optional, default None
          Title of the plot.
  
      Returns
      -------
      fig: `plotly.graph_objects.Figure`
          Figure plotting components against appropriate time scale. Plot layout includes:
          - Plot 1, "Component Plot" - breakdown from forecast_breakdown
          - Plot 2, "Trend + Change Points"
          - Plot 3, "Residuals + Smoothed Residuals"; smoothing done using exponentially weighted moving average
      """
      if self.model_dict is None:
          raise NotFittedError(
              "Call `fit` before calling `plot_components`."
          )
      if self.forecast_x_mat is None and predict_phase is True:
          raise ValueError(
              "Call the predict method before calling `plot_components` to generate forecasts"
          )
  
      if not hasattr(self.model_dict["ml_model"], "coef_"):
          raise NotImplementedError(
              "Component plot has only been implemented for additive linear models."
          )
  
      if type(center_components) is not bool:
          raise TypeError("center_components must be bool: True/False")
  
      if denominator is not None:
          if denominator not in ["abs_y_mean", "y_std"]:
              raise ValueError(
                  "Choose denominator from: ['abs_y_mean', 'y_std']"
              )
  
      # Defines regex dictionary to be the default component dictionary:
      if grouping_regex_patterns_dict is None:
          grouping_regex_patterns_dict = (
              cst.DEFAULT_COMPONENTS_REGEX_DICT
          )
      if type(grouping_regex_patterns_dict) is not dict:
          raise TypeError(
              "grouping_regex_patterns_dict must be type dict"
          )
      if len(grouping_regex_patterns_dict) == 0:
          raise ValueError(
              "grouping_regex_patterns_dict must be non-empty"
          )
  
      # Chooses `x_mat` and `time_values` to use
      # Creates default plot title if not user supplied
      if not predict_phase:
          x_mat = self.model_dict["x_mat"].reset_index(drop=True)
          time_values = pd.to_datetime(
              self.model_dict["df_dropna"][self.model_dict["time_col"]]
          )
          if title is None:
              title = "Component Plot - Training"
      else:
          train_end_date = self.model_dict["last_date_for_fit"]
          x_mat = self.forecast_x_mat.reset_index(drop=True)
          time_values = pd.to_datetime(self.forecast[self.time_col_])
          if title is None:
              title = "Component Plot - Predicted"
  
      # Builds a forecast breakdown
      breakdown = self.forecast_breakdown(
          grouping_regex_patterns_dict=grouping_regex_patterns_dict,
          forecast_x_mat=x_mat,
          time_values=time_values,
          center_components=center_components,
          denominator=denominator,
          plt_title=title,
      )
  
      # Selects results for component dataframe
      df = breakdown["breakdown_df"]
      self.fit_components = df
  
      # Collects change points from estimator
      changepoint_columns = [
          x.split(":")[0]
          for x in self.model_dict["x_mat"].columns
          if re.match("changepoint", x) is not None
      ]
      change_points = get_trend_changepoint_dates_from_cols(
          trend_cols=set(changepoint_columns)
      )
  
      # Calculates residuals and a smoothed estimate of residuals
      y_true = self.model_dict["y_train"].values
      y_pred = self.model_dict["y_train_pred"]
      residuals = pd.Series(y_true - y_pred)
      residuals_smoothed = residuals.ewm(
          int(len(residuals) / 50)
      ).mean()
  
      # Defines a color palette for the figure
      line_colors = px.colors.qualitative.Bold
  
      # Creates the figure
      if predict_phase:
          num_rows = 1
          subplot_title_set = ["Component plot"]
      else:
          num_rows = 3
          subplot_title_set = [
              "Component plot",
              "Trend and change points",
              "Residuals",
          ]
  
      fig = make_subplots(
          rows=num_rows,
          cols=1,
          vertical_spacing=0.5 / num_rows,
          subplot_titles=subplot_title_set,
          shared_xaxes=True,
      )
  
      # Panel 1 - Adds breakdown component traces to the figure
      for i, column_name in enumerate(df.columns):
          if column_name != "Trend":
              fig.add_trace(
                  go.Scatter(
                      x=time_values,
                      y=df[column_name],
                      name=column_name,
                      line=go.scatter.Line(color=line_colors[i]),
                      opacity=0.8,
                  ),
                  row=1,
                  col=1,
              )
          else:
              # If "Trend" line, adds legendgroup so that trend lines toggle on/off together
              fig.add_trace(
                  go.Scatter(
                      x=time_values,
                      y=df[column_name],
                      name=column_name,
                      line=go.scatter.Line(color=line_colors[i]),
                      opacity=0.8,
                      legendgroup="TrendGroup",
                  ),
                  row=1,
                  col=1,
              )
  
      # Adds rangeslider under the first panel
      fig.update_xaxes(
          rangeslider_visible=True,
          rangeslider_thickness=0.05,
          title_text="Date",
          row=1,
          col=1,
      )
      # By default, rangeslider turns off vertical zoom, this turns it back on
      fig.update_yaxes(fixedrange=False)
  
      # Only plot second and third panels if not predict_phase
      if predict_phase:
          if (
              train_end_date is not None
              and train_end_date in time_values.to_list()
          ):
              new_layout = dict(
                  # Adds vertical line
                  shapes=[
                      dict(
                          type="line",
                          xref="x",
                          yref="paper",  # y-reference is assigned to the plot paper [0,1]
                          x0=pd.to_datetime(train_end_date),
                          y0=0,
                          x1=pd.to_datetime(train_end_date),
                          y1=1,
                          line=dict(
                              color="rgba(100, 100, 100, 0.9)",
                              width=1.0,
                          ),
                      )
                  ],
                  # Adds text annotation
                  annotations=[
                      dict(
                          xref="x",
                          xanchor="right",
                          yanchor="middle",
                          x=pd.to_datetime(train_end_date),
                          yref="paper",
                          y=0.97,
                          text="Train End Date",
                          showarrow=True,
                          arrowhead=0,
                          ax=-20,
                          axref="pixel",
                          ay=0,
                      )
                  ],
              )
              fig.update_layout(new_layout)
  
          # Updates title based on user input, centers the title, adjusts spacing, and turns on tick labels for all plots
          fig.update_layout(title={"text": title, "x": 0.5})
      else:
          # Panel 2 - Adds trace for trend plot
          fig.add_trace(
              go.Scatter(
                  x=time_values,
                  y=df["Trend"],
                  name="Trend",
                  line=go.scatter.Line(
                      color=line_colors[
                          df.columns.to_list().index("Trend")
                      ]
                  ),
                  opacity=0.8,
                  legendgroup="TrendGroup",
                  showlegend=False,
              ),
              row=2,
              col=1,
          )
  
          # Panel 2 - Adds traces for changepoints. All change groups are one group in legend so they toggle on/off together.
          if change_points is not None:
              for cp_num, cp in enumerate(change_points):
                  if cp_num == 0:
                      in_legend = True
                  else:
                      in_legend = False
  
                  fig.add_trace(
                      go.Scatter(
                          x=[pd.to_datetime(cp), pd.to_datetime(cp)],
                          y=[df["Trend"].min(), df["Trend"].max()],
                          name="Changepoints",
                          legendgroup="Changepoints",
                          mode="lines",
                          line=go.scatter.Line(
                              color="#000000",  # black
                              width=2,
                              dash="dot",
                          ),
                          opacity=0.75,
                          showlegend=in_legend,
                      ),
                      row=2,
                      col=1,
                  )
  
          # Panel 3 - Adds traces for the residuals and smoothed residuals
          fig.add_trace(
              go.Scatter(
                  x=pd.to_datetime(
                      self.df[self.model_dict["time_col"]]
                  ),
                  y=residuals,
                  name="Residuals",
                  line=go.scatter.Line(color="rgb(0,0,0)"),
                  opacity=0.75,
              ),
              row=3,
              col=1,
          )
          fig.add_trace(
              go.Scatter(
                  x=pd.to_datetime(
                      self.df[self.model_dict["time_col"]]
                  ),
                  y=residuals_smoothed,
                  name="Smoothed Residuals",
                  line=go.scatter.Line(color="rgb(250,237,9)"),
                  opacity=0.75,
              ),
              row=3,
              col=1,
          )
  
          # Updates title based on user input, centers the title, adjusts spacing, and turns on tick labels for all plots
          fig.update_layout(
              title={"text": title, "x": 0.5},
              height=350 * num_rows,
              xaxis_showticklabels=True,
              xaxis2_showticklabels=True,
          )
  
      return fig
  
imports: import re
  from typing import Dict
  from typing import Optional
  from typing import Type
  import pandas
  import plotly.express
  from pandas.tseries.frequencies import to_offset
  from plotly import graph_objects
  from plotly.subplots import make_subplots
  from sklearn.exceptions import NotFittedError
  from sklearn.metrics import mean_squared_error
  from greykite.algo.changepoint.adalasso.changepoints_utils import get_trend_changepoint_dates_from_cols
  from greykite.algo.common.col_name_utils import create_pred_category
  from greykite.algo.common.ml_models import breakdown_regression_based_prediction
  from greykite.algo.common.model_summary import ModelSummary
  from greykite.algo.forecast.silverkite.constants.silverkite_component import SilverkiteComponentsEnum
  from greykite.algo.forecast.silverkite.constants.silverkite_constant import default_silverkite_constant
  from greykite.algo.forecast.silverkite.forecast_silverkite import SilverkiteForecast
  from greykite.algo.forecast.silverkite.forecast_silverkite_helper import get_silverkite_uncertainty_dict
  from greykite.common import constants
  from greykite.common.features.timeseries_lags import min_max_lag_order
  from greykite.common.time_properties import min_gap_in_seconds
  from greykite.common.time_properties_forecast import get_simple_time_frequency_from_period
  from greykite.sklearn.estimator.base_forecast_estimator import BaseForecastEstimator
  from greykite.sklearn.uncertainty.uncertainty_methods import UncertaintyMethodEnum
label_desc: Use image node_id 10 for calling the BaseSilverkiteEstimator obj's underlying member method code with example usage: obj.plot_components(grouping_regex_patterns_dict, center_components, denominator, predict_phase, title) and returns: fig