code: def fit_ml_model_with_evaluation(
      df,
      model_formula_str=None,
      y_col=None,
      pred_cols=None,
      fit_algorithm="linear",
      fit_algorithm_params=None,
      ind_train=None,
      ind_test=None,
      training_fraction=0.9,
      randomize_training=False,
      min_admissible_value=None,
      max_admissible_value=None,
      uncertainty_dict=None,
      normalize_method="zero_to_one",
      regression_weight_col=None,
      remove_intercept=False,
  ):
      """Fits prediction models to continuous response vector (y)
      and report results.
  
      Parameters
      ----------
      df : `pandas.DataFrame`
          A data frame with the response vector (y) and the feature columns (``x_mat``)
      model_formula_str : `str`
          The prediction model formula e.g. "y~x1+x2+x3*x4".
          This is similar to R language (https://www.r-project.org/) formulas.
          See https://patsy.readthedocs.io/en/latest/formulas.html#how-formulas-work.
      y_col : `str`
          The column name which has the value of interest to be forecasted
          If the ``model_formula_str`` is not passed, ``y_col`` e.g. ["y"] is used as the response
          vector column
      pred_cols : `list` [`str`]
          The names of the feature columns
          If the ``model_formula_str`` is not passed, ``pred_cols`` e.g. ["x1", "x2", "x3"]
          is used as the design matrix columns
      fit_algorithm : `str`, optional, default "linear"
          The type of predictive model used in fitting.
  
          See `~greykite.algo.common.ml_models.fit_model_via_design_matrix`
          for available options and their parameters.
      fit_algorithm_params : `dict` or None, optional, default None
          Parameters passed to the requested fit_algorithm.
          If None, uses the defaults in `~greykite.algo.common.ml_models.fit_model_via_design_matrix`.
      ind_train : `list` [`int`]
          The index (row number) of the training set
      ind_test : `list` [`int`]
          The index (row number) of the test set
      training_fraction : `float`, between 0.0 and 1.0
          The fraction of data used for training
          This is invoked if ind_train and ind_test are not passed
          If this is also None or 1.0, then we skip testing
          and train on the entire dataset
      randomize_training : `bool`
          If True, then the training and the test sets will be randomized
          rather than in chronological order
      min_admissible_value : Optional[Union[int, float, double]]
          The minimum admissible value for the ``predict`` function to return
      max_admissible_value : Optional[Union[int, float, double]]
          The maximum admissible value for the ``predict`` function to return
      uncertainty_dict: `dict` or None
          If passed as a dictionary an uncertainty model will be fit.
          The items in the dictionary are:
  
              ``"uncertainty_method"`` : `str`
                  the title of the method
                  as of now only "simple_conditional_residuals" is implemented
                  which calculates CIs by using residuals
              ``"params"`` : `dict`
                  A dictionary of parameters needed for the ``uncertainty_method``
                  requested
  
      normalize_method : `str` or None, default "zero_to_one"
          If a string is provided, it will be used as the normalization method
          in `~greykite.common.features.normalize.normalize_df`, passed via
          the argument ``method``.
          Available options are: "zero_to_one", "statistical", "minus_half_to_half", "zero_at_origin".
          If None, no normalization will be performed.
          See that function for more details.
      regression_weight_col : `str` or None, default None
          The column name for the weights to be used in weighted regression version
          of applicable machine-learning models.
      remove_intercept : `bool`, default False
          Whether to remove explicit and implicit intercepts.
          By default, `patsy` will make the design matrix always full rank.
          It will always include an intercept term unless we specify "-1" or "+0".
          However, if there are categorical variables, even we specify "-1" or "+0",
          it will include an implicit intercept by adding all levels of a categorical
          variable into the design matrix.
          Sometimes we don't want this to happen.
          Setting this parameter to True will remove both explicit and implicit intercepts.
  
      Returns
      -------
      trained_model : `dict`
          Trained model dictionary with the following keys.
  
              "ml_model": A trained model object
              "summary": Summary of the final model trained on all data
              "x_mat": Feature vectors matrix used for training of full data (rows of ``df`` with NA are dropped)
              "y": Response vector for training and testing (rows of ``df`` with NA are dropped).
                  The index corresponds to selected rows in the input ``df``.
              "y_train": Response vector used for training
              "y_train_pred": Predicted values of ``y_train``
              "training_evaluation": score function value of ``y_train`` and ``y_train_pred``
              "y_test": Response vector used for testing
              "y_test_pred": Predicted values of ``y_test``
              "test_evaluation": score function value of ``y_test`` and ``y_test_pred``
              "uncertainty_model": `dict`
                  The returned uncertainty_model dict from
                  `~greykite.algo.uncertainty.conditional.conf_interval.conf_interval`.
              "plt_compare_test": plot function to compare ``y_test`` and ``y_test_pred``,
              "plt_pred": plot function to compare
                  ``y_train``, ``y_train_pred``, ``y_test`` and ``y_test_pred``.
  
      """
      # to avoid pandas unnecessary warnings due to chain assignment
      pd.options.mode.chained_assignment = None
  
      # dropping NAs
      if df.isnull().values.any():
          nrows_original = df.shape[0]
          df = df.dropna(subset=df.columns)  # preserves index
          nrows = df.shape[0]
          warnings.warn(
              f"The data frame included {nrows_original-nrows} row(s) with NAs which were removed for model fitting.",
              UserWarning,
          )
          if nrows <= 2:
              raise ValueError(
                  f"Model training requires at least 3 observations, but the dataframe passed "
                  f"to training has {nrows} rows after removing NAs."
                  f"Sometimes this can be caused by unnecessary columns in your training data "
                  f"which contain NAs. Make sure to remove unnecessary columns from data before "
                  f"passing it to the function."
              )
  
      # an internal function for fitting model
      # this is wrapped into a function since we can do evaluations
      trained_model = fit_ml_model(
          df=df,
          model_formula_str=model_formula_str,
          fit_algorithm=fit_algorithm,
          fit_algorithm_params=fit_algorithm_params,
          y_col=y_col,
          pred_cols=pred_cols,
          min_admissible_value=min_admissible_value,
          max_admissible_value=max_admissible_value,
          uncertainty_dict=uncertainty_dict,
          normalize_method=normalize_method,
          regression_weight_col=regression_weight_col,
          remove_intercept=remove_intercept,
      )
  
      # we store the obtained ``y_col`` from the function in a new variable (``y_col_final``)
      # this is done since the input y_col could be None
      # in which case we extract ``y_col`` from the formula (``model_formula_str``)
      y_col_final = trained_model["y_col"]
  
      # determining what should be the training and test sets
      n = len(df)
      skip_test = False
      if ind_train is not None and ind_test is not None:
          if max(ind_train) >= min(ind_test):
              raise Exception(
                  "Test set indices should start after training set indices."
              )
          elif max(ind_test) >= n:
              warnings.warn(
                  "Testing set indices exceed the size of the dataset."
                  "Setting max index of the Test set "
                  "equal to the max index of the dataset."
              )
              ind_test = [x for x in ind_test if x < n]
      elif (
          ind_train is None
          and training_fraction is not None
          and training_fraction < 1.0
      ):
          k = round(n * training_fraction)
          k = int(k)
          ind_train = range(k)
          if randomize_training:
              ind_train = random.sample(range(n), k)
              ind_train = np.sort(ind_train)
          ind_test = list(set(range(n)) - set(ind_train))
          ind_test = np.sort(ind_test)
      else:
          ind_train = range(n)
          ind_test = []
          skip_test = True
  
      df_train = df.iloc[ind_train]
      df_test = df.iloc[ind_test]
      y_train = df_train[y_col_final]
      y_test = df_test[y_col_final]
  
      if skip_test:
          y_test_pred = None
          test_evaluation = None
          plt_compare_test = None
          y_train_pred = predict_ml(
              fut_df=df,
              trained_model=trained_model,
          )["fut_df"][y_col_final].tolist()
  
          def plt_pred():
              plt.plot(ind_train, y_train, label="full data", alpha=0.4)
              plt.plot(ind_train, y_train_pred, label="fit", alpha=0.4)
              plt.xlabel("index")
              plt.ylabel("value")
              plt.title("fit on the whole dataset")
              plt.legend()
  
      else:
          # validation: fit with df_train only and predict with df_test
          # first remove responses from df_test
          df_test[y_col_final] = None
          trained_model_tr = fit_ml_model(
              df=df_train,
              model_formula_str=model_formula_str,
              fit_algorithm=fit_algorithm,
              y_col=y_col,
              pred_cols=pred_cols,
              min_admissible_value=min_admissible_value,
              max_admissible_value=max_admissible_value,
              uncertainty_dict=uncertainty_dict,
              normalize_method=normalize_method,
              regression_weight_col=regression_weight_col,
              remove_intercept=remove_intercept,
          )
  
          y_train_pred = predict_ml(
              fut_df=df_train, trained_model=trained_model_tr
          )["fut_df"][y_col_final].tolist()
  
          y_test_pred = predict_ml(
              fut_df=df_test, trained_model=trained_model_tr
          )["fut_df"][y_col_final].tolist()
  
          test_evaluation = calc_pred_err(
              y_true=y_test, y_pred=y_test_pred
          )
  
          test_evaluation[R2_null_model_score] = r2_null_model_score(
              y_true=y_test,
              y_pred=y_test_pred,
              y_pred_null=y_train.mean(),
              y_train=None,
          )
  
          def plt_compare_test():
              plt.scatter(y_test, y_test_pred, color="red", alpha=0.05)
              plt.xlabel("observed")
              plt.ylabel("predicted")
              plt.title("test set")
  
          def plt_pred():
              plt.plot(ind_train, y_train, label="train", alpha=0.4)
              plt.plot(ind_train, y_train_pred, label="fit", alpha=0.4)
              plt.plot(
                  ind_test, y_test, label="observed test set", alpha=0.4
              )
              plt.plot(
                  ind_test,
                  y_test_pred,
                  label="predicted test set",
                  alpha=0.4,
              )
              plt.xlabel("index")
              plt.ylabel("value")
              plt.title("training and test fits")
              plt.legend()
  
      training_evaluation = calc_pred_err(
          y_true=y_train, y_pred=y_train_pred
      )
      training_evaluation[R2_null_model_score] = r2_null_model_score(
          y_true=y_train,
          y_pred=y_train_pred,
          y_pred_null=y_train.mean(),
          y_train=None,
      )
  
      trained_model["summary"] = None
      trained_model["y"] = df[y_col_final]
      trained_model["y_train"] = y_train
      trained_model["y_train_pred"] = y_train_pred
      trained_model["training_evaluation"] = training_evaluation
      trained_model["y_test"] = y_test
      trained_model["y_test_pred"] = y_test_pred
      trained_model["test_evaluation"] = test_evaluation
      trained_model["plt_compare_test"] = plt_compare_test
      trained_model["plt_pred"] = plt_pred
  
      return trained_model
  
