code: def plt_compare_series_annotations(
      df,
      x_col,
      actual_col,
      actual_label_col,
      forecast_label_col,
      forecast_col=None,
      keep_cols=None,
      fill_cols=None,
      standardize_col=None,
      title=None,
      x_label=None,
      y_label=None,
  ):
      """A function which plots ``actual_col`` and ``forecast_col`` and well as
          ``forecast_lower_col``, ``forecast_upper_col`` with respect to x axis
          (often time) given in ``x_col``. Then it annotates:
  
      - the ``actual_col`` with markers when ``actual_label_col`` is non-zero.
      - the ``forecast_col`` with markers when ``forecast_label_col`` is non-zero.
  
      This will help user to identify where the two annotations are consistent.
  
      Parameters
      ----------
      df : `pandas.DataFrame`
          Data frame with all columns used in this function (see arguments passed
              ending with "_col").
      x_col: `str`
          Which column to plot on the x-axis.
      actual_col: `str`
          The column in the dataframe which includes the actual values (ground truth).
      actual_label_col: `str`
          The column which has labels including 0, and will be used to annotate
          the values in ``actual_col`` for non-zero values. The non-zero labels
          are often considerd anomalies.
      forecast_label_col: `str`
          The column which has labels including 0, and will be used to annotate
          the values in ``forecast_col`` for non-zero values. The non-zero labels
          are often considerd anomalies.
      forecast_col : `str` or None, default None
          The column in the dataframe which includes the forecast values
      keep_cols: `List` [`str`] or None, default None
          Extra columns to be plotted. The color for these extra columns will be
          opaque grey so they do not be the center of attention in the plot.
          Often these columns will be model upper and lower bound predictions.
      fill_cols: `list` [`list` [`str`] ] or None, default None
          Fill between each list of columns in ``fill_cols``.
      standardize_col : `str` or `None`
          In case we like to standardize all values with respect to a column, that
          column name would be passed here. Often ``actual_col`` will be a good
          candidate to be used here.
      title : `str` or None, default None
          Plot title. If None, default is based on axis labels.
      x_label : `str` or None, default None
          x axis label, if None ``x_col`` will be used.
      y_label : `str` or None, default None
          y axis label, if None, no label will be used for y-axis. Note that the
          legend will include the label for various curves / markers.
  
  
      Returns
      -------
      fig : `plotly.graph_objects.Figure`
          Interactive plotly graph.
      """
  
      df = df.copy()
      df[f"model_anomaly"] = df[actual_col]
      df.loc[
          df[forecast_label_col].map(float) == 0, f"model_anomaly"
      ] = None
      df[f"true_anomaly"] = df[actual_col]
      df.loc[
          df[actual_label_col].map(float) == 0, f"true_anomaly"
      ] = None
  
      cols = [actual_col, f"model_anomaly", f"true_anomaly"]
  
      all_fill_cols = []
      # The columns in fill_cols will be handled separately.
      if fill_cols is not None and len(fill_cols) > 0:
          if not isinstance(fill_cols[0], list):
              raise ValueError(
                  f"fill_cols must be a list of lists of strings."
              )
          all_fill_cols = [c for c_list in fill_cols for c in c_list]
          if keep_cols is not None:
              # Removes a col in keep_cols if it is already in fill_cols.
              keep_cols = [
                  col for col in keep_cols if col not in all_fill_cols
              ]
  
      if keep_cols is not None:
          cols += keep_cols
  
      if forecast_col is not None:
          cols += [forecast_col]
  
      cols += all_fill_cols
  
      if standardize_col:
          x = df[standardize_col]
          for col in cols:
              df[col] = df[col] / x
  
      y_col_style_dict = {
          actual_col: {
              "mode": "lines",
              "line": {
                  "color": "rgba(0, 255, 0, 0.3)",
                  "dash": "solid",
              },
          },
          "true_anomaly": {
              "mode": "markers",
              "marker": dict(
                  color="rgba(255, 0, 0, 0.5)", symbol="square"
              ),
          },
          "model_anomaly": {
              "mode": "markers",
              "marker": dict(
                  color="rgba(0, 0, 255, 0.6)", symbol="star"
              ),
          },
      }
  
      if forecast_col is not None:
          y_col_style_dict[forecast_col] = {
              "mode": "lines",
              "line": {
                  "color": "rgba(0, 0, 255, 0.4)",
                  "dash": "solid",
              },
          }
  
      if keep_cols is not None:
          for i in range(len(keep_cols)):
              col = keep_cols[i]
              # this is to be able to distinguish between the ``keep_cols`` using opacity
              opacity = (i + 1) / (len(keep_cols) + 1)
              y_col_style_dict[col] = {
                  "mode": "lines",
                  "line": {
                      "color": f"rgba(191, 191, 191, {opacity})",
                      "dash": "solid",
                  },
              }
  
      # Instead of calling plot_multivariate, we make the figure manually to avoid the column order to be
      # flipped due to the fact that dictionary is unordered. This may affect the fill between certain columns.
      data = []
      # Plots fill_cols first to make them in the bottom layer.
      if fill_cols is not None:
          first_fill_color = "rgba(142, 215, 246, 0.3)"  # blue
  
          for g, col_list in enumerate(fill_cols):
              for i, col in enumerate(col_list):
                  line = go.Scatter(
                      x=df[x_col],
                      y=df[col],
                      name=col,
                      legendgroup=f"{g}",
                      mode="lines",
                      line=dict(
                          color=(first_fill_color if g == 0 else None)
                      ),
                      fill=("tonexty" if i != 0 else None),
                      fillcolor=(first_fill_color if g == 0 else None),
                  )
                  data.append(line)
  
      for column, style_dict in y_col_style_dict.items():
          style_dict = dict(name=column, **style_dict)
          line = go.Scatter(x=df[x_col], y=df[column], **style_dict)
          data.append(line)
  
      layout = go.Layout(
          xaxis=dict(title=(x_label if x_label is not None else x_col)),
          yaxis=dict(title=(y_label if y_label is not None else None)),
          title=title,
          title_x=0.5,
          showlegend=True,
      )
      fig = go.Figure(data=data, layout=layout)
  
      return fig
  
file_path: greykite/greykite/common/viz/timeseries_annotate.py
imports: import pandas
  import plotly.graph_objects
  from greykite.common.constants import ACTUAL_COL
  from greykite.common.constants import ANOMALY_COL
  from greykite.common.constants import END_TIME_COL
  from greykite.common.constants import PREDICTED_ANOMALY_COL
  from greykite.common.constants import PREDICTED_COL
  from greykite.common.constants import START_TIME_COL
  from greykite.common.constants import TIME_COL
  from greykite.common.features.adjust_anomalous_data import label_anomalies_multi_metric
  from greykite.common.viz.colors_utils import get_distinct_colors
  from greykite.common.viz.timeseries_plotting import plot_forecast_vs_actual
label_desc: Use image node_id 2 for calling a global function with example usage: plt_compare_series_annotations(df, x_col, actual_col, actual_label_col, forecast_label_col, forecast_col, keep_cols, fill_cols, standardize_col, title, x_label, y_label) and returns: fig