%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%20Parallel%20Feature%20Engineering%20with%20FeatureUnion%0A%0A%20%20%20%20%5B%60FeatureUnion%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.compose.feature_union.FeatureUnion%2F)%20applies%20multiple%20transformers%20in%20parallel%20to%20the%0A%20%20%20%20same%20input%20and%20concatenates%20the%20results%20column-wise.%20The%20resulting%0A%20%20%20%20%60observation_horizon%60%20is%20the%20**maximum**%20across%20all%20transformers.%0A%0A%20%20%20%20This%20notebook%20shows%20how%20to%20combine%20lag%20features%2C%20rolling%20statistics%2C%20EMA%2C%20and%20scaling%20in%20parallel%20with%20FeatureUnion%20and%20automatic%20observation%20horizon%20resolution.%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%20FeatureUnion%0A%20%20%20%20from%20yohou.datasets%20import%20fetch_sunspot%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%20plot_forecast%2C%20plot_time_series%0A%20%20%20%20from%20yohou.point%20import%20PointReductionForecaster%2C%20SeasonalNaive%0A%20%20%20%20from%20yohou.preprocessing%20import%20(%0A%20%20%20%20%20%20%20%20ExponentialMovingAverage%2C%0A%20%20%20%20%20%20%20%20LagTransformer%2C%0A%20%20%20%20%20%20%20%20RollingStatisticsTransformer%2C%0A%20%20%20%20%20%20%20%20StandardScaler%2C%0A%20%20%20%20)%0A%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20ExponentialMovingAverage%2C%0A%20%20%20%20%20%20%20%20FeatureUnion%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%20RollingStatisticsTransformer%2C%0A%20%20%20%20%20%20%20%20SeasonalNaive%2C%0A%20%20%20%20%20%20%20%20fetch_sunspot%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.%20Load%20Data%0A%0A%20%20%20%20We%20load%20the%20Sunspot%20Numbers%20dataset%20and%20resample%20to%20monthly%20frequency.%0A%20%20%20%20The%20data%20is%20split%2085%2F15%20into%20training%20and%20test%20sets.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(fetch_sunspot%2C%20mo%2C%20pl%2C%20train_test_split)%3A%0A%20%20%20%20_raw%20%3D%20fetch_sunspot().frame%0A%20%20%20%20sunspots%20%3D%20_raw.group_by_dynamic(%22time%22%2C%20every%3D%221mo%22).agg(pl.col(%22sunspot_number%22).mean())%0A%20%20%20%20y_train%2C%20y_test%20%3D%20train_test_split(sunspots%2C%20test_size%3D0.15)%0A%20%20%20%20horizon%20%3D%20len(y_test)%0A%20%20%20%20mo.md(f%22**Sunspots**%3A%20%7Blen(sunspots)%7D%20months%2C%20**Train**%3A%20%7Blen(y_train)%7D%2C%20**Test**%3A%20%7Blen(y_test)%7D%22)%0A%20%20%20%20return%20horizon%2C%20sunspots%2C%20y_test%2C%20y_train%0A%0A%0A%40app.cell%0Adef%20_(plot_time_series%2C%20sunspots)%3A%0A%20%20%20%20plot_time_series(sunspots%2C%20title%3D%22Monthly%20Sunspot%20Numbers%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.%20Basic%20FeatureUnion%0A%0A%20%20%20%20Combine%20lag%20features%20with%20rolling%20statistics.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(FeatureUnion%2C%20LagTransformer%2C%20RollingStatisticsTransformer%2C%20mo%2C%20y_train)%3A%0A%20%20%20%20union_basic%20%3D%20FeatureUnion(%0A%20%20%20%20%20%20%20%20transformer_list%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22lags%22%2C%20LagTransformer(lag%3D%5B1%2C%206%2C%2012%5D))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22rolling%22%2C%20RollingStatisticsTransformer(window_size%3D12%2C%20statistics%3D%5B%22mean%22%2C%20%22std%22%5D))%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20)%0A%20%20%20%20union_basic.fit(y_train)%0A%20%20%20%20basic_features%20%3D%20union_basic.transform(y_train)%0A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22**Output%20columns**%3A%20%7Bbasic_features.columns%7D%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20f%22**Output%20shape**%3A%20%7Bbasic_features.shape%7D%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20f%22**observation_horizon**%3A%20%7Bunion_basic.observation_horizon%7D%20%22%0A%20%20%20%20%20%20%20%20f%22(max%20of%20lag%3D12%20and%20window%3D12)%22%0A%20%20%20%20)%0A%20%20%20%20return%20(basic_features%2C)%0A%0A%0A%40app.cell%0Adef%20_(basic_features%2C%20plot_time_series)%3A%0A%20%20%20%20plot_time_series(basic_features%2C%20title%3D%22Basic%20FeatureUnion%3A%20Lags%20%2B%20Rolling%20Stats%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.%20Verbose%20Feature%20Names%0A%0A%20%20%20%20When%20%60verbose_feature_names_out%3DTrue%60%20(default)%2C%20each%20output%20column%0A%20%20%20%20is%20prefixed%20with%20the%20transformer%20name.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(FeatureUnion%2C%20LagTransformer%2C%20RollingStatisticsTransformer%2C%20mo%2C%20y_train)%3A%0A%20%20%20%20_union_verbose%20%3D%20FeatureUnion(%0A%20%20%20%20%20%20%20%20transformer_list%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22lags%22%2C%20LagTransformer(lag%3D%5B1%2C%206%5D))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22rolling%22%2C%20RollingStatisticsTransformer(window_size%3D6%2C%20statistics%3D%22mean%22))%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%20_union_verbose.fit(y_train)%0A%20%20%20%20_out_verbose%20%3D%20_union_verbose.transform(y_train)%0A%0A%20%20%20%20_union_no_verbose%20%3D%20FeatureUnion(%0A%20%20%20%20%20%20%20%20transformer_list%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22lags%22%2C%20LagTransformer(lag%3D%5B1%2C%206%5D))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22rolling%22%2C%20RollingStatisticsTransformer(window_size%3D6%2C%20statistics%3D%22mean%22))%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_union_no_verbose.fit(y_train)%0A%20%20%20%20_out_plain%20%3D%20_union_no_verbose.transform(y_train)%0A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22**verbose%3DTrue%20columns**%3A%20%7B_out_verbose.columns%7D%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20f%22**verbose%3DFalse%20columns**%3A%20%7B_out_plain.columns%7D%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20%22Verbose%20names%20help%20disambiguate%20when%20multiple%20transformers%20produce%20%22%0A%20%20%20%20%20%20%20%20%22similarly-named%20features.%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%204.%20Three-Way%20Union%0A%0A%20%20%20%20Combine%20lags%2C%20rolling%20statistics%2C%20and%20exponential%20moving%20average.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ExponentialMovingAverage%2C%0A%20%20%20%20FeatureUnion%2C%0A%20%20%20%20LagTransformer%2C%0A%20%20%20%20RollingStatisticsTransformer%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20y_train%2C%0A)%3A%0A%20%20%20%20union_three%20%3D%20FeatureUnion(%0A%20%20%20%20%20%20%20%20transformer_list%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22lags%22%2C%20LagTransformer(lag%3D%5B1%2C%206%2C%2012%5D))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22rolling%22%2C%20RollingStatisticsTransformer(window_size%3D12%2C%20statistics%3D%5B%22mean%22%2C%20%22std%22%5D))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22ema%22%2C%20ExponentialMovingAverage(alpha%3D0.3))%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20)%0A%20%20%20%20union_three.fit(y_train)%0A%20%20%20%20three_features%20%3D%20union_three.transform(y_train)%0A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22**Three-way%20union%20output**%3A%20%7Bthree_features.shape%7D%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20f%22**Columns**%3A%20%7Bthree_features.columns%7D%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20f%22**observation_horizon**%3A%20%7Bunion_three.observation_horizon%7D%22%0A%20%20%20%20)%0A%20%20%20%20return%20three_features%2C%20union_three%0A%0A%0A%40app.cell%0Adef%20_(plot_time_series%2C%20three_features)%3A%0A%20%20%20%20plot_time_series(three_features%2C%20title%3D%22Three-Way%20Union%3A%20Lags%20%2B%20Rolling%20%2B%20EMA%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.%20FeatureUnion%20in%20a%20Forecaster%0A%0A%20%20%20%20Use%20%5B%60FeatureUnion%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.compose.feature_union.FeatureUnion%2F)%20as%20the%20%60feature_transformer%60%20to%20produce%20rich%0A%20%20%20%20feature%20sets%20for%20the%20reduction-based%20forecaster.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20MeanAbsoluteError%2C%0A%20%20%20%20PointReductionForecaster%2C%0A%20%20%20%20Ridge%2C%0A%20%20%20%20SeasonalNaive%2C%0A%20%20%20%20horizon%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20union_three%2C%0A%20%20%20%20y_test%2C%0A%20%20%20%20y_train%2C%0A)%3A%0A%20%20%20%20fc_union%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%3Dunion_three%2C%0A%20%20%20%20)%0A%20%20%20%20fc_union.fit(y_train%2C%20forecasting_horizon%3Dhorizon)%0A%20%20%20%20_y_pred_union%20%3D%20fc_union.predict(forecasting_horizon%3Dhorizon)%0A%0A%20%20%20%20%23%20Baseline%0A%20%20%20%20_naive%20%3D%20SeasonalNaive(seasonality%3D12)%0A%20%20%20%20_naive.fit(y_train%2C%20forecasting_horizon%3Dhorizon)%0A%20%20%20%20_y_pred_naive%20%3D%20_naive.predict(forecasting_horizon%3Dhorizon)%0A%0A%20%20%20%20_scorer%20%3D%20MeanAbsoluteError()%0A%20%20%20%20_scorer.fit(y_train)%0A%20%20%20%20_mae_union%20%3D%20float(_scorer.score(y_test%2C%20_y_pred_union))%0A%20%20%20%20_mae_naive%20%3D%20float(_scorer.score(y_test%2C%20_y_pred_naive))%0A%0A%20%20%20%20mo.md(f%22**FeatureUnion%20%2B%20Ridge%20MAE**%3A%20%7B_mae_union%3A.2f%7D%5Cn%5Cn**SeasonalNaive%20MAE**%3A%20%7B_mae_naive%3A.2f%7D%22)%0A%20%20%20%20return%20(fc_union%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)%20displays%20the%20predictions%20produced%20by%20the%0A%20%20%20%20%5B%60FeatureUnion%60%5D(%2Fpages%2Fapi%2Fgenerated%2Fyohou.compose.feature_union.FeatureUnion%2F)-powered%20forecaster%20against%20the%20test%20data.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(fc_union%2C%20horizon%2C%20plot_forecast%2C%20y_test%2C%20y_train)%3A%0A%20%20%20%20_y_pred%20%3D%20fc_union.predict(forecasting_horizon%3Dhorizon)%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%20_y_pred%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%22FeatureUnion%20(Lags%20%2B%20Rolling%20%2B%20EMA)%3A%20Sunspots%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%5BCompose%20Feature%20Pipelines%5D(%2Fpages%2Fhow-to%2Fcompose-feature-pipelines%2F)%20for%20the%20full%20guide%0A%20%20%20%20-%20%5BUse%20Preprocessing%20Transformers%5D(%2Fpages%2Fhow-to%2Fuse-preprocessing-transformers%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
7456f73c02b8eac1e311e97cdb04443b