def models_validation(X, y, model_paramGrid_list, scale_list=None, test_size=0.2, time_series=False, random_state=123, n_folds=5, regr=True, plot=False, plot_train=False, xvalues=None, xlabel='Models', title='Models validation', figsize=(6, 6)):
    "\n    Select the best model on the given dataset.\n\n    The parameter `model_paramGrid_list` is the list of the models to test. It also contains, for each model, the grid of\n    hyperparameters that have to be tested on that model (i.e. the grid which contains the values to test for each\n    specified hyperparameter of the model).\n    (That grid has the same structure as the `param_grid` parameter of the function `hyperparameters_validation`. See\n    `hyperparameters_validation`).\n\n    For each specified model, the best combination of hyperparameters values is selected in an exhaustive  way (i.e. grid\n    search).\n    Actually, the function `hyperparameters_validation` is used.\n    (See `hyperparameters_validation`).\n\n    The selection of the best model is made using the validation score (i.e. the best model is the one with the best\n    validation score).\n    The validation score is computed by splitting the dataset into the training-test sets and then by applying the cross\n    validation on the training set.\n    Additionally, the training and test scores are also computed.\n\n    Optionally, the validation scores of the different models can be plotted, making a graphical visualization of the\n    selection.\n\n    Parameters\n    ----------\n    X: np.array\n        Two-dimensional np.array, containing the explanatory features of the dataset.\n    y: np.array\n        Mono dimensional np.array, containing the response feature of the dataset.\n    model_paramGrid_list: list\n        List that specifies the models and the relative grids of hyperparameters to be tested.\n        It's a list of triples (i.e. tuples), where each triple represents a model:\n            - the first element is a string, which is a mnemonic name of that model;\n            - the second element is the sklearn model;\n            - the third element is the grid of hyperparameters to test for that model. It's a dictionary, with the same\n              structure of the parameter `param_grid` of the function `hyperparameters_validation`.\n    scale_list: list or bool\n        List of booleans, which has as many elements as the models to test (i.e. as the elements of the\n        `model_paramGrid_list` list).\n        This list indicates, for each different model, if the features in `X` have to be scaled or not.\n        `scale_list` can be None or False: in this case the `X` features aren't scaled for any model. `scale_list` can be\n        True: in this case the `X` features are scaled for all the models.\n    test_size: float\n        Decimal number between 0 and 1, which indicates the proportion of the test set.\n    time_series: bool\n        Indicates if the given dataset is a time series dataset (i.e. dataset indexed by days).\n        (This affects the computing of the validation score).\n    random_state: int\n        Used in the training-test splitting of the dataset.\n    n_folds: int\n        Indicates how many folds are made in order to compute the k-fold cross validation.\n        (It's used only if `time_series` is False).\n    regr: bool\n        Indicates if it's either a regression or a classification problem.\n    plot: bool\n        Indicates whether to plot or not the validation score values.\n    plot_train: bool\n        Indicates whether to plot also the training scores.\n        (It's considered only if `plot` is True).\n    xvalues: list (in general, iterable)\n        Values that have to be put in the x axis of the plot.\n    xlabel: str\n        Label of the x axis of the plot.\n    title: str\n        Title of the plot.\n    figsize: tuple\n        Two dimensions of the plot.\n\n    Returns\n    ----------\n    models_train_val_score: np.array\n        Two dimensional np.array, containing two columns: the first contains the training scores, the second the validation\n        scores.\n        It has as many rows as the number of models to test (i.e. number of elements in the `model_paramGrid_list` list).\n    models_best_params: list\n        List which indicates, for each model, the best combination of the hyperparameters values for that model.\n        It has as many elements as the models to test (i.e. as the elements of the `model_paramGrid_list` list), and it\n        contains dictionaries: each dictionary represents the best combination of the hyperparameters values for the\n        associated model.\n    best_index: int\n        Index of `model_paramGrid_list` that indicates which is the best model.\n    test_score: float\n        Test score associated with the best model.\n    ax: matplotlib.axes.Axes\n        The matplotlib Axes where the plot has been made.\n        If `plot` is False, then it is None.\n\n    See also\n    ----------\n    hyperparameters_validation:\n        select the best combination of values for the specified hyperparameters of the specified model on the given dataset.\n\n    Notes\n    ----------\n    - If `regr` is True, the validation scores are errors (MSE, i.e. Mean Squared Errors): this means that the best\n      model is the one associated with the minimum validation score.\n      Otherwise, the validation scores are accuracies: this means that the best model is the one associated with the\n      maximum validation score.\n    - If `time_series` is False, the training-test splitting of the dataset is made randomly. In addition, the cross\n      validation strategy performed is the classic k-fold cross validation: the number of folds is specified by `n_folds`.\n      Otherwise, if `time_series` is True, the training-test sets are simply obtained by splitting the dataset into two\n      contiguous parts. In addition, the cross validation strategy performed is the sklearn TimeSeriesSplit.\n    "
    if (not scale_list):
        scale_list = ([False] * len(model_paramGrid_list))
    elif (scale_list is True):
        scale_list = ([True] * len(model_paramGrid_list))
    models_train_val_score = []
    models_best_params = []
    models_test_score = []
    for (i, triple) in enumerate(model_paramGrid_list):
        (model, param_grid) = triple[1:]
        (params, train_val_scores, best_index, test_score) = hyperparameters_validation(X, y, model, param_grid, scale=scale_list[i], test_size=test_size, time_series=time_series, random_state=random_state, n_folds=n_folds, regr=regr)
        models_train_val_score.append(tuple(train_val_scores[best_index]))
        models_best_params.append(params[best_index])
        models_test_score.append(test_score)
    models_train_val_score = np.array(models_train_val_score)
    if regr:
        best_index = np.argmin(models_train_val_score, axis=0)[1]
    else:
        best_index = np.argmax(models_train_val_score, axis=0)[1]
    test_score = models_test_score[best_index]
    ax = None
    if plot:
        if (not xvalues):
            xvalues = [model_paramGrid_list[i][0] for i in range(len(model_paramGrid_list))]
        ax = _plot_TrainVal_values(xvalues, models_train_val_score, plot_train, xlabel, title, figsize, bar=True)
    return (models_train_val_score, models_best_params, best_index, test_score, ax)
