%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%20Column-Wise%20Feature%20Transformation%0A%0A%20%20%20%20When%20forecasting%20with%20multivariate%20data%2C%20different%20columns%20often%20need%0A%20%20%20%20different%20preprocessing%2C%20demands%20from%20different%20regions%20should%20be%20scaled%2C%0A%20%20%20%20and%20some%20features%20may%20pass%20through%20untouched.%0A%20%20%20%20%5B%60ColumnTransformer%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.compose.column_transformer.ColumnTransformer%2F)%20applies%20**distinct%20transformers%20to%20distinct%20columns**%0A%20%20%20%20in%20a%20single%20step.%0A%0A%20%20%20%20This%20notebook%20shows%20how%20to%20route%20columns%20through%20distinct%20transformers%20with%20ColumnTransformer%2C%20including%20remainder%20handling%20and%20automatic%20panel-aware%20column%20detection.%0A%0A%20%20%20%20**Prerequisites%3A**%20Familiarity%20with%20%5B%60PointReductionForecaster%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.point.reduction.PointReductionForecaster%2F)%20and%20basic%20transformers%0A%20%20%20%20(see%20%60examples%2Fpoint%2Freduction_forecaster.py%60).%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%20polars%20as%20pl%0A%20%20%20%20from%20sklearn.linear_model%20import%20Ridge%0A%0A%20%20%20%20from%20yohou.compose%20import%20ColumnTransformer%0A%20%20%20%20from%20yohou.datasets%20import%20fetch_dominick%2C%20fetch_electricity_demand%0A%20%20%20%20from%20yohou.model_selection%20import%20train_test_split%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%2C%20StandardScaler%0A%20%20%20%20from%20yohou.utils.panel%20import%20inspect_panel%0A%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20ColumnTransformer%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%20Ridge%2C%0A%20%20%20%20%20%20%20%20StandardScaler%2C%0A%20%20%20%20%20%20%20%20fetch_dominick%2C%0A%20%20%20%20%20%20%20%20fetch_electricity_demand%2C%0A%20%20%20%20%20%20%20%20inspect_panel%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_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(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%20Multivariate%20Data%0A%0A%20%20%20%20The%20Electricity%20Demand%20dataset%20has%20five%20state-level%20demand%20columns%20with%0A%20%20%20%20very%20different%20dynamics%3A%20%60vic__demand%60%2C%20%60nsw__demand%60%2C%20%60sa__demand%60%2C%20etc.%0A%20%20%20%20We%20use%20Victoria%20as%20the%20target%20and%20other%20states%20as%20exogenous%20features.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(fetch_electricity_demand%2C%20pl%2C%20train_test_split)%3A%0A%20%20%20%20_elec%20%3D%20fetch_electricity_demand().frame%0A%20%20%20%20%23%20Downsample%20to%20daily%20for%20manageable%20size%20(drop%20trailing%20all-null%20days)%0A%20%20%20%20vic_daily%20%3D%20(%0A%20%20%20%20%20%20%20%20_elec%0A%20%20%20%20%20%20%20%20.group_by_dynamic(%22time%22%2C%20every%3D%221d%22)%0A%20%20%20%20%20%20%20%20.agg(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22vic__demand%22).mean().alias(%22Demand%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22nsw__demand%22).mean().alias(%22NSW_Demand%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22sa__demand%22).mean().alias(%22SA_Demand%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.drop_nulls()%0A%20%20%20%20)%0A%0A%20%20%20%20_train_df%2C%20_test_df%20%3D%20train_test_split(vic_daily%2C%20test_size%3D0.15)%0A%20%20%20%20y_train%20%3D%20_train_df.select(%22time%22%2C%20%22Demand%22)%0A%20%20%20%20X_actual_train%20%3D%20_train_df.select(%22time%22%2C%20%22NSW_Demand%22%2C%20%22SA_Demand%22)%0A%20%20%20%20y_test%20%3D%20_test_df.select(%22time%22%2C%20%22Demand%22)%0A%20%20%20%20X_actual_test%20%3D%20_test_df.select(%22time%22%2C%20%22NSW_Demand%22%2C%20%22SA_Demand%22)%0A%20%20%20%20return%20X_actual_train%2C%20vic_daily%2C%20y_test%2C%20y_train%0A%0A%0A%40app.cell%0Adef%20_(plot_time_series%2C%20vic_daily)%3A%0A%20%20%20%20plot_time_series(vic_daily%2C%20title%3D%22Daily%20Victorian%20Electricity%20Demand%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.%20Build%20a%20ColumnTransformer%0A%0A%20%20%20%20Each%20tuple%20is%20%60(name%2C%20transformer%2C%20columns)%60.%20We%20scale%20NSW_Demand%20with%0A%20%20%20%20%5B%60StandardScaler%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.preprocessing.sklearn_wrappers.StandardScaler%2F)%2C%20pass%20SA_Demand%20through%20unchanged%2C%20and%20drop%20any%20remaining%0A%20%20%20%20columns.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ColumnTransformer%2C%20StandardScaler)%3A%0A%20%20%20%20ct%20%3D%20ColumnTransformer(%0A%20%20%20%20%20%20%20%20transformers%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22scale_nsw%22%2C%20StandardScaler()%2C%20%5B%22NSW_Demand%22%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22sa%22%2C%20%22passthrough%22%2C%20%5B%22SA_Demand%22%5D)%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20remainder%3D%22drop%22%2C%0A%20%20%20%20)%0A%20%20%20%20ct%0A%20%20%20%20return%20(ct%2C)%0A%0A%0A%40app.cell%0Adef%20_(X_actual_train%2C%20ct%2C%20pl%2C%20plot_time_series)%3A%0A%20%20%20%20_ct_fitted%20%3D%20ct.fit(X_actual_train)%0A%20%20%20%20_X_transformed%20%3D%20_ct_fitted.transform(X_actual_train)%0A%20%20%20%20_combined%20%3D%20pl.concat(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20X_actual_train.rename(%7B%22NSW_Demand%22%3A%20%22NSW%20(raw)%22%2C%20%22SA_Demand%22%3A%20%22SA%20(raw)%22%7D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20_X_transformed.drop(%22time%22).rename(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20c%3A%20c.split(%22__%22%2C%201)%5B-1%5D%20%2B%20%22%20(scaled)%22%20for%20c%20in%20_X_transformed.columns%20if%20c%20!%3D%20%22time%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D)%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20how%3D%22horizontal%22%2C%0A%20%20%20%20)%0A%20%20%20%20plot_time_series(_combined%2C%20title%3D%22Raw%20vs%20Scaled%20Features%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.%20ColumnTransformer%20Inside%20a%20Forecaster%0A%0A%20%20%20%20Pass%20the%20%5B%60ColumnTransformer%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.compose.column_transformer.ColumnTransformer%2F)%20as%20%60feature_transformer%60%20to%0A%20%20%20%20%5B%60PointReductionForecaster%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.point.reduction.PointReductionForecaster%2F).%20The%20forecaster%20calls%20%60.fit_transform()%60%20on%20%60X_actual%60%0A%20%20%20%20at%20fit%20time%20and%20%60.transform()%60%20on%20%60X_actual%60%20at%20predict%20time.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(PointReductionForecaster%2C%20Ridge%2C%20X_actual_train%2C%20ct%2C%20y_test%2C%20y_train)%3A%0A%20%20%20%20forecaster_ct%20%3D%20PointReductionForecaster(%0A%20%20%20%20%20%20%20%20estimator%3DRidge(alpha%3D1e-3)%2C%0A%20%20%20%20%20%20%20%20feature_transformer%3Dct%2C%0A%20%20%20%20)%0A%0A%20%20%20%20forecasting_horizon%20%3D%20len(y_test)%0A%20%20%20%20forecaster_ct.fit(y_train%2C%20X_actual_train%2C%20forecasting_horizon%3Dforecasting_horizon)%0A%20%20%20%20y_pred_ct%20%3D%20forecaster_ct.predict(forecasting_horizon%3Dforecasting_horizon)%0A%20%20%20%20return%20(y_pred_ct%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_forecast%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.plotting.forecasting.plot_forecast%2F)%20visualises%20how%20the%20%5B%60ColumnTransformer%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.compose.column_transformer.ColumnTransformer%2F)-equipped%0A%20%20%20%20forecaster%20performs%20on%20the%20held-out%20test%20data.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(plot_forecast%2C%20y_pred_ct%2C%20y_test%2C%20y_train)%3A%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_ct%2C%0A%20%20%20%20%20%20%20%20y_train%3Dy_train%2C%0A%20%20%20%20%20%20%20%20n_history%3D60%2C%0A%20%20%20%20%20%20%20%20title%3D%22Forecast%20with%20ColumnTransformer%20(NSW%20scaled%2C%20SA%20passthrough)%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%204.%20Remainder%20Handling%0A%0A%20%20%20%20Setting%20%60remainder%3D%22passthrough%22%60%20keeps%20all%20columns%20not%20explicitly%20listed.%0A%20%20%20%20Compare%20with%20%60remainder%3D%22drop%22%60%20(the%20default)%20to%20see%20the%20effect%20on%0A%20%20%20%20feature%20availability.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ColumnTransformer%2C%20StandardScaler%2C%20mo)%3A%0A%20%20%20%20ct_pass%20%3D%20ColumnTransformer(%0A%20%20%20%20%20%20%20%20transformers%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22scale_nsw%22%2C%20StandardScaler()%2C%20%5B%22NSW_Demand%22%5D)%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20remainder%3D%22passthrough%22%2C%0A%20%20%20%20)%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22**With%20%60remainder%3D'passthrough'%60**%3A%20SA_Demand%20is%20kept%20automatically.%5Cn%5CnTransformer%20configuration%3A%20%7Bct_pass%7D%22%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%205.%20Inspecting%20Feature%20Names%0A%0A%20%20%20%20After%20fitting%2C%20%60get_feature_names_out()%60%20reveals%20the%20output%20column%20names%2C%0A%20%20%20%20which%20include%20the%20transformer%20name%20prefix%20when%20%60verbose_feature_names_out%3DTrue%60%0A%20%20%20%20(the%20default).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ColumnTransformer%2C%20StandardScaler%2C%20X_actual_train%2C%20mo)%3A%0A%20%20%20%20ct_verbose%20%3D%20ColumnTransformer(%0A%20%20%20%20%20%20%20%20transformers%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22scale_nsw%22%2C%20StandardScaler()%2C%20%5B%22NSW_Demand%22%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22sa%22%2C%20%22passthrough%22%2C%20%5B%22SA_Demand%22%5D)%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20verbose_feature_names_out%3DTrue%2C%0A%20%20%20%20)%0A%20%20%20%20ct_verbose.fit(X_actual_train)%0A%20%20%20%20_names%20%3D%20ct_verbose.get_feature_names_out()%0A%20%20%20%20mo.md(f%22**Feature%20names%20(verbose%3DTrue)**%3A%20%7B_names%7D%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ColumnTransformer%2C%20StandardScaler%2C%20X_actual_train%2C%20mo)%3A%0A%20%20%20%20_ct_short%20%3D%20ColumnTransformer(%0A%20%20%20%20%20%20%20%20transformers%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22scale_nsw%22%2C%20StandardScaler()%2C%20%5B%22NSW_Demand%22%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22sa%22%2C%20%22passthrough%22%2C%20%5B%22SA_Demand%22%5D)%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20verbose_feature_names_out%3DFalse%2C%0A%20%20%20%20)%0A%20%20%20%20_ct_short.fit(X_actual_train)%0A%20%20%20%20_names%20%3D%20_ct_short.get_feature_names_out()%0A%20%20%20%20mo.md(f%22**Feature%20names%20(verbose%3DFalse)**%3A%20%7B_names%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.%20With%20Panel%20Data%0A%0A%20%20%20%20When%20a%20%5B%60ColumnTransformer%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.compose.column_transformer.ColumnTransformer%2F)%20is%20used%20as%20the%20%60feature_transformer%60%20inside%20a%0A%20%20%20%20panel-aware%20forecaster%2C%20_yohou%20automatically%20applies%20it%20per%20group_.%0A%20%20%20%20The%20forecaster%20decomposes%20the%20panel%20into%20individual%20groups%2C%20applies%20the%0A%20%20%20%20transformer%20to%20each%20group's%20unprefixed%20columns%2C%20and%20reassembles%20the%20result.%0A%0A%20%20%20%20Here%20we%20use%20the%20Dominick%20dataset%20(9%20panel%20groups).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(fetch_dominick%2C%20inspect_panel%2C%20mo%2C%20train_test_split)%3A%0A%20%20%20%20_dom_full%20%3D%20fetch_dominick().frame%0A%20%20%20%20_profit_cols%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%20store%20%3D%20_dom_full.select(%22time%22%2C%20*_profit_cols)%0A%20%20%20%20_globals%2C%20_groups%20%3D%20inspect_panel(store)%0A%20%20%20%20mo.md(f%22**Panel%20groups**%3A%20%7Blen(_groups)%7D%20groups%5Cn%5Cn**First%20group%20columns**%3A%20%7Blist(_groups.values())%5B0%5D%7D%22)%0A%0A%20%20%20%20%23%20Panel%20data%3A%20y%20contains%20all%20%60__profit%60%20columns%2C%20no%20separate%20X_actual%20needed%0A%20%20%20%20y_train_panel%2C%20y_test_panel%20%3D%20train_test_split(store%2C%20test_size%3D0.1)%0A%0A%20%20%20%20y_train_panel.head()%0A%20%20%20%20return%20y_test_panel%2C%20y_train_panel%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%20plot_forecast%2C%0A%20%20%20%20y_test_panel%2C%0A%20%20%20%20y_train_panel%2C%0A)%3A%0A%20%20%20%20forecaster_panel%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%0A%20%20%20%20_horizon%20%3D%20min(len(y_test_panel)%2C%2014)%0A%20%20%20%20forecaster_panel.fit(y_train_panel%2C%20forecasting_horizon%3D_horizon)%0A%20%20%20%20_y_pred_panel%20%3D%20forecaster_panel.predict(forecasting_horizon%3D_horizon)%0A%0A%20%20%20%20plot_forecast(%0A%20%20%20%20%20%20%20%20y_test_panel%2C%0A%20%20%20%20%20%20%20%20_y_pred_panel%2C%0A%20%20%20%20%20%20%20%20y_train%3Dy_train_panel%2C%0A%20%20%20%20%20%20%20%20n_history%3D30%2C%0A%20%20%20%20%20%20%20%20groups%3D%5B%22T7%22%2C%20%22T11%22%2C%20%22T12%22%5D%2C%0A%20%20%20%20%20%20%20%20title%3D%22Panel%20Forecast%3A%20First%203%20Groups%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%5BUse%20Preprocessing%20Transformers%5D(%2Fpages%2Fhow-to%2Fuse-preprocessing-transformers%2F)%20for%20the%20full%20guide%0A%20%20%20%20-%20%5BCompose%20Feature%20Pipelines%5D(%2Fpages%2Fhow-to%2Fcompose-feature-pipelines%2F)%20for%20related%20techniques%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
7e18695de8ce938e78c709981ce8e2d4