imports: import random
  import re
  import traceback
  import warnings
  from typing import Dict
  from typing import List
  from typing import Optional
  import matplotlib
  import numpy
  import pandas
  import patsy
  import scipy
  import statsmodels.api
  from pandas.plotting import register_matplotlib_converters
  from sklearn.ensemble import GradientBoostingRegressor
  from sklearn.ensemble import RandomForestRegressor
  from sklearn.linear_model import ElasticNetCV
  from sklearn.linear_model import LarsCV
  from sklearn.linear_model import LassoCV
  from sklearn.linear_model import LassoLarsCV
  from sklearn.linear_model import RidgeCV
  from sklearn.linear_model import SGDRegressor
  from greykite.algo.common.l1_quantile_regression import QuantileRegression
  from greykite.algo.uncertainty.conditional.conf_interval import conf_interval
  from greykite.algo.uncertainty.conditional.conf_interval import predict_ci
  from greykite.common.constants import RESIDUAL_COL
  from greykite.common.constants import R2_null_model_score
  from greykite.common.evaluation import calc_pred_err
  from greykite.common.evaluation import r2_null_model_score
  from greykite.common.features.normalize import normalize_df
  from greykite.common.logging import LoggingLevelEnum
  from greykite.common.logging import log_message
  from greykite.common.python_utils import group_strs_with_regex_patterns
  from greykite.common.viz.timeseries_plotting import plot_multivariate
  import matplotlib.pyplot
label_desc: Use image node_id 8 for calling a global function with example usage: fit_ml_model_with_evaluation(df, model_formula_str, y_col, pred_cols, fit_algorithm, fit_algorithm_params, ind_train, ind_test, training_fraction, randomize_training, min_admissible_value, max_admissible_value, uncertainty_dict, normalize_method, regression_weight_col, remove_intercept) and returns: trained_model