%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.11%22%0A%23%20dependencies%20%3D%20%5B%0A%23%20%20%20%20%20%22scikit-learn%22%2C%0A%23%20%20%20%20%20%22yohou%5Bplotting%5D%22%2C%0A%23%20%5D%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.23.8%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Multi-Metric%20Hyperparameter%20Search%0A%0A%20%20%20%20Evaluate%20model%20configurations%20against%20multiple%20metrics%20simultaneously.%0A%0A%20%20%20%20%23%23%201.%20Prepare%20Data%0A%0A%20%20%20%20We%20load%20the%20Tourism%20Monthly%20dataset%20and%20split%20it%20into%20training%20and%20test%0A%20%20%20%20partitions.%20The%20test%20length%20defines%20the%20forecasting%20horizon%20for%20all%0A%20%20%20%20search%20experiments%20below.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20from%20copy%20import%20deepcopy%0A%0A%20%20%20%20import%20polars%20as%20pl%0A%20%20%20%20from%20scipy.stats%20import%20loguniform%0A%20%20%20%20from%20sklearn.linear_model%20import%20ElasticNet%2C%20Ridge%0A%0A%20%20%20%20from%20yohou.datasets%20import%20fetch_tourism_monthly%0A%20%20%20%20from%20yohou.metrics%20import%20(%0A%20%20%20%20%20%20%20%20MeanAbsoluteError%2C%0A%20%20%20%20%20%20%20%20MeanAbsolutePercentageError%2C%0A%20%20%20%20%20%20%20%20RootMeanSquaredError%2C%0A%20%20%20%20)%0A%20%20%20%20from%20yohou.model_selection%20import%20(%0A%20%20%20%20%20%20%20%20ExpandingWindowSplitter%2C%0A%20%20%20%20%20%20%20%20GridSearchCV%2C%0A%20%20%20%20%20%20%20%20RandomizedSearchCV%2C%0A%20%20%20%20%20%20%20%20train_test_split%2C%0A%20%20%20%20)%0A%20%20%20%20from%20yohou.plotting%20import%20(%0A%20%20%20%20%20%20%20%20plot_cv_results_scatter%2C%0A%20%20%20%20%20%20%20%20plot_forecast%2C%0A%20%20%20%20%20%20%20%20plot_score_summary%2C%0A%20%20%20%20%20%20%20%20plot_time_series%2C%0A%20%20%20%20)%0A%20%20%20%20from%20yohou.point%20import%20PointReductionForecaster%0A%20%20%20%20from%20yohou.preprocessing%20import%20LagTransformer%0A%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20ElasticNet%2C%0A%20%20%20%20%20%20%20%20ExpandingWindowSplitter%2C%0A%20%20%20%20%20%20%20%20GridSearchCV%2C%0A%20%20%20%20%20%20%20%20LagTransformer%2C%0A%20%20%20%20%20%20%20%20MeanAbsoluteError%2C%0A%20%20%20%20%20%20%20%20MeanAbsolutePercentageError%2C%0A%20%20%20%20%20%20%20%20PointReductionForecaster%2C%0A%20%20%20%20%20%20%20%20RandomizedSearchCV%2C%0A%20%20%20%20%20%20%20%20Ridge%2C%0A%20%20%20%20%20%20%20%20RootMeanSquaredError%2C%0A%20%20%20%20%20%20%20%20deepcopy%2C%0A%20%20%20%20%20%20%20%20fetch_tourism_monthly%2C%0A%20%20%20%20%20%20%20%20loguniform%2C%0A%20%20%20%20%20%20%20%20pl%2C%0A%20%20%20%20%20%20%20%20plot_cv_results_scatter%2C%0A%20%20%20%20%20%20%20%20plot_forecast%2C%0A%20%20%20%20%20%20%20%20plot_score_summary%2C%0A%20%20%20%20%20%20%20%20plot_time_series%2C%0A%20%20%20%20%20%20%20%20train_test_split%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell%0Adef%20_(fetch_tourism_monthly%2C%20mo%2C%20train_test_split)%3A%0A%0A%20%20%20%20tourism%20%3D%20(%0A%20%20%20%20%20%20%20%20fetch_tourism_monthly().frame.select(%22time%22%2C%20%22T1__tourists%22).drop_nulls().rename(%7B%22T1__tourists%22%3A%20%22tourists%22%7D)%0A%20%20%20%20)%0A%20%20%20%20y_train%2C%20y_test%20%3D%20train_test_split(tourism%2C%20test_size%3D0.15)%0A%20%20%20%20horizon%20%3D%20len(y_test)%0A%0A%20%20%20%20mo.md(f%22**Train**%3A%20%7Blen(y_train)%7D%20months%2C%20**Test**%3A%20%7Blen(y_test)%7D%20months%22)%0A%20%20%20%20return%20horizon%2C%20tourism%2C%20y_test%2C%20y_train%0A%0A%0A%40app.cell%0Adef%20_(plot_time_series%2C%20tourism)%3A%0A%20%20%20%20plot_time_series(tourism%2C%20title%3D%22Tourism%20Monthly%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%202.%20Multi-Metric%20GridSearchCV%0A%0A%20%20%20%20Pass%20a%20dict%20of%20%5B%60BaseScorer%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.metrics.base.BaseScorer%2F)%20instances.%20When%20using%20a%20dict%2C%20%60refit%60%0A%20%20%20%20must%20be%20a%20key%20name%20(string)%20or%20%60False%60%2C%20using%20%60refit%3DTrue%60%20raises%0A%20%20%20%20an%20error.%0A%0A%20%20%20%20Scores%20for%20%22lower%20is%20better%22%20metrics%20are%20**negated**%20internally%0A%20%20%20%20(sklearn%20convention%3A%20higher%20%3D%20better).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ExpandingWindowSplitter%2C%0A%20%20%20%20GridSearchCV%2C%0A%20%20%20%20LagTransformer%2C%0A%20%20%20%20MeanAbsoluteError%2C%0A%20%20%20%20MeanAbsolutePercentageError%2C%0A%20%20%20%20PointReductionForecaster%2C%0A%20%20%20%20Ridge%2C%0A%20%20%20%20RootMeanSquaredError%2C%0A%20%20%20%20horizon%2C%0A%20%20%20%20y_train%2C%0A)%3A%0A%20%20%20%20_base%20%3D%20PointReductionForecaster(%0A%20%20%20%20%20%20%20%20estimator%3DRidge()%2C%0A%20%20%20%20%20%20%20%20feature_transformer%3DLagTransformer(lag%3D%5B1%2C%2012%5D)%2C%0A%20%20%20%20)%0A%20%20%20%20multi_scoring%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22mae%22%3A%20MeanAbsoluteError()%2C%0A%20%20%20%20%20%20%20%20%22rmse%22%3A%20RootMeanSquaredError()%2C%0A%20%20%20%20%20%20%20%20%22mape%22%3A%20MeanAbsolutePercentageError()%2C%0A%20%20%20%20%7D%0A%20%20%20%20multi_gs%20%3D%20GridSearchCV(%0A%20%20%20%20%20%20%20%20forecaster%3D_base%2C%0A%20%20%20%20%20%20%20%20param_grid%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22estimator__alpha%22%3A%20%5B0.01%2C%200.1%2C%201.0%2C%2010.0%5D%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20scoring%3Dmulti_scoring%2C%0A%20%20%20%20%20%20%20%20refit%3D%22mae%22%2C%20%20%23%20Best%20model%20selected%20by%20MAE%0A%20%20%20%20%20%20%20%20cv%3DExpandingWindowSplitter(n_splits%3D2)%2C%0A%20%20%20%20)%0A%20%20%20%20multi_gs.fit(y_train%2C%20forecasting_horizon%3Dhorizon)%0A%20%20%20%20return%20(multi_gs%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%203.%20Inspect%20cv_results_%0A%0A%20%20%20%20With%20multiple%20scorers%2C%20each%20metric%20gets%20its%20own%20%60mean_test_%7Bname%7D%60%2C%0A%20%20%20%20%60std_test_%7Bname%7D%60%2C%20and%20%60rank_test_%7Bname%7D%60%20columns.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20multi_gs%2C%20pl)%3A%0A%20%20%20%20_results%20%3D%20pl.DataFrame(multi_gs.cv_results_)%0A%20%20%20%20_cols%20%3D%20%5Bc%20for%20c%20in%20_results.columns%20if%20%22mean_test%22%20in%20c%20or%20%22rank_test%22%20in%20c%20or%20%22param_%22%20in%20c%5D%0A%20%20%20%20mo.ui.table(_results.select(_cols))%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%204.%20Refit%20Strategies%0A%0A%20%20%20%20Compare%20which%20alpha%20each%20metric%20selects%20as%20%22best%22.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ExpandingWindowSplitter%2C%0A%20%20%20%20GridSearchCV%2C%0A%20%20%20%20LagTransformer%2C%0A%20%20%20%20MeanAbsoluteError%2C%0A%20%20%20%20MeanAbsolutePercentageError%2C%0A%20%20%20%20PointReductionForecaster%2C%0A%20%20%20%20Ridge%2C%0A%20%20%20%20RootMeanSquaredError%2C%0A%20%20%20%20horizon%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20y_train%2C%0A)%3A%0A%20%20%20%20_rows%20%3D%20%5B%5D%0A%20%20%20%20for%20_metric_name%20in%20%5B%22mae%22%2C%20%22rmse%22%2C%20%22mape%22%5D%3A%0A%20%20%20%20%20%20%20%20_gs%20%3D%20GridSearchCV(%0A%20%20%20%20%20%20%20%20%20%20%20%20forecaster%3DPointReductionForecaster(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20estimator%3DRidge()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20feature_transformer%3DLagTransformer(lag%3D%5B1%2C%2012%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20param_grid%3D%7B%22estimator__alpha%22%3A%20%5B0.01%2C%200.1%2C%201.0%2C%2010.0%5D%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20scoring%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mae%22%3A%20MeanAbsoluteError()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22rmse%22%3A%20RootMeanSquaredError()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mape%22%3A%20MeanAbsolutePercentageError()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20refit%3D_metric_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20cv%3DExpandingWindowSplitter(n_splits%3D2)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_gs.fit(y_train%2C%20forecasting_horizon%3Dhorizon)%0A%20%20%20%20%20%20%20%20_rows.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Refit%20Metric%22%3A%20_metric_name.upper()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Best%20Alpha%22%3A%20_gs.best_params_%5B%22estimator__alpha%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Best%20Score%22%3A%20-round(float(_gs.best_score_)%2C%204)%2C%0A%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20mo.ui.table(pl.DataFrame(_rows))%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%205.%20RandomizedSearchCV%20with%20Distributions%0A%0A%20%20%20%20For%20larger%20parameter%20spaces%2C%20random%20sampling%20is%20more%20efficient.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ElasticNet%2C%0A%20%20%20%20ExpandingWindowSplitter%2C%0A%20%20%20%20LagTransformer%2C%0A%20%20%20%20MeanAbsoluteError%2C%0A%20%20%20%20PointReductionForecaster%2C%0A%20%20%20%20RandomizedSearchCV%2C%0A%20%20%20%20RootMeanSquaredError%2C%0A%20%20%20%20horizon%2C%0A%20%20%20%20loguniform%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20y_train%2C%0A)%3A%0A%20%20%20%20rand_search%20%3D%20RandomizedSearchCV(%0A%20%20%20%20%20%20%20%20forecaster%3DPointReductionForecaster(%0A%20%20%20%20%20%20%20%20%20%20%20%20estimator%3DElasticNet()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20feature_transformer%3DLagTransformer(lag%3D%5B1%2C%2012%5D)%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20param_distributions%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22estimator__alpha%22%3A%20loguniform(0.001%2C%2010.0)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22estimator__l1_ratio%22%3A%20%5B0.1%2C%200.3%2C%200.5%2C%200.7%2C%200.9%5D%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20scoring%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mae%22%3A%20MeanAbsoluteError()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22rmse%22%3A%20RootMeanSquaredError()%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20refit%3D%22mae%22%2C%0A%20%20%20%20%20%20%20%20n_iter%3D10%2C%0A%20%20%20%20%20%20%20%20cv%3DExpandingWindowSplitter(n_splits%3D2)%2C%0A%20%20%20%20)%0A%20%20%20%20rand_search.fit(y_train%2C%20forecasting_horizon%3Dhorizon)%0A%0A%20%20%20%20mo.md(f%22**Best%20params**%3A%20%7Brand_search.best_params_%7D%5Cn%5Cn**Best%20MAE%20(negated)**%3A%20%7Brand_search.best_score_%3A.4f%7D%22)%0A%20%20%20%20return%20(rand_search%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%5B%60plot_cv_results_scatter%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.plotting.model_selection.plot_cv_results_scatter%2F)%20shows%20how%20each%20randomly%20sampled%20alpha%20value%0A%20%20%20%20performed%20according%20to%20the%20refit%20metric.%20This%20helps%20identify%20whether%0A%20%20%20%20the%20search%20has%20explored%20enough%20of%20the%20parameter%20space.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(plot_cv_results_scatter%2C%20rand_search)%3A%0A%20%20%20%20plot_cv_results_scatter(rand_search.cv_results_%2C%20%22estimator__alpha%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%5B%60plot_forecast%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.plotting.forecasting.plot_forecast%2F)%20displays%20the%20best%20model's%20predictions%20against%20the%20actual%0A%20%20%20%20test%20values%2C%20along%20with%20a%20trailing%20window%20of%20training%20history%20for%20context.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(horizon%2C%20plot_forecast%2C%20rand_search%2C%20y_test%2C%20y_train)%3A%0A%20%20%20%20y_pred_best%20%3D%20rand_search.predict(forecasting_horizon%3Dhorizon)%0A%20%20%20%20plot_forecast(%0A%20%20%20%20%20%20%20%20y_test%2C%0A%20%20%20%20%20%20%20%20y_pred_best%2C%0A%20%20%20%20%20%20%20%20y_train%3Dy_train%2C%0A%20%20%20%20%20%20%20%20n_history%3D36%2C%0A%20%20%20%20%20%20%20%20title%3D%22Best%20Forecaster%20(RandomizedSearchCV%2C%20multi-metric)%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Multi-vintage%20Scoring%0A%0A%20%20%20%20The%20%60observe_predict%60%20method%20with%20%60stride%3D1%60%20produces%20one%20forecast%20per%0A%20%20%20%20observation%20point%2C%20creating%20multiple%20*vintages*.%20Each%20vintage%20represents%0A%20%20%20%20a%20different%20forecast%20origin%2C%20so%20you%20can%20analyse%20how%20accuracy%20evolves%20as%0A%20%20%20%20the%20model%20absorbs%20more%20data.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(deepcopy%2C%20horizon%2C%20multi_gs%2C%20y_test)%3A%0A%20%20%20%20_vintage_model%20%3D%20deepcopy(multi_gs.best_forecaster_)%0A%20%20%20%20y_pred_vintages%20%3D%20_vintage_model.observe_predict(%0A%20%20%20%20%20%20%20%20y%3Dy_test%2C%0A%20%20%20%20%20%20%20%20stride%3D1%2C%0A%20%20%20%20%20%20%20%20forecasting_horizon%3Dhorizon%2C%0A%20%20%20%20)%0A%20%20%20%20print(f%22Vintages%3A%20%7By_pred_vintages%5B'vintage_time'%5D.n_unique()%7D%22)%0A%20%20%20%20y_pred_vintages.head(10)%0A%20%20%20%20return%20(y_pred_vintages%2C)%0A%0A%0A%40app.cell%0Adef%20_(MeanAbsoluteError%2C%20y_train)%3A%0A%20%20%20%20vintage_scorer%20%3D%20MeanAbsoluteError()%0A%20%20%20%20vintage_scorer.fit(y_train)%0A%20%20%20%20return%20(vintage_scorer%2C)%0A%0A%0A%40app.cell%0Adef%20_(plot_score_summary%2C%20vintage_scorer%2C%20y_pred_vintages%2C%20y_test)%3A%0A%20%20%20%20plot_score_summary(%0A%20%20%20%20%20%20%20%20vintage_scorer%2C%0A%20%20%20%20%20%20%20%20y_test%2C%0A%20%20%20%20%20%20%20%20y_pred_vintages%2C%0A%20%20%20%20%20%20%20%20title%3D%22Model%20Score%20Summary%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Next%20Steps%0A%0A%20%20%20%20-%20**Interval%20search**%3A%20See%20%5B%60examples%2Fmodel_selection%2Finterval_search.py%60%5D(%2Fexamples%2Fevaluation-search%2Finterval_search%2F)%0A%20%20%20%20-%20**Optuna%20search**%3A%20See%20%5B%60examples%2Fmodel_selection%2Foptuna_search.py%60%5D(%2Fexamples%2Fmodel_selection%2Foptuna_search%2F)%0A%20%20%20%20-%20**Panel%20CV**%3A%20See%20%5B%60examples%2Fmodel_selection%2Fpanel_cross_validation.py%60%5D(%2Fexamples%2Fpanel-data%2Fpanel_cross_validation%2F)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
3fc3dd2b4a85b84d476545f1205e8afa