%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%20Panel%20Cross-Validation%0A%0A%20%20%20%20Time%20series%20CV%20on%20panel%20data%20splits%20by%20time%20while%20preserving%20all%0A%20%20%20%20groups.%20Each%20split%20trains%20and%20evaluates%20on%20all%20groups%20simultaneously.%0A%0A%20%20%20%20%23%23%201.%20Prepare%20Panel%20Data%0A%0A%20%20%20%20We%20load%20the%20Tourism%20Quarterly%20dataset%20and%20select%20eight%20panel%20groups.%0A%20%20%20%20Each%20column%20follows%20the%20%60GROUP__MEMBER%60%20naming%20convention%2C%20which%20yohou%0A%20%20%20%20uses%20to%20identify%20panel%20structure%20automatically.%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%20sklearn.linear_model%20import%20Ridge%0A%0A%20%20%20%20from%20yohou.datasets%20import%20fetch_tourism_quarterly%0A%20%20%20%20from%20yohou.metrics%20import%20MeanAbsoluteError%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%20train_test_split%2C%0A%20%20%20%20)%0A%20%20%20%20from%20yohou.plotting%20import%20plot_forecast%2C%20plot_score_per_vintage%2C%20plot_time_series%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%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%20PointReductionForecaster%2C%0A%20%20%20%20%20%20%20%20Ridge%2C%0A%20%20%20%20%20%20%20%20deepcopy%2C%0A%20%20%20%20%20%20%20%20fetch_tourism_quarterly%2C%0A%20%20%20%20%20%20%20%20pl%2C%0A%20%20%20%20%20%20%20%20plot_forecast%2C%0A%20%20%20%20%20%20%20%20plot_score_per_vintage%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_quarterly%2C%20mo%2C%20train_test_split)%3A%0A%0A%20%20%20%20tourism%20%3D%20fetch_tourism_quarterly().frame.select(%22time%22%2C%20*%5Bf%22T%7Bi%7D__tourists%22%20for%20i%20in%20range(3%2C%2011)%5D).drop_nulls()%0A%20%20%20%20fh%20%3D%208%0A%20%20%20%20y_train%2C%20y_test%20%3D%20train_test_split(tourism%2C%20test_size%3Dfh)%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22**Shape**%3A%20%7Btourism.shape%7D%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20f%22**Columns**%3A%20%7Btourism.columns%7D%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20f%22**Quarterly**%3A%20%7Blen(tourism)%7D%20observations%2C%20last%20%7Bfh%7D%20held%20out%20for%20testing%22%0A%20%20%20%20)%0A%20%20%20%20return%20fh%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%22Australian%20Tourism%20Quarterly%20(Panel)%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.%20GridSearchCV%20on%20Panel%20Data%0A%0A%20%20%20%20Search%20over%20forecaster%20hyperparameters%20using%20time%20series%20CV.%20The%0A%20%20%20%20scorer%20evaluates%20all%20panel%20groups%20together.%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%20PointReductionForecaster%2C%0A%20%20%20%20Ridge%2C%0A%20%20%20%20fh%2C%0A%20%20%20%20y_train%2C%0A)%3A%0A%20%20%20%20_forecaster%20%3D%20PointReductionForecaster(%0A%20%20%20%20%20%20%20%20estimator%3DRidge(alpha%3D1.0)%2C%0A%20%20%20%20%20%20%20%20feature_transformer%3DLagTransformer(lag%3D%5B1%2C%204%5D)%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_param_grid%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22estimator__alpha%22%3A%20%5B0.1%2C%201.0%2C%2010.0%5D%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20_cv%20%3D%20ExpandingWindowSplitter(n_splits%3D3%2C%20test_size%3D8)%0A%0A%20%20%20%20search%20%3D%20GridSearchCV(%0A%20%20%20%20%20%20%20%20forecaster%3D_forecaster%2C%0A%20%20%20%20%20%20%20%20param_grid%3D_param_grid%2C%0A%20%20%20%20%20%20%20%20scoring%3DMeanAbsoluteError()%2C%0A%20%20%20%20%20%20%20%20cv%3D_cv%2C%0A%20%20%20%20%20%20%20%20refit%3DTrue%2C%0A%20%20%20%20)%0A%20%20%20%20search.fit(y_train%2C%20forecasting_horizon%3Dfh)%0A%20%20%20%20return%20(search%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20pl%2C%20search)%3A%0A%20%20%20%20_results%20%3D%20search.cv_results_%0A%20%20%20%20_table%20%3D%20pl.DataFrame(%7B%0A%20%20%20%20%20%20%20%20%22alpha%22%3A%20%5Bp%5B%22estimator__alpha%22%5D%20for%20p%20in%20_results%5B%22params%22%5D%5D%2C%0A%20%20%20%20%20%20%20%20%22mean_score%22%3A%20_results%5B%22mean_test_score%22%5D%2C%0A%20%20%20%20%20%20%20%20%22std_score%22%3A%20_results%5B%22std_test_score%22%5D%2C%0A%20%20%20%20%20%20%20%20%22rank%22%3A%20_results%5B%22rank_test_score%22%5D%2C%0A%20%20%20%20%7D).sort(%22rank%22)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(f%22**Best%20parameters**%3A%20%60%7Bsearch.best_params_%7D%60%5Cn%5Cn**Best%20score**%3A%20%7Bsearch.best_score_%3A.4f%7D%22)%2C%0A%20%20%20%20%20%20%20%20mo.ui.table(_table)%2C%0A%20%20%20%20%5D)%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%203.%20Observe%2C%20Predict%2C%20and%20Rewind%20the%20Best%20Model%0A%0A%20%20%20%20After%20%60refit%3DTrue%60%2C%20the%20search%20object%20delegates%20%60predict%60%2C%20%60observe%60%2C%0A%20%20%20%20and%20%60rewind%60%20to%20its%20%60best_forecaster_%60.%20All%20three%20accept%0A%20%20%20%20%60groups%60%20for%20selective%20group%20operations.%0A%0A%20%20%20%20We%20demonstrate%20three%20steps%20on%20groups%20**T5**%20and%20**T8**%3A%0A%0A%20%20%20%201.%20**Predict%20from%20training%20window**%3A%20baseline%20forecast%0A%20%20%20%202.%20**Observe**%20the%20first%20half%20of%20test%20data%20for%20those%20groups%2C%20then%0A%20%20%20%20%20%20%20predict%20again%3A%20the%20forecast%20origin%20moves%20forward%0A%20%20%20%203.%20**Rewind**%20those%20groups%20back%20to%20the%20training%20window%20and%20predict%0A%20%20%20%20%20%20%20once%20more%3A%20the%20origin%20returns%20to%20its%20original%20position%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(deepcopy%2C%20fh%2C%20mo%2C%20plot_forecast%2C%20search%2C%20y_test%2C%20y_train)%3A%0A%20%20%20%20_groups%20%3D%20%5B%22T5%22%2C%20%22T8%22%5D%0A%20%20%20%20_search%20%3D%20deepcopy(search)%0A%0A%20%20%20%20%23%201)%20Predict%20from%20training%20window%0A%20%20%20%20_y_pred_baseline%20%3D%20_search.predict(forecasting_horizon%3Dfh%2C%20groups%3D_groups)%0A%20%20%20%20_fig_baseline%20%3D%20plot_forecast(%0A%20%20%20%20%20%20%20%20y_test%2C%0A%20%20%20%20%20%20%20%20_y_pred_baseline%2C%0A%20%20%20%20%20%20%20%20y_train%3Dy_train%2C%0A%20%20%20%20%20%20%20%20groups%3D_groups%2C%0A%20%20%20%20%20%20%20%20n_history%3D16%2C%0A%20%20%20%20%20%20%20%20title%3D%22Step%201%20-%20Predict%20from%20training%20window%22%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%202)%20Observe%20first%20half%20of%20test%20data%20for%20those%20groups%2C%20then%20predict%0A%20%20%20%20_y_obs%20%3D%20y_test.head(fh%20%2F%2F%202)%0A%20%20%20%20_search.observe(_y_obs%2C%20groups%3D_groups)%0A%20%20%20%20_y_pred_observed%20%3D%20_search.predict(forecasting_horizon%3Dfh%2C%20groups%3D_groups)%0A%20%20%20%20_fig_observed%20%3D%20plot_forecast(%0A%20%20%20%20%20%20%20%20y_test%2C%0A%20%20%20%20%20%20%20%20_y_pred_observed%2C%0A%20%20%20%20%20%20%20%20y_train%3Dy_train%2C%0A%20%20%20%20%20%20%20%20groups%3D_groups%2C%0A%20%20%20%20%20%20%20%20n_history%3D16%2C%0A%20%20%20%20%20%20%20%20title%3D%22Step%202%20-%20After%20observing%20first%20half%20of%20test%20(origin%20moves%20forward)%22%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%203)%20Rewind%20back%20and%20predict%20again%0A%20%20%20%20_search.rewind(y_train%2C%20groups%3D_groups)%0A%20%20%20%20_y_pred_rewound%20%3D%20_search.predict(forecasting_horizon%3Dfh%2C%20groups%3D_groups)%0A%20%20%20%20_fig_rewound%20%3D%20plot_forecast(%0A%20%20%20%20%20%20%20%20y_test%2C%0A%20%20%20%20%20%20%20%20_y_pred_rewound%2C%0A%20%20%20%20%20%20%20%20y_train%3Dy_train%2C%0A%20%20%20%20%20%20%20%20groups%3D_groups%2C%0A%20%20%20%20%20%20%20%20n_history%3D16%2C%0A%20%20%20%20%20%20%20%20title%3D%22Step%203%20-%20After%20rewind%20(origin%20returns)%22%2C%0A%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20_fig_baseline%2C%0A%20%20%20%20%20%20%20%20mo.md(%22**After%20%60observe%60**%3A%20the%20forecast%20origin%20shifts%20forward%20by%204%20quarters%20for%20T5%20and%20T8%3A%22)%2C%0A%20%20%20%20%20%20%20%20_fig_observed%2C%0A%20%20%20%20%20%20%20%20mo.md(%22**After%20%60rewind%60**%3A%20the%20forecast%20origin%20resets%20to%20the%20end%20of%20the%20training%20window%3A%22)%2C%0A%20%20%20%20%20%20%20%20_fig_rewound%2C%0A%20%20%20%20%5D)%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%20fh%2C%20search%2C%20y_test)%3A%0A%20%20%20%20_vintage_model%20%3D%20deepcopy(search.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%3Dfh%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_per_vintage%2C%20vintage_scorer%2C%20y_pred_vintages%2C%20y_test)%3A%0A%20%20%20%20plot_score_per_vintage(%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%22MAE%20per%20Forecast%20Vintage%22%2C%0A%20%20%20%20%20%20%20%20y_label%3D%22MAE%22%2C%0A%20%20%20%20%20%20%20%20height%3D380%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**Multi-metric%20search**%3A%20See%20%5B%60examples%2Fmodel_selection%2Fmulti_metric_search.py%60%5D(%2Fexamples%2Fevaluation-search%2Fmulti_metric_search%2F)%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**CV%20splitters**%3A%20See%20%5B%60examples%2Fmodel_selection%2Fcv_splitters.py%60%5D(%2Fexamples%2Fgetting-started%2Fcv_splitters%2F)%0A%20%20%20%20-%20**Panel%20forecasting**%3A%20See%20%5B%60examples%2Fpoint%2Fpanel_forecasting.py%60%5D(%2Fexamples%2Fpanel-data%2Fpanel_forecasting%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
f2f68ead5f2a8a9632a0ddd84f1abb15