%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%20Time-Weighted%20Scoring%0A%0A%20%20%20%20Yohou%20supports%20weighting%20forecast%20errors%20by%20time%2C%20allowing%20recent%0A%20%20%20%20errors%20to%20count%20more%2C%20or%20emphasising%20specific%20seasonal%20periods.%0A%0A%20%20%20%20%23%23%201.%20Prepare%20Data%20and%20Predictions%0A%0A%20%20%20%20We%20load%20the%20Tourism%20Monthly%20dataset%2C%20fit%20a%20%5B%60PointReductionForecaster%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.point.reduction.PointReductionForecaster%2F)%20with%0A%20%20%20%20a%20%5B%60Ridge%60%5D(https%3A%2F%2Fscikit-learn.org%2Fstable%2Fmodules%2Fgenerated%2Fsklearn.linear_model.Ridge.html)%20regressor%2C%20and%20generate%20predictions.%20The%20scorer%20will%20then%20evaluate%0A%20%20%20%20these%20predictions%20with%20different%20time-weighting%20strategies.%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%20from%20sklearn.linear_model%20import%20Ridge%0A%0A%20%20%20%20from%20yohou.datasets%20import%20fetch_dominick%2C%20fetch_tourism_monthly%0A%20%20%20%20from%20yohou.metrics%20import%20MeanAbsoluteError%0A%20%20%20%20from%20yohou.model_selection%20import%20train_test_split%0A%20%20%20%20from%20yohou.plotting%20import%20(%0A%20%20%20%20%20%20%20%20plot_score_per_vintage%2C%0A%20%20%20%20%20%20%20%20plot_score_time_series%2C%0A%20%20%20%20%20%20%20%20plot_time_series%2C%0A%20%20%20%20%20%20%20%20plot_time_weight%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%20%20%20%20from%20yohou.utils.weighting%20import%20(%0A%20%20%20%20%20%20%20%20compose_weights%2C%0A%20%20%20%20%20%20%20%20exponential_decay_weight%2C%0A%20%20%20%20%20%20%20%20linear_decay_weight%2C%0A%20%20%20%20%20%20%20%20seasonal_emphasis_weight%2C%0A%20%20%20%20)%0A%0A%20%20%20%20return%20(%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%20compose_weights%2C%0A%20%20%20%20%20%20%20%20deepcopy%2C%0A%20%20%20%20%20%20%20%20exponential_decay_weight%2C%0A%20%20%20%20%20%20%20%20fetch_dominick%2C%0A%20%20%20%20%20%20%20%20fetch_tourism_monthly%2C%0A%20%20%20%20%20%20%20%20linear_decay_weight%2C%0A%20%20%20%20%20%20%20%20plot_score_per_vintage%2C%0A%20%20%20%20%20%20%20%20plot_score_time_series%2C%0A%20%20%20%20%20%20%20%20plot_time_series%2C%0A%20%20%20%20%20%20%20%20plot_time_weight%2C%0A%20%20%20%20%20%20%20%20seasonal_emphasis_weight%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_(%0A%20%20%20%20LagTransformer%2C%0A%20%20%20%20PointReductionForecaster%2C%0A%20%20%20%20Ridge%2C%0A%20%20%20%20fetch_tourism_monthly%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20train_test_split%2C%0A)%3A%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%20fc%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%2012%5D)%2C%0A%20%20%20%20)%0A%20%20%20%20fc.fit(y_train%2C%20forecasting_horizon%3Dhorizon)%0A%20%20%20%20y_pred%20%3D%20fc.predict(forecasting_horizon%3Dhorizon)%0A%0A%20%20%20%20mo.md(f%22**Tourism%20Monthly**%3A%20Train%3D%7Blen(y_train)%7D%2C%20Test%3D%7Blen(y_test)%7D%22)%0A%20%20%20%20return%20fc%2C%20horizon%2C%20tourism%2C%20y_pred%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.%20Exponential%20Decay%20Weight%0A%0A%20%20%20%20Recent%20observations%20receive%20the%20highest%20weight%2C%20decaying%0A%20%20%20%20exponentially%20with%20a%20configurable%20%60half_life%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(exponential_decay_weight%2C%20plot_time_weight%2C%20y_test)%3A%0A%20%20%20%20w_exp%20%3D%20exponential_decay_weight(half_life%3D6)%0A%20%20%20%20_weights_df%20%3D%20y_test.select(%22time%22).with_columns(w_exp(y_test%5B%22time%22%5D).alias(%22time_weight%22))%0A%20%20%20%20plot_time_weight(_weights_df%2C%20title%3D%22Exponential%20Decay%20(half_life%3D6)%22)%0A%20%20%20%20return%20(w_exp%2C)%0A%0A%0A%40app.cell%0Adef%20_(MeanAbsoluteError%2C%20mo%2C%20w_exp%2C%20y_pred%2C%20y_test%2C%20y_train)%3A%0A%20%20%20%20_scorer%20%3D%20MeanAbsoluteError()%0A%20%20%20%20_scorer.fit(y_train)%0A%20%20%20%20_unweighted%20%3D%20float(_scorer.score(y_test%2C%20y_pred))%0A%20%20%20%20_weighted%20%3D%20float(_scorer.score(y_test%2C%20y_pred%2C%20time_weight%3Dw_exp))%0A%20%20%20%20mo.md(f%22**Unweighted%20MAE**%3A%20%7B_unweighted%3A.2f%7D%5Cn%5Cn**Exponential-weighted%20MAE**%3A%20%7B_weighted%3A.2f%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%203.%20Linear%20Decay%20Weight%0A%0A%20%20%20%20%5B%60linear_decay_weight%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.utils.weighting.linear_decay_weight%2F)%20assigns%20weights%20that%20decrease%20linearly%20from%20the%20most%0A%20%20%20%20recent%20observation%20to%20the%20oldest.%20The%20optional%20%60max_steps%60%20parameter%20limits%0A%20%20%20%20how%20far%20back%20the%20decay%20extends%3B%20when%20set%20to%20%60None%60%2C%20the%20full%20test%20range%0A%20%20%20%20is%20used.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(linear_decay_weight%2C%20plot_time_weight%2C%20y_test)%3A%0A%20%20%20%20w_lin%20%3D%20linear_decay_weight(max_steps%3DNone)%0A%20%20%20%20_weights_df%20%3D%20y_test.select(%22time%22).with_columns(w_lin(y_test%5B%22time%22%5D).alias(%22time_weight%22))%0A%20%20%20%20plot_time_weight(_weights_df%2C%20title%3D%22Linear%20Decay%20(full%20range)%22)%0A%20%20%20%20return%20(w_lin%2C)%0A%0A%0A%40app.cell%0Adef%20_(MeanAbsoluteError%2C%20mo%2C%20w_lin%2C%20y_pred%2C%20y_test%2C%20y_train)%3A%0A%20%20%20%20_scorer%20%3D%20MeanAbsoluteError()%0A%20%20%20%20_scorer.fit(y_train)%0A%20%20%20%20_weighted_lin%20%3D%20float(_scorer.score(y_test%2C%20y_pred%2C%20time_weight%3Dw_lin))%0A%20%20%20%20mo.md(f%22**Linear-weighted%20MAE**%3A%20%7B_weighted_lin%3A.2f%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%204.%20Seasonal%20Emphasis%20Weight%0A%0A%20%20%20%20Boost%20the%20weight%20at%20specific%20seasonal%20positions%20(e.g.%2C%20every%2012%0A%20%20%20%20months%20for%20annual%20cycles).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(plot_time_weight%2C%20seasonal_emphasis_weight%2C%20y_test)%3A%0A%20%20%20%20w_season%20%3D%20seasonal_emphasis_weight(seasonality%3D12%2C%20emphasis%3D3.0)%0A%20%20%20%20_weights_df%20%3D%20y_test.select(%22time%22).with_columns(w_season(y_test%5B%22time%22%5D).alias(%22time_weight%22))%0A%20%20%20%20plot_time_weight(_weights_df%2C%20title%3D%22Seasonal%20Emphasis%20(period%3D12%2C%20emphasis%3D3x)%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.%20Compose%20Weights%0A%0A%20%20%20%20Multiply%20multiple%20weight%20functions%20together%3A%20e.g.%2C%20exponential%0A%20%20%20%20decay%20AND%20seasonal%20emphasis.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20compose_weights%2C%0A%20%20%20%20exponential_decay_weight%2C%0A%20%20%20%20plot_time_weight%2C%0A%20%20%20%20seasonal_emphasis_weight%2C%0A%20%20%20%20y_test%2C%0A)%3A%0A%20%20%20%20w_composed%20%3D%20compose_weights(%0A%20%20%20%20%20%20%20%20exponential_decay_weight(half_life%3D6)%2C%0A%20%20%20%20%20%20%20%20seasonal_emphasis_weight(seasonality%3D12%2C%20emphasis%3D2.0)%2C%0A%20%20%20%20)%0A%20%20%20%20_weights_df%20%3D%20y_test.select(%22time%22).with_columns(w_composed(y_test%5B%22time%22%5D).alias(%22time_weight%22))%0A%20%20%20%20plot_time_weight(_weights_df%2C%20title%3D%22Composed%3A%20Exponential%20%2B%20Seasonal%22)%0A%20%20%20%20return%20(w_composed%2C)%0A%0A%0A%40app.cell%0Adef%20_(MeanAbsoluteError%2C%20mo%2C%20w_composed%2C%20y_pred%2C%20y_test%2C%20y_train)%3A%0A%20%20%20%20_scorer%20%3D%20MeanAbsoluteError()%0A%20%20%20%20_scorer.fit(y_train)%0A%20%20%20%20_weighted_comp%20%3D%20float(_scorer.score(y_test%2C%20y_pred%2C%20time_weight%3Dw_composed))%0A%20%20%20%20mo.md(f%22**Composed-weighted%20MAE**%3A%20%7B_weighted_comp%3A.2f%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%206.%20Panel%20Data%20with%20Time%20Weights%0A%0A%20%20%20%20Time%20weights%20work%20with%20panel%20data%3A%20applied%20per-timestep%20across%0A%20%20%20%20all%20groups.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%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%20compose_weights%2C%0A%20%20%20%20exponential_decay_weight%2C%0A%20%20%20%20fetch_dominick%2C%0A%20%20%20%20linear_decay_weight%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20plot_score_time_series%2C%0A%20%20%20%20seasonal_emphasis_weight%2C%0A%20%20%20%20train_test_split%2C%0A)%3A%0A%20%20%20%20_full%20%3D%20fetch_dominick().frame%0A%20%20%20%20_selected%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%22T7__profit%22%2C%0A%20%20%20%20%20%20%20%20%22T11__profit%22%2C%0A%20%20%20%20%20%20%20%20%22T12__profit%22%2C%0A%20%20%20%20%20%20%20%20%22T13__profit%22%2C%0A%20%20%20%20%20%20%20%20%22T15__profit%22%2C%0A%20%20%20%20%20%20%20%20%22T19__profit%22%2C%0A%20%20%20%20%20%20%20%20%22T22__profit%22%2C%0A%20%20%20%20%20%20%20%20%22T23__profit%22%2C%0A%20%20%20%20%20%20%20%20%22T24__profit%22%2C%0A%20%20%20%20%5D%0A%20%20%20%20_store%20%3D%20_full.select(%22time%22%2C%20*_selected)%0A%20%20%20%20_target_cols%20%3D%20%5Bc%20for%20c%20in%20_store.columns%20if%20c.endswith(%22__profit%22)%5D%0A%20%20%20%20_y%20%3D%20_store.select(%22time%22%2C%20*_target_cols)%0A%20%20%20%20_y_train_p%2C%20_y_test_p%20%3D%20train_test_split(_y%2C%20test_size%3D0.15)%0A%20%20%20%20_horizon_p%20%3D%20len(_y_test_p)%0A%0A%20%20%20%20_fc_p%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%207%5D)%2C%0A%20%20%20%20)%0A%20%20%20%20_fc_p.fit(_y_train_p%2C%20forecasting_horizon%3D_horizon_p)%0A%20%20%20%20_y_pred_p%20%3D%20_fc_p.predict(forecasting_horizon%3D_horizon_p)%0A%0A%20%20%20%20_weights%20%3D%20%5B%0A%20%20%20%20%20%20%20%20(%22Exponential%20Decay%20(half_life%3D10)%22%2C%20exponential_decay_weight(half_life%3D10))%2C%0A%20%20%20%20%20%20%20%20(%22Linear%20Decay%22%2C%20linear_decay_weight(max_steps%3DNone))%2C%0A%20%20%20%20%20%20%20%20(%22Seasonal%20Emphasis%20(period%3D7%2C%203%C3%97)%22%2C%20seasonal_emphasis_weight(seasonality%3D7%2C%20emphasis%3D3.0))%2C%0A%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Composed%3A%20Exponential%20%2B%20Seasonal%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20compose_weights(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20exponential_decay_weight(half_life%3D10)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seasonal_emphasis_weight(seasonality%3D7%2C%20emphasis%3D2.0)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20_plots%20%3D%20%5B%5D%0A%20%20%20%20for%20_label%2C%20_w%20in%20_weights%3A%0A%20%20%20%20%20%20%20%20_fig%20%3D%20plot_score_time_series(%0A%20%20%20%20%20%20%20%20%20%20%20%20MeanAbsoluteError()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_test_p%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_pred_p%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20time_weight%3D_w%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22Panel%20MAE%20-%20%7B_label%7D%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_plots.append(_fig)%0A%0A%20%20%20%20mo.vstack(_plots)%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%20fc%2C%20horizon%2C%20y_test)%3A%0A%20%20%20%20_vintage_model%20%3D%20deepcopy(fc)%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_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**Time-weighted%20forecasting**%3A%20See%20%60examples%2Ftime_weighted_forecasting.py%60%0A%20%20%20%20-%20**Aggregation%20modes**%3A%20See%20%5B%60examples%2Fmetrics%2Faggregation_modes.py%60%5D(%2Fexamples%2Fevaluation-search%2Faggregation_modes%2F)%0A%20%20%20%20-%20**Scoring**%3A%20See%20%60examples%2Fscoring.py%60%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
2164a1257ac626afcc298f854056067e