code: def test_flexible_grouping_evaluation():
      """Tests flexible_grouping_evaluation function"""
      nrows = 365
      df = pd.DataFrame(
          {
              cst.TIME_COL: pd.date_range(
                  start="2018-01-01", periods=nrows, freq="D"
              ),
              cst.ACTUAL_COL: np.random.random(nrows),
              cst.PREDICTED_COL: np.random.random(nrows),
              "groups": np.random.choice(list("ABCDE"), size=nrows),
          }
      )
  
      # no changes
      mapped_df = flexible_grouping_evaluation(
          df=df, map_func_dict=None
      )
      assert_frame_equal(mapped_df, df)
  
      # `map_func_dict`
      map_func_dict = {
          "abs_error": lambda row: np.abs(
              row[cst.PREDICTED_COL] - row[cst.ACTUAL_COL]
          ),
          "squared_error": lambda row: (
              row[cst.PREDICTED_COL] - row[cst.ACTUAL_COL]
          )
          ** 2,
      }
      mapped_df = flexible_grouping_evaluation(
          df=df, map_func_dict=map_func_dict
      )
  
      expected = df.copy()
      expected["abs_error"] = (
          df[cst.PREDICTED_COL] - df[cst.ACTUAL_COL]
      ).abs()
      expected["squared_error"] = (
          df[cst.PREDICTED_COL] - df[cst.ACTUAL_COL]
      ) ** 2
      assert_frame_equal(mapped_df, expected)
  
      with pytest.raises(
          ValueError,
          match="Must specify `agg_kwargs` if grouping is requested via `groupby_col`.",
      ):
          flexible_grouping_evaluation(
              df=df, map_func_dict=map_func_dict, groupby_col="groups"
          )
  
      # `agg_kwargs` with tuples for named aggregation
      agg_kwargs = {
          f"{cst.ACTUAL_COL}_mean": pd.NamedAgg(
              column=cst.ACTUAL_COL, aggfunc="mean"
          ),
          f"{cst.PREDICTED_COL}_min": pd.NamedAgg(
              column=cst.ACTUAL_COL, aggfunc="min"
          ),
          f"{cst.PREDICTED_COL}_max": pd.NamedAgg(
              column=cst.ACTUAL_COL, aggfunc="max"
          ),
          "squared_error_MedianSquaredError": pd.NamedAgg(
              column="squared_error", aggfunc=np.nanmedian
          ),
          "squared_error_MSE": pd.NamedAgg(
              column="squared_error", aggfunc=np.nanmean
          ),
          "abs_error_MAE": pd.NamedAgg(
              column="abs_error", aggfunc=np.nanmean
          ),
          "abs_error_q95_abs_error": pd.NamedAgg(
              column="abs_error",
              aggfunc=partial(np.nanquantile, q=0.95),
          ),
          "abs_error_q05_abs_error": pd.NamedAgg(
              column="abs_error",
              aggfunc=partial(np.nanquantile, q=0.05),
          ),
      }
      eval_df = flexible_grouping_evaluation(
          df=df,
          map_func_dict=map_func_dict,
          groupby_col="groups",
          agg_kwargs=agg_kwargs,
      )
      assert eval_df.shape[0] == df["groups"].nunique()
      assert eval_df.index.name == "groups"
      assert list(eval_df.columns) == [
          f"{cst.ACTUAL_COL}_mean",
          f"{cst.PREDICTED_COL}_min",
          f"{cst.PREDICTED_COL}_max",
          "squared_error_MedianSquaredError",
          "squared_error_MSE",
          "abs_error_MAE",
          "abs_error_q95_abs_error",
          "abs_error_q05_abs_error",
      ]
      assert all(
          eval_df["squared_error_MedianSquaredError"].values
          == expected.groupby("groups")["squared_error"]
          .agg("median")
          .values
      )
      assert all(
          eval_df["abs_error_MAE"].values
          == expected.groupby("groups")["abs_error"].agg("mean").values
      )
  
      with LogCapture(cst.LOGGER_NAME) as log_capture:
          flexible_grouping_evaluation(
              df=df, map_func_dict=map_func_dict, agg_kwargs=agg_kwargs
          )
          expected_message = (
              "`agg_kwargs` is ignored because `groupby_col` is None. "
              "Specify `groupby_col` to allow aggregation."
          )
          log_capture.check(
              (cst.LOGGER_NAME, "WARNING", expected_message)
          )
  
      # `agg_kwargs` with `func`
      agg_kwargs = {
          "func": {
              cst.ACTUAL_COL: "mean",
              cst.PREDICTED_COL: ["min", "max"],
              "squared_error": [np.nanmedian, np.nanmean],
              "abs_error": [
                  np.nanmean,
                  partial(np.nanquantile, q=0.95),
                  partial(np.nanquantile, q=0.05),
              ],
          }
      }
      eval_df = flexible_grouping_evaluation(
          df=df,
          map_func_dict=map_func_dict,
          groupby_col="groups",
          agg_kwargs=agg_kwargs,
      )
      assert eval_df.shape[0] == df["groups"].nunique()
      assert eval_df.index.name == "groups"
      assert list(eval_df.columns) == [
          f"{cst.ACTUAL_COL}_mean",
          f"{cst.PREDICTED_COL}_min",
          f"{cst.PREDICTED_COL}_max",
          "squared_error_nanmedian",
          "squared_error_nanmean",
          "abs_error_nanmean",
          "abs_error_nanquantile",
          "abs_error_nanquantile",
      ]  # duplicate name
  
      # same result without extended column names
      with pytest.warns(UserWarning) as record:
          eval_df2 = flexible_grouping_evaluation(
              df=df,
              map_func_dict=map_func_dict,
              groupby_col="groups",
              agg_kwargs=agg_kwargs,
              extend_col_names=False,
          )
          assert list(eval_df2.columns) == [
              "mean",
              "min",
              "max",
              "nanmedian",
              "nanmean",
              "nanmean",
              "nanquantile",
              "nanquantile",
          ]
          assert (
              "Column names are not unique. Use `extend_col_names=True` "
              "to uniquely identify every column."
              in record[0].message.args[0]
          )
  
      # same result with multilevel index
      eval_df = flexible_grouping_evaluation(
          df=df,
          map_func_dict=map_func_dict,
          groupby_col="groups",
          agg_kwargs=agg_kwargs,
          extend_col_names=None,
      )
      assert eval_df.columns.nlevels == 2
  
      # `agg_kwargs` with aggregation dictionary and list value
      agg_list = [np.nanmedian, np.nanmean]
      eval_df = flexible_grouping_evaluation(
          df=df,
          map_func_dict=None,
          groupby_col="groups",
          agg_kwargs={"func": agg_list},
      )
      eval_df = eval_df[
          [col for col in eval_df.columns if "ts" not in col]
      ]
      agg_dict = {  # equivalent dictionary specification
          "actual": [np.nanmedian, np.nanmean],
          "forecast": [np.nanmedian, np.nanmean],
      }
      eval_df_expected = flexible_grouping_evaluation(
          df=df,
          map_func_dict=None,
          groupby_col="groups",
          agg_kwargs={"func": agg_dict},
      )
      assert_frame_equal(eval_df, eval_df_expected)
  
      # `unpack_list` without `list_names_dict`
      actual_quantiles = [0.1, 0.2, 0.8, 0.9]
      forecast_quantiles = [0.25, 0.75]
      agg_kwargs = dict(
          actual_Q=pd.NamedAgg(
              column="actual",
              aggfunc=lambda grp_values: partial(
                  np.nanquantile, q=actual_quantiles
              )(grp_values).tolist(),
          ),
          forecast_Q=pd.NamedAgg(
              column="forecast",
              aggfunc=lambda grp_values: partial(
                  np.nanquantile, q=forecast_quantiles
              )(grp_values).tolist(),
          ),
      )
      eval_df = flexible_grouping_evaluation(
          df=df,
          map_func_dict=None,
          groupby_col="groups",
          agg_kwargs=agg_kwargs,
          extend_col_names=True,
          unpack_list=True,
      )
      assert list(eval_df.columns) == (
          [f"actual_Q{i}" for i in range(len(actual_quantiles))]
          + [f"forecast_Q{i}" for i in range(len(forecast_quantiles))]
      )
      assert np.all(eval_df["actual_Q0"] <= eval_df["actual_Q1"])
      assert np.all(eval_df["actual_Q1"] <= eval_df["actual_Q2"])
      assert np.all(eval_df["actual_Q2"] <= eval_df["actual_Q3"])
      assert np.all(eval_df["forecast_Q0"] <= eval_df["forecast_Q1"])
  
      # `unpack_list` with `list_names_dict`
      list_names_dict = {
          # keys match the keys in `agg_kwargs` above
          "actual_Q": [f"actual_Q{q}" for q in actual_quantiles],
          "forecast_Q": [f"forecast_Q{q}" for q in forecast_quantiles],
      }
      eval_df = flexible_grouping_evaluation(
          df=df,
          map_func_dict=None,
          groupby_col="groups",
          agg_kwargs=agg_kwargs,
          extend_col_names=True,
          unpack_list=True,
          list_names_dict=list_names_dict,
      )
      assert (
          list(eval_df.columns)
          == list_names_dict["actual_Q"] + list_names_dict["forecast_Q"]
      )
  
      with pytest.raises(
          ValueError,
          match="list_names_dict\\['actual_Q'\\] has length 2, but there are 4 columns to name.",
      ):
          list_names_dict = {
              "actual_Q": [
                  f"actual_Q{q}" for q in forecast_quantiles
              ],  # reversed, wrong length
              "forecast_Q": [
                  f"forecast_Q{q}" for q in actual_quantiles
              ],
          }
          flexible_grouping_evaluation(
              df=df,
              map_func_dict=None,
              groupby_col="groups",
              agg_kwargs=agg_kwargs,
              extend_col_names=True,
              unpack_list=True,
              list_names_dict=list_names_dict,
          )
  
      with pytest.warns(
          UserWarning,
          match=r"These names from `list_names_dict` are not used, because the "
          r"column \(key\) is not found in the dataframe after aggregation:\n"
          r"\['actual', 'forecast'\].\nAvailable columns are:\n"
          r"\['actual_Q', 'forecast_Q'\].",
      ):
          list_names_dict = {
              "actual": [f"actual_Q{q}" for q in actual_quantiles],
              "forecast": [
                  f"forecast_Q{q}" for q in forecast_quantiles
              ],
          }
          flexible_grouping_evaluation(
              df=df,
              map_func_dict=None,
              groupby_col="groups",
              agg_kwargs=agg_kwargs,
              extend_col_names=True,
              unpack_list=True,
              list_names_dict=list_names_dict,
          )
  
      # `agg_kwargs` with string format
      eval_df = flexible_grouping_evaluation(
          df=df,
          map_func_dict=None,
          groupby_col="groups",
          agg_kwargs={"func": "mean"},
      )
      assert list(eval_df.columns) == ["actual", "forecast"]
      # only one level, no extension
      eval_df = flexible_grouping_evaluation(
          df=df,
          map_func_dict=None,
          groupby_col="groups",
          agg_kwargs={"func": "mean"},
          extend_col_names=True,
      )
      assert list(eval_df.columns) == ["actual", "forecast"]
  
imports: import datetime
  from datetime import datetime
  from functools import partial
  import numpy
  import pandas
  import pytest
  from pandas.testing import assert_frame_equal
  from plotly.colors import DEFAULT_PLOTLY_COLORS
  from testfixtures import LogCapture
  import greykite.common.constants
  from greykite.common.evaluation import EvaluationMetricEnum
  from greykite.common.viz.timeseries_plotting import add_groupby_column
  from greykite.common.viz.timeseries_plotting import flexible_grouping_evaluation
  from greykite.common.viz.timeseries_plotting import grouping_evaluation
  from greykite.common.viz.timeseries_plotting import plot_dual_axis_figure
  from greykite.common.viz.timeseries_plotting import plot_forecast_vs_actual
  from greykite.common.viz.timeseries_plotting import plot_multivariate
  from greykite.common.viz.timeseries_plotting import plot_multivariate_grouped
  from greykite.common.viz.timeseries_plotting import plot_univariate
  from greykite.common.viz.timeseries_plotting import split_range_into_groups
label_desc: Use image node_id 8 for calling a global function with example usage: test_flexible_grouping_evaluation() without return types