%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%22numpy%22%2C%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%20How%20to%20Produce%20Multi-Vintage%20Predictions%0A%0A%20%20%20%20This%20notebook%20shows%20how%20to%20generate%20multiple%20predictions%20from%0A%20%20%20%20different%20external%20forecast%20vintages%20at%20the%20same%20observation%20point%20using%0A%20%20%20%20%5B%60PointReductionForecaster%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.point.reduction.PointReductionForecaster%2F).%0A%20%20%20%20Each%20%60predict()%60%20call%20swaps%20step%20columns%20temporarily%20without%0A%20%20%20%20changing%20the%20forecaster's%20internal%20state.%0A%0A%20%20%20%20**Prerequisites%3A**%20Familiarity%20with%20%60X_actual%60%2C%20%60X_future%60%2C%0A%20%20%20%20%60X_forecast%60%20parameters%0A%20%20%20%20(%5BTutorial%5D(%2Fexamples%2Fgetting-started%2Fexogenous_features%2F)).%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%20import%20numpy%20as%20np%0A%20%20%20%20import%20polars%20as%20pl%0A%20%20%20%20from%20sklearn.ensemble%20import%20HistGradientBoostingRegressor%0A%0A%20%20%20%20from%20yohou.datasets%20import%20make_exogenous_regression%0A%20%20%20%20from%20yohou.metrics%20import%20MeanAbsoluteError%0A%20%20%20%20from%20yohou.plotting%20import%20plot_forecast%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%20HistGradientBoostingRegressor%2C%0A%20%20%20%20%20%20%20%20LagTransformer%2C%0A%20%20%20%20%20%20%20%20PointReductionForecaster%2C%0A%20%20%20%20%20%20%20%20make_exogenous_regression%2C%0A%20%20%20%20%20%20%20%20np%2C%0A%20%20%20%20%20%20%20%20pl%2C%0A%20%20%20%20)%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%201.%20Prepare%20Data%20and%20Fit%0A%0A%20%20%20%20Set%20up%20synthetic%20electricity%20prices%20with%20temperature%20dependence%0A%20%20%20%20and%20fit%20a%20forecaster%20with%20all%20three%20exogenous%20types.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20HistGradientBoostingRegressor%2C%0A%20%20%20%20LagTransformer%2C%0A%20%20%20%20PointReductionForecaster%2C%0A%20%20%20%20make_exogenous_regression%2C%0A)%3A%0A%20%20%20%20H%20%3D%2012%0A%20%20%20%20data%20%3D%20make_exogenous_regression(n_samples%3D300%2C%20forecasting_horizon%3DH)%0A%20%20%20%20y%20%3D%20data.y%0A%20%20%20%20X_actual%20%3D%20data.X_actual%0A%20%20%20%20X_future%20%3D%20data.X_future%0A%20%20%20%20X_forecast%20%3D%20data.X_forecast%0A%0A%20%20%20%20from%20yohou.model_selection%20import%20train_test_split%0A%0A%20%20%20%20y_train%2C%20_%2C%20X_actual_train%2C%20_%20%3D%20train_test_split(%0A%20%20%20%20%20%20%20%20y%2C%20X_actual%2C%20test_size%3Dlen(y)%20-%20250%0A%20%20%20%20)%0A%20%20%20%20train_size%20%3D%20len(y_train)%0A%20%20%20%20forecaster%20%3D%20PointReductionForecaster(%0A%20%20%20%20%20%20%20%20estimator%3DHistGradientBoostingRegressor(max_iter%3D50%2C%20max_depth%3D3%2C%20random_state%3D42)%2C%0A%20%20%20%20%20%20%20%20feature_transformer%3DLagTransformer(%5B1%2C%202%2C%203%5D)%2C%0A%20%20%20%20%20%20%20%20reduction_strategy%3D%22direct%22%2C%0A%20%20%20%20)%0A%20%20%20%20forecaster.fit(%0A%20%20%20%20%20%20%20%20y%3Dy_train%2C%0A%20%20%20%20%20%20%20%20X_actual%3DX_actual_train%2C%0A%20%20%20%20%20%20%20%20forecasting_horizon%3DH%2C%0A%20%20%20%20%20%20%20%20X_future%3DX_future%2C%0A%20%20%20%20%20%20%20%20X_forecast%3DX_forecast%2C%0A%20%20%20%20)%0A%20%20%20%20print(f%22Fitted%3A%20observed_time_%20%3D%20%7Bforecaster.observed_time_%7D%22)%0A%20%20%20%20return%20H%2C%20X_actual%2C%20forecaster%2C%20train_size%2C%20y%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.%20Create%20Multiple%20Weather%20Vintages%0A%0A%20%20%20%20Simulate%20five%20forecast%20vintages%20with%20decreasing%20bias%20(later%0A%20%20%20%20vintages%20are%20more%20accurate).%20All%20vintages%20share%20the%20same%0A%20%20%20%20observation%20point.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(H%2C%20X_actual%2C%20np%2C%20pl%2C%20train_size%2C%20y)%3A%0A%20%20%20%20_times%20%3D%20y%5B%22time%22%5D%0A%20%20%20%20_actual_temp%20%3D%20X_actual%5B%22temperature%22%5D.to_numpy()%0A%20%20%20%20last_obs%20%3D%20_times%5Btrain_size%20-%201%5D%0A%20%20%20%20test_times%20%3D%20%5B_times%5Btrain_size%20%2B%20i%5D%20for%20i%20in%20range(H)%5D%0A%0A%20%20%20%20vintage_configs%20%3D%20%5B%0A%20%20%20%20%20%20%20%20(%2206%3A00%22%2C%203.0)%2C%0A%20%20%20%20%20%20%20%20(%2207%3A00%22%2C%202.0)%2C%0A%20%20%20%20%20%20%20%20(%2208%3A00%22%2C%201.0)%2C%0A%20%20%20%20%20%20%20%20(%2209%3A00%22%2C%200.5)%2C%0A%20%20%20%20%20%20%20%20(%2209%3A30%22%2C%200.1)%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20vintages%20%3D%20%7B%7D%0A%20%20%20%20for%20_name%2C%20_bias%20in%20vintage_configs%3A%0A%20%20%20%20%20%20%20%20_seed%20%3D%20hash(_name)%20%25%20(2**31)%0A%20%20%20%20%20%20%20%20_local_rng%20%3D%20np.random.default_rng(_seed)%0A%20%20%20%20%20%20%20%20vintages%5B_name%5D%20%3D%20pl.DataFrame(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22vintage_time%22%3A%20%5Blast_obs%5D%20*%20H%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22time%22%3A%20test_times%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22wx_temp%22%3A%20%5Bfloat(_actual_temp%5Btrain_size%20%2B%20i%5D%20%2B%20_bias%20%2B%20_local_rng.normal(0%2C%200.2))%20for%20i%20in%20range(H)%5D%2C%0A%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20print(f%22Created%20%7Blen(vintages)%7D%20vintages%20at%20observation%20point%20%7Blast_obs%7D%22)%0A%20%20%20%20return%20last_obs%2C%20vintages%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.%20Predict%20with%20Each%20Vintage%0A%0A%20%20%20%20Call%20%60predict()%60%20once%20per%20vintage.%20The%20forecaster%20swaps%20step%0A%20%20%20%20columns%20and%20restores%20them%20after%20each%20call.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(H%2C%20forecaster%2C%20np%2C%20pl%2C%20train_size%2C%20vintages%2C%20y)%3A%0A%20%20%20%20predictions%20%3D%20%7B%7D%0A%20%20%20%20for%20_name%2C%20_X_fc%20in%20vintages.items()%3A%0A%20%20%20%20%20%20%20%20_pred%20%3D%20forecaster.predict(X_forecast%3D_X_fc)%0A%20%20%20%20%20%20%20%20predictions%5B_name%5D%20%3D%20_pred%0A%0A%20%20%20%20%23%20Compare%20MAE%20against%20actual%20test%20prices%0A%20%20%20%20y_test_h%20%3D%20y%5Btrain_size%20%3A%20train_size%20%2B%20H%5D%0A%20%20%20%20actual_prices%20%3D%20y_test_h%5B%22price%22%5D.to_numpy()%0A%0A%20%20%20%20results%20%3D%20%5B%5D%0A%20%20%20%20for%20_name%2C%20_pred%20in%20predictions.items()%3A%0A%20%20%20%20%20%20%20%20mae%20%3D%20float(np.mean(np.abs(_pred%5B%22price%22%5D.to_numpy()%20-%20actual_prices)))%0A%20%20%20%20%20%20%20%20results.append(%7B%22vintage%22%3A%20_name%2C%20%22mae%22%3A%20f%22%7Bmae%3A.3f%7D%22%7D)%0A%0A%20%20%20%20comparison%20%3D%20pl.DataFrame(results)%0A%20%20%20%20print(%22MAE%20by%20vintage%20(lower%20is%20better)%3A%22)%0A%20%20%20%20comparison%0A%20%20%20%20return%20(predictions%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%20Later%20vintages%20(smaller%20bias)%20produce%20lower%20error.%20The%2009%3A30%0A%20%20%20%20vintage%20is%20closest%20to%20reality.%0A%0A%20%20%20%20%23%23%204.%20Verify%20State%20Preservation%0A%0A%20%20%20%20Confirm%20that%20the%20five%20%60predict()%60%20calls%20did%20not%20change%20the%0A%20%20%20%20forecaster's%20internal%20state.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(forecaster%2C%20last_obs%2C%20np%2C%20predictions%2C%20vintages)%3A%0A%20%20%20%20%23%20Bare%20predict%20should%20return%20the%20stored%20baseline%0A%20%20%20%20pred_baseline%20%3D%20forecaster.predict()%0A%0A%20%20%20%20%23%20Re-run%2006%3A00%20vintage%3A%20same%20result%20as%20before%0A%20%20%20%20pred_rerun%20%3D%20forecaster.predict(X_forecast%3Dvintages%5B%2206%3A00%22%5D)%0A%20%20%20%20np.testing.assert_array_almost_equal(%0A%20%20%20%20%20%20%20%20pred_rerun%5B%22price%22%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20predictions%5B%2206%3A00%22%5D%5B%22price%22%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20decimal%3D10%2C%0A%20%20%20%20)%0A%0A%20%20%20%20assert%20forecaster.observed_time_%20%3D%3D%20last_obs%0A%20%20%20%20print(%22State%20preserved%3A%20observed_time_%20unchanged%2C%20predictions%20reproducible%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%205.%20Observe%20and%20Predict%20Again%0A%0A%20%20%20%20In%20a%20production%20loop%2C%20observe%20new%20data%2C%20then%20predict%20with%20the%0A%20%20%20%20latest%20vintage.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(H%2C%20X_actual%2C%20forecaster%2C%20np%2C%20pl%2C%20train_size%2C%20y)%3A%0A%20%20%20%20%23%20Observe%2012%20new%20hours%0A%20%20%20%20n_new%20%3D%2012%0A%20%20%20%20forecaster.observe(%0A%20%20%20%20%20%20%20%20y%3Dy%5Btrain_size%20%3A%20train_size%20%2B%20n_new%5D%2C%0A%20%20%20%20%20%20%20%20X_actual%3DX_actual%5Btrain_size%20%3A%20train_size%20%2B%20n_new%5D%2C%0A%20%20%20%20)%0A%20%20%20%20print(f%22After%20observe%3A%20observed_time_%20%3D%20%7Bforecaster.observed_time_%7D%22)%0A%0A%20%20%20%20%23%20Create%20fresh%20vintage%20at%20the%20new%20observation%20point%0A%20%20%20%20_times%20%3D%20y%5B%22time%22%5D%0A%20%20%20%20new_obs%20%3D%20forecaster.observed_time_%0A%20%20%20%20new_test_times%20%3D%20%5B_times%5Btrain_size%20%2B%20n_new%20%2B%20i%5D%20for%20i%20in%20range(H)%5D%0A%20%20%20%20rng_new%20%3D%20np.random.default_rng(999)%0A%0A%20%20%20%20new_vintage%20%3D%20pl.DataFrame(%7B%0A%20%20%20%20%20%20%20%20%22vintage_time%22%3A%20%5Bnew_obs%5D%20*%20H%2C%0A%20%20%20%20%20%20%20%20%22time%22%3A%20new_test_times%2C%0A%20%20%20%20%20%20%20%20%22wx_temp%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20float(15.0%20%2B%205.0%20*%20np.sin(2%20*%20np.pi%20*%20float(train_size%20%2B%20n_new%20%2B%20i)%20%2F%2024)%20%2B%200.1%20%2B%20rng_new.normal(0%2C%200.2))%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20i%20in%20range(H)%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%7D)%0A%0A%20%20%20%20pred_new%20%3D%20forecaster.predict(X_forecast%3Dnew_vintage)%0A%20%20%20%20print(f%22New%20prediction%3A%20%7Blen(pred_new)%7D%20steps%20from%20%7Bpred_new%5B'time'%5D.min()%7D%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%20Next%20Steps%0A%0A%20%20%20%20-%20%5BHow%20to%20Use%20Exogenous%20Features%5D(%2Fpages%2Fhow-to%2Fexogenous-features%2F)%20for%20the%20full%20guide%0A%20%20%20%20-%20%5BExogenous%20Features%20tutorial%5D(%2Fpages%2Ftutorials%2Fexogenous-features%2F)%20for%20a%20step-by-step%20walkthrough%0A%20%20%20%20-%20%5BAbout%20Exogenous%20Features%5D(%2Fpages%2Fexplanation%2Fexogenous-features%2F)%20for%20conceptual%20background%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
4fd5b5cdf9cf204e3a5f86a3c4231f3b