Select the best model on the given dataset.

The parameter `model_paramGrid_list` is the list of the models to test. It also contains, for each model, the grid of
hyperparameters that have to be tested on that model (i.e. the grid which contains the values to test for each
specified hyperparameter of the model).
(That grid has the same structure as the `param_grid` parameter of the function `hyperparameters_validation`. See
`hyperparameters_validation`).

For each specified model, the best combination of hyperparameters values is selected in an exhaustive  way (i.e. grid
search).
Actually, the function `hyperparameters_validation` is used.
(See `hyperparameters_validation`).

The selection of the best model is made using the validation score (i.e. the best model is the one with the best
validation score).
The validation score is computed by splitting the dataset into the training-test sets and then by applying the cross
validation on the training set.
Additionally, the training and test scores are also computed.

Optionally, the validation scores of the different models can be plotted, making a graphical visualization of the
selection.

Parameters
----------
X: np.array
    Two-dimensional np.array, containing the explanatory features of the dataset.
y: np.array
    Mono dimensional np.array, containing the response feature of the dataset.
model_paramGrid_list: list
    List that specifies the models and the relative grids of hyperparameters to be tested.
    It's a list of triples (i.e. tuples), where each triple represents a model:
        - the first element is a string, which is a mnemonic name of that model;
        - the second element is the sklearn model;
        - the third element is the grid of hyperparameters to test for that model. It's a dictionary, with the same
          structure of the parameter `param_grid` of the function `hyperparameters_validation`.
scale_list: list or bool
    List of booleans, which has as many elements as the models to test (i.e. as the elements of the
    `model_paramGrid_list` list).
    This list indicates, for each different model, if the features in `X` have to be scaled or not.
    `scale_list` can be None or False: in this case the `X` features aren't scaled for any model. `scale_list` can be
    True: in this case the `X` features are scaled for all the models.
test_size: float
    Decimal number between 0 and 1, which indicates the proportion of the test set.
time_series: bool
    Indicates if the given dataset is a time series dataset (i.e. dataset indexed by days).
    (This affects the computing of the validation score).
random_state: int
    Used in the training-test splitting of the dataset.
