code: def _evaluate_grid_hyperparameters_sarimax(
      forecaster,
      y: pd.Series,
      param_grid: dict,
      steps: int,
      metric: Union[str, Callable, list],
      initial_train_size: int,
      fixed_train_size: bool = True,
      gap: int = 0,
      allow_incomplete_fold: bool = True,
      exog: Optional[Union[pd.Series, pd.DataFrame]] = None,
      refit: Optional[Union[bool, int]] = False,
      return_best: bool = True,
      n_jobs: Optional[Union[int, str]] = "auto",
      verbose: bool = True,
      suppress_warnings_fit: bool = False,
      show_progress: bool = True,
  ) -> pd.DataFrame:
      """
      Evaluate parameter values for a Forecaster object using time series backtesting.
  
      Parameters
      ----------
      forecaster : ForecasterSarimax
          Forecaster model.
      y : pandas Series
          Training time series.
      param_grid : dict
          Dictionary with parameters names (`str`) as keys and lists of parameter
          settings to try as values.
      steps : int
          Number of steps to predict.
      metric : str, Callable, list
          Metric used to quantify the goodness of fit of the model.
  
          - If `string`: {'mean_squared_error', 'mean_absolute_error',
          'mean_absolute_percentage_error', 'mean_squared_log_error'}
          - If `Callable`: Function with arguments y_true, y_pred that returns
          a float.
          - If `list`: List containing multiple strings and/or Callables.
      initial_train_size : int
          Number of samples in the initial train split. The backtest forecaster is
          trained using the first `initial_train_size` observations.
      fixed_train_size : bool, default `True`
          If True, train size doesn't increase but moves by `steps` in each iteration.
      gap : int, default `0`
          Number of samples to be excluded after the end of each training set and
          before the test set.
      allow_incomplete_fold : bool, default `True`
          Last fold is allowed to have a smaller number of samples than the
          `test_size`. If `False`, the last fold is excluded.
      exog : pandas Series, pandas DataFrame, default `None`
          Exogenous variable/s included as predictor/s. Must have the same
          number of observations as `y` and should be aligned so that y[i] is
          regressed on exog[i].
      refit : bool, int, default `False`
          Whether to re-fit the forecaster in each iteration. If `refit` is an integer,
          the Forecaster will be trained every that number of iterations.
      return_best : bool, default `True`
          Refit the `forecaster` using the best found parameters on the whole data.
      n_jobs : int, 'auto', default `'auto'`
          The number of jobs to run in parallel. If `-1`, then the number of jobs is
          set to the number of cores. If 'auto', `n_jobs` is set using the function
          skforecast.utils.select_n_jobs_backtesting.
          **New in version 0.9.0**
      verbose : bool, default `True`
          Print number of folds used for cv or backtesting.
      suppress_warnings_fit : bool, default `False`
          If `True`, warnings generated during fitting will be ignored.
      show_progress: bool, default `True`
          Whether to show a progress bar.
  
      Returns
      -------
      results : pandas DataFrame
          Results for each combination of parameters.
  
          - column params: parameters configuration for each iteration.
          - column metric: metric value estimated for each iteration.
          - additional n columns with param = value.
  
      """
  
      if return_best and exog is not None and (len(exog) != len(y)):
          raise ValueError(
              (
                  f"`exog` must have same number of samples as `y`. "
                  f"length `exog`: ({len(exog)}), length `y`: ({len(y)})"
              )
          )
  
      params_list = []
      if not isinstance(metric, list):
          metric = [metric]
      metric_dict = {
          (m if isinstance(m, str) else m.__name__): [] for m in metric
      }
  
      if len(metric_dict) != len(metric):
          raise ValueError(
              "When `metric` is a `list`, each metric name must be unique."
          )
  
      print(f"Number of models compared: {len(param_grid)}.")
  
      if show_progress:
          param_grid = tqdm(param_grid, desc="params grid", position=0)
  
      for params in param_grid:
          forecaster.set_params(params)
          metrics_values = backtesting_sarimax(
              forecaster=forecaster,
              y=y,
              steps=steps,
              metric=metric,
              initial_train_size=initial_train_size,
              fixed_train_size=fixed_train_size,
              gap=gap,
              allow_incomplete_fold=allow_incomplete_fold,
              exog=exog,
              refit=refit,
              alpha=None,
              interval=None,
              n_jobs=n_jobs,
              verbose=verbose,
              suppress_warnings_fit=suppress_warnings_fit,
              show_progress=False,
          )[0]
          warnings.filterwarnings(
              "ignore",
              category=RuntimeWarning,
              message="The forecaster will be fit.*",
          )
          params_list.append(params)
          for m, m_value in zip(metric, metrics_values):
              m_name = m if isinstance(m, str) else m.__name__
              metric_dict[m_name].append(m_value)
  
      results = pd.DataFrame({"params": params_list, **metric_dict})
  
      results = results.sort_values(
          by=list(metric_dict.keys())[0], ascending=True
      )
      results = pd.concat(
          [results, results["params"].apply(pd.Series)], axis=1
      )
  
      if return_best:
          best_params = results["params"].iloc[0]
          best_metric = results[list(metric_dict.keys())[0]].iloc[0]
  
          forecaster.set_params(best_params)
          forecaster.fit(
              y=y, exog=exog, suppress_warnings=suppress_warnings_fit
          )
  
          print(
              f"`Forecaster` refitted using the best-found parameters, "
              f"and the whole data set: \n"
              f"  Parameters: {best_params}\n"
              f"  Backtesting metric: {best_metric}\n"
          )
  
      return results
  
file_path: skforecast/skforecast/model_selection_sarimax/model_selection_sarimax.py
imports: from typing import Union, Tuple, Optional, Callable
  import pandas
  import warnings
  import logging
  from copy import deepcopy
  from joblib import Parallel, delayed, cpu_count
  from tqdm.auto import tqdm
  from sklearn.model_selection import ParameterGrid
  from sklearn.model_selection import ParameterSampler
  from ..exceptions import LongTrainingWarning
  from ..model_selection.model_selection import _get_metric
  from ..model_selection.model_selection import _create_backtesting_folds
  from ..utils import check_backtesting_input
  from ..utils import select_n_jobs_backtesting
label_desc: Use image node_id 5 for calling a global function with example usage: _evaluate_grid_hyperparameters_sarimax(forecaster, y, param_grid, steps, metric, initial_train_size, fixed_train_size, gap, allow_incomplete_fold, exog, refit, return_best, n_jobs, verbose, suppress_warnings_fit, show_progress) and returns: results