%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%20Exogenous%20Features%3A%20X_actual%2C%20X_future%2C%20X_forecast%0A%0A%20%20%20%20In%20this%20notebook%2C%20we%20will%20build%20a%20forecasting%20model%20that%20uses%20all%20three%0A%20%20%20%20types%20of%20exogenous%20features%20on%20synthetic%20electricity%20price%20data.%20We%20will%0A%20%20%20%20fit%20with%20actual%20temperature%2C%20holiday%20calendars%2C%20and%20weather%20forecasts%2C%0A%20%20%20%20then%20produce%20predictions%20from%20two%20different%20forecast%20vintages%20and%20run%0A%20%20%20%20a%20walk-forward%20evaluation%20using%0A%20%20%20%20%5B%60PointReductionForecaster%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.point.reduction.PointReductionForecaster%2F).%0A%0A%20%20%20%20**Prerequisites%3A**%20Basic%20familiarity%20with%20yohou's%20fit%2Fpredict%20API%0A%20%20%20%20(%5BQuickstart%5D(%2Fexamples%2Fquickstart%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%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%20HistGradientBoostingRegressor%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%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%20%20%20%20plot_forecast%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.%20Load%20the%20Synthetic%20Data%0A%0A%20%20%20%20%60make_exogenous_regression()%60%20generates%20hourly%20electricity%20prices%0A%20%20%20%20with%20a%20known%20linear%20relationship%3A%0A%20%20%20%20%60price%20%3D%2050%20%2B%202%C2%B7temperature%20%2B%2010%C2%B7is_holiday%20%2B%20noise%60%2C%20plus%0A%20%20%20%20weather%20forecasts%20with%20a%20small%20systematic%20bias.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(make_exogenous_regression)%3A%0A%20%20%20%20data%20%3D%20make_exogenous_regression(n_samples%3D200%2C%20forecasting_horizon%3D6)%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%20%20%20%20H%20%3D%206%0A%0A%20%20%20%20print(f%22Dataset%3A%20%7Blen(y)%7D%20hourly%20observations%2C%20forecast%20horizon%20H%3D%7BH%7D%22)%0A%20%20%20%20print(f%22X_forecast%3A%20%7Blen(X_forecast)%7D%20rows%2C%20%7BX_forecast%5B'vintage_time'%5D.n_unique()%7D%20vintages%22)%0A%20%20%20%20y.head()%0A%20%20%20%20return%20H%2C%20X_actual%2C%20X_forecast%2C%20X_future%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.%20Fit%20the%20Forecaster%0A%0A%20%20%20%20We%20use%20%5B%60PointReductionForecaster%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.point.reduction.PointReductionForecaster%2F)%20with%20the%20%60%22direct%22%60%20strategy%20and%0A%20%20%20%20%60HistGradientBoostingRegressor%60%20(handles%20nulls%20from%20partial%20forecast%0A%20%20%20%20coverage).%20All%20three%20exogenous%20types%20go%20to%20%60fit()%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20H%2C%0A%20%20%20%20HistGradientBoostingRegressor%2C%0A%20%20%20%20LagTransformer%2C%0A%20%20%20%20PointReductionForecaster%2C%0A%20%20%20%20X_actual%2C%0A%20%20%20%20X_forecast%2C%0A%20%20%20%20X_future%2C%0A%20%20%20%20y%2C%0A)%3A%0A%20%20%20%20from%20yohou.model_selection%20import%20train_test_split%0A%0A%20%20%20%20y_train%2C%20y_test%2C%20X_actual_train%2C%20X_actual_test%20%3D%20train_test_split(%0A%20%20%20%20%20%20%20%20y%2C%20X_actual%2C%20test_size%3D40%0A%20%20%20%20)%0A%20%20%20%20train_size%20%3D%20len(y_train)%0A%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%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%0A%20%20%20%20print(f%22Fitted%20with%20%7Blen(forecaster._step_column_names_)%7D%20step%20columns%22)%0A%20%20%20%20print(f%22Step%20columns%3A%20%7Bsorted(forecaster._step_column_names_)%7D%22)%0A%20%20%20%20return%20X_actual_test%2C%20forecaster%2C%20train_size%2C%20y_test%2C%20y_train%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%20Notice%20that%20both%20%60is_holiday%60%20and%20%60wx_temp%60%20were%20converted%20to%0A%20%20%20%20step-indexed%20columns%20(%60is_holiday_step_1..6%60%2C%20%60wx_temp_step_1..6%60).%0A%20%20%20%20These%20sit%20alongside%20the%20lag%20features%20from%20%5B%60LagTransformer%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.preprocessing.window.LagTransformer%2F)%20in%20the%0A%20%20%20%20internal%20feature%20matrix.%0A%0A%20%20%20%20%23%23%203.%20Predict%20with%20Multiple%20Vintages%0A%0A%20%20%20%20We%20create%20two%20weather%20forecast%20vintages%20at%20the%20test%20boundary%3A%20one%0A%20%20%20%20accurate%20(small%20bias)%20and%20one%20deliberately%20wrong%20(large%20bias).%0A%20%20%20%20Each%20%60predict()%60%20call%20swaps%20step%20columns%20temporarily%20without%0A%20%20%20%20mutating%20the%20forecaster's%20state.%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%20X_forecast_accurate%20%3D%20pl.DataFrame(%7B%0A%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%22time%22%3A%20test_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(_actual_temp%5Btrain_size%20%2B%20i%5D%20%2B%200.1%20%2B%20np.random.default_rng(100).normal(0%2C%200.1))%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%20X_forecast_biased%20%3D%20pl.DataFrame(%7B%0A%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%22time%22%3A%20test_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(_actual_temp%5Btrain_size%20%2B%20i%5D%20%2B%205.0%20%2B%20np.random.default_rng(200).normal(0%2C%200.1))%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%20print(%22Created%20two%20test%20vintages%20(accurate%20%2B%20biased)%22)%0A%20%20%20%20return%20X_forecast_accurate%2C%20X_forecast_biased%0A%0A%0A%40app.cell%0Adef%20_(X_forecast_accurate%2C%20X_forecast_biased%2C%20forecaster%2C%20np)%3A%0A%20%20%20%20pred_accurate%20%3D%20forecaster.predict(X_forecast%3DX_forecast_accurate)%0A%20%20%20%20pred_biased%20%3D%20forecaster.predict(X_forecast%3DX_forecast_biased)%0A%0A%20%20%20%20print(%22Accurate%20vintage%20prices%3A%22%2C%20%5Bf%22%7Bv%3A.1f%7D%22%20for%20v%20in%20pred_accurate%5B%22price%22%5D.to_list()%5D)%0A%20%20%20%20print(%22Biased%20vintage%20prices%3A%20%20%22%2C%20%5Bf%22%7Bv%3A.1f%7D%22%20for%20v%20in%20pred_biased%5B%22price%22%5D.to_list()%5D)%0A%20%20%20%20print(%0A%20%20%20%20%20%20%20%20f%22%5CnMax%20difference%3A%20%7Bnp.max(np.abs(pred_accurate%5B'price'%5D.to_numpy()%20-%20pred_biased%5B'price'%5D.to_numpy()))%3A.2f%7D%22%0A%20%20%20%20)%0A%20%20%20%20return%20(pred_accurate%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%20The%20predictions%20differ%20because%20the%20weather%20forecasts%20differ.%20The%0A%20%20%20%20accurate%20vintage%20produces%20prices%20closer%20to%20truth.%20Importantly%2C%20calling%0A%20%20%20%20%60predict()%60%20again%20with%20the%20same%20vintage%20returns%20identical%20results%2C%0A%20%20%20%20confirming%20no%20internal%20state%20mutation%20occurred.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(X_forecast_accurate%2C%20forecaster%2C%20np%2C%20pred_accurate)%3A%0A%20%20%20%20%23%20Verify%20that%20calling%20predict%20with%20the%20same%20vintage%20again%20gives%20identical%20results%0A%20%20%20%20pred_again%20%3D%20forecaster.predict(X_forecast%3DX_forecast_accurate)%0A%0A%20%20%20%20np.testing.assert_array_almost_equal(%0A%20%20%20%20%20%20%20%20pred_again%5B%22price%22%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20pred_accurate%5B%22price%22%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20decimal%3D10%2C%0A%20%20%20%20)%0A%20%20%20%20print(%22Repeated%20predict%20with%20same%20vintage%3A%20identical%20results%20confirmed%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%204.%20Walk-Forward%20Evaluation%0A%0A%20%20%20%20The%20%60observe_predict%60%20loop%20handles%20all%20three%20parameter%20types.%0A%20%20%20%20%60X_actual%60%20is%20observed%20at%20each%20step%20(through%20the%20transformer)%2C%0A%20%20%20%20%60X_future%60%20and%20%60X_forecast%60%20provide%20step-indexed%20columns%20for%0A%20%20%20%20prediction.%20We%20build%20forecast%20vintages%20covering%20the%20test%20period.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20H%2C%0A%20%20%20%20MeanAbsoluteError%2C%0A%20%20%20%20X_actual%2C%0A%20%20%20%20X_actual_test%2C%0A%20%20%20%20X_future%2C%0A%20%20%20%20forecaster%2C%0A%20%20%20%20np%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20train_size%2C%0A%20%20%20%20y%2C%0A%20%20%20%20y_test%2C%0A%20%20%20%20y_train%2C%0A)%3A%0A%20%20%20%20import%20copy%0A%0A%20%20%20%20%23%20Build%20X_forecast%20vintages%20covering%20the%20test%20period%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%20_rng%20%3D%20np.random.default_rng(42)%0A%20%20%20%20_n%20%3D%20len(y)%0A%20%20%20%20test_forecast_rows%20%3D%20%5B%5D%0A%20%20%20%20for%20_i%20in%20range(train_size%2C%20_n)%3A%0A%20%20%20%20%20%20%20%20for%20_step%20in%20range(1%2C%20H%20%2B%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_i%20%2B%20_step%20%3C%20_n%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20test_forecast_rows.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22vintage_time%22%3A%20_times%5B_i%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22time%22%3A%20_times%5B_i%20%2B%20_step%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22wx_temp%22%3A%20float(_actual_temp%5B_i%20%2B%20_step%5D%20%2B%200.5%20%2B%20_rng.normal(0%2C%200.3))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20X_forecast_test%20%3D%20pl.DataFrame(test_forecast_rows)%0A%0A%20%20%20%20eval_forecaster%20%3D%20copy.deepcopy(forecaster)%0A%20%20%20%20preds%20%3D%20eval_forecaster.observe_predict(%0A%20%20%20%20%20%20%20%20y%3Dy_test%2C%0A%20%20%20%20%20%20%20%20X_actual%3DX_actual_test%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_test%2C%0A%20%20%20%20%20%20%20%20stride%3DH%2C%0A%20%20%20%20)%0A%0A%20%20%20%20scorer%20%3D%20MeanAbsoluteError()%0A%20%20%20%20scorer.fit(y_train)%0A%20%20%20%20n_preds%20%3D%20min(len(preds)%2C%20len(y_test))%0A%20%20%20%20score%20%3D%20scorer.score(y_test%5B%3An_preds%5D%2C%20preds%5B%3An_preds%5D)%0A%20%20%20%20print(f%22Walk-forward%20MAE%3A%20%7Bscore%3A.4f%7D%22)%0A%20%20%20%20print(f%22Predictions%20generated%3A%20%7Blen(preds)%7D%20rows%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.%20Visualize%20the%20Results%0A%0A%20%20%20%20We%20compare%20the%20accurate%20and%20biased%20vintage%20predictions%20against%0A%20%20%20%20the%20actual%20test%20prices.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(plot_forecast%2C%20pred_accurate%2C%20y_test%2C%20y_train)%3A%0A%20%20%20%20plot_forecast(%0A%20%20%20%20%20%20%20%20y_test%5B%3A6%5D%2C%0A%20%20%20%20%20%20%20%20pred_accurate%2C%0A%20%20%20%20%20%20%20%20y_train%3Dy_train%5B-48%3A%5D%2C%0A%20%20%20%20%20%20%20%20title%3D%22Electricity%20Price%20Forecast%20(Accurate%20Weather%20Vintage)%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%5BExogenous%20Features%20tutorial%5D(%2Fpages%2Ftutorials%2Fexogenous-features%2F)%20for%20the%20companion%20walkthrough%0A%20%20%20%20-%20%5BMulti-Vintage%20Forecasting%5D(%2Fexamples%2Fforecasting-models%2Fmulti_vintage_forecasting%2F)%20for%20production%20multi-vintage%20workflows%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
81c9592bd969f69207155ed64f8e73d4