n_folds: int
    Indicates how many folds are made in order to compute the k-fold cross validation.
    (It's used only if `time_series` is False).
regr: bool
    Indicates if it's either a regression or a classification problem.
plot: bool
    Indicates whether to plot or not the validation score values.
plot_train: bool
    Indicates whether to plot also the training scores.
    (It's considered only if `plot` is True).
xvalues: list (in general, iterable)
    Values that have to be put in the x axis of the plot.
xlabel: str
    Label of the x axis of the plot.
title: str
    Title of the plot.
figsize: tuple
    Two dimensions of the plot.

Returns
----------
models_train_val_score: np.array
    Two dimensional np.array, containing two columns: the first contains the training scores, the second the validation
    scores.
    It has as many rows as the number of models to test (i.e. number of elements in the `model_paramGrid_list` list).
models_best_params: list
    List which indicates, for each model, the best combination of the hyperparameters values for that model.
    It has as many elements as the models to test (i.e. as the elements of the `model_paramGrid_list` list), and it
    contains dictionaries: each dictionary represents the best combination of the hyperparameters values for the
    associated model.
best_index: int
    Index of `model_paramGrid_list` that indicates which is the best model.
test_score: float
    Test score associated with the best model.
ax: matplotlib.axes.Axes
    The matplotlib Axes where the plot has been made.
    If `plot` is False, then it is None.

See also
----------
hyperparameters_validation:
    select the best combination of values for the specified hyperparameters of the specified model on the given dataset.

Notes
----------
- If `regr` is True, the validation scores are errors (MSE, i.e. Mean Squared Errors): this means that the best
  model is the one associated with the minimum validation score.
  Otherwise, the validation scores are accuracies: this means that the best model is the one associated with the
  maximum validation score.
- If `time_series` is False, the training-test splitting of the dataset is made randomly. In addition, the cross
  validation strategy performed is the classic k-fold cross validation: the number of folds is specified by `n_folds`.
  Otherwise, if `time_series` is True, the training-test sets are simply obtained by splitting the dataset into two
  contiguous parts. In addition, the cross validation strategy performed is the sklearn TimeSeriesSplit.
def models_validation(X, y, model_paramGrid_list, scale_list=None, test_size=0.2, time_series=False, random_state=123, n_folds=5, regr=True, plot=False, plot_train=False, xvalues=None, xlabel='Models', title='Models validation', figsize=(6, 6)):
    "\n    Select the best model on the given dataset.\n\n    The parameter `model_paramGrid_list` is the list of the models to test. It also contains, for each model, the grid of\n    hyperparameters that have to be tested on that model (i.e. the grid which contains the values to test for each\n    specified hyperparameter of the model).\n    (That grid has the same structure as the `param_grid` parameter of the function `hyperparameters_validation`. See\n    `hyperparameters_validation`).\n\n    For each specified model, the best combination of hyperparameters values is selected in an exhaustive  way (i.e. grid\n    search).\n    Actually, the function `hyperparameters_validation` is used.\n    (See `hyperparameters_validation`).\n\n    The selection of the best model is made using the validation score (i.e. the best model is the one with the best\n    validation score).\n    The validation score is computed by splitting the dataset into the training-test sets and then by applying the cross\n    validation on the training set.\n    Additionally, the training and test scores are also computed.\n\n    Optionally, the validation scores of the different models can be plotted, making a graphical visualization of the\n    selection.\n\n    Parameters\n    ----------\n    X: np.array\n        Two-dimensional np.array, containing the explanatory features of the dataset.\n    y: np.array\n        Mono dimensional np.array, containing the response feature of the dataset.\n    model_paramGrid_list: list\n        List that specifies the models and the relative grids of hyperparameters to be tested.\n        It's a list of triples (i.e. tuples), where each triple represents a model:\n            - the first element is a string, which is a mnemonic name of that model;\n            - the second element is the sklearn model;\n            - the third element is the grid of hyperparameters to test for that model. It's a dictionary, with the same\n              structure of the parameter `param_grid` of the function `hyperparameters_validation`.\n    scale_list: list or bool\n        List of booleans, which has as many elements as the models to test (i.e. as the elements of the\n        `model_paramGrid_list` list).\n        This list indicates, for each different model, if the features in `X` have to be scaled or not.\n        `scale_list` can be None or False: in this case the `X` features aren't scaled for any model. `scale_list` can be\n        True: in this case the `X` features are scaled for all the models.\n    test_size: float\n        Decimal number between 0 and 1, which indicates the proportion of the test set.\n    time_series: bool\n        Indicates if the given dataset is a time series dataset (i.e. dataset indexed by days).\n        (This affects the computing of the validation score).\n    random_state: int\n        Used in the training-test splitting of the dataset.\n    n_folds: int\n        Indicates how many folds are made in order to compute the k-fold cross validation.\n        (It's used only if `time_series` is False).\n    regr: bool\n        Indicates if it's either a regression or a classification problem.\n    plot: bool\n        Indicates whether to plot or not the validation score values.\n    plot_train: bool\n        Indicates whether to plot also the training scores.\n        (It's considered only if `plot` is True).\n    xvalues: list (in general, iterable)\n        Values that have to be put in the x axis of the plot.\n    xlabel: str\n        Label of the x axis of the plot.\n    title: str\n        Title of the plot.\n    figsize: tuple\n        Two dimensions of the plot.\n\n    Returns\n    ----------\n    models_train_val_score: np.array\n        Two dimensional np.array, containing two columns: the first contains the training scores, the second the validation\n        scores.\n        It has as many rows as the number of models to test (i.e. number of elements in the `model_paramGrid_list` list).\n    models_best_params: list\n        List which indicates, for each model, the best combination of the hyperparameters values for that model.\n        It has as many elements as the models to test (i.e. as the elements of the `model_paramGrid_list` list), and it\n        contains dictionaries: each dictionary represents the best combination of the hyperparameters values for the\n        associated model.\n    best_index: int\n        Index of `model_paramGrid_list` that indicates which is the best model.\n    test_score: float\n        Test score associated with the best model.\n    ax: matplotlib.axes.Axes\n        The matplotlib Axes where the plot has been made.\n        If `plot` is False, then it is None.\n\n    See also\n    ----------\n    hyperparameters_validation:\n        select the best combination of values for the specified hyperparameters of the specified model on the given dataset.\n\n    Notes\n    ----------\n    - If `regr` is True, the validation scores are errors (MSE, i.e. Mean Squared Errors): this means that the best\n      model is the one associated with the minimum validation score.\n      Otherwise, the validation scores are accuracies: this means that the best model is the one associated with the\n      maximum validation score.\n    - If `time_series` is False, the training-test splitting of the dataset is made randomly. In addition, the cross\n      validation strategy performed is the classic k-fold cross validation: the number of folds is specified by `n_folds`.\n      Otherwise, if `time_series` is True, the training-test sets are simply obtained by splitting the dataset into two\n      contiguous parts. In addition, the cross validation strategy performed is the sklearn TimeSeriesSplit.\n    "
    if (not scale_list):
        scale_list = ([False] * len(model_paramGrid_list))
    elif (scale_list is True):
        scale_list = ([True] * len(model_paramGrid_list))
    models_train_val_score = []
    models_best_params = []
    models_test_score = []
    for (i, triple) in enumerate(model_paramGrid_list):
        (model, param_grid) = triple[1:]
        (params, train_val_scores, best_index, test_score) = hyperparameters_validation(X, y, model, param_grid, scale=scale_list[i], test_size=test_size, time_series=time_series, random_state=random_state, n_folds=n_folds, regr=regr)
        models_train_val_score.append(tuple(train_val_scores[best_index]))
        models_best_params.append(params[best_index])
        models_test_score.append(test_score)
    models_train_val_score = np.array(models_train_val_score)
    if regr:
        best_index = np.argmin(models_train_val_score, axis=0)[1]
    else:
        best_index = np.argmax(models_train_val_score, axis=0)[1]
    test_score = models_test_score[best_index]
    ax = None
    if plot:
        if (not xvalues):
            xvalues = [model_paramGrid_list[i][0] for i in range(len(model_paramGrid_list))]
        ax = _plot_TrainVal_values(xvalues, models_train_val_score, plot_train, xlabel, title, figsize, bar=True)
    return (models_train_val_score, models_best_params, best_index, test_score, ax)