look at my ensemble code again, from statsforecast import StatsForecast
from statsforecast.models import AutoARIMA, AutoETS, DynamicOptimizedTheta
from statsforecast.utils import ConformalIntervals
import numpy as np
import polars as pl

# Polars option to display all rows
pl.Config.set_tbl_rows(None)

# Initialize the models
models = [
AutoARIMA(season_length=12),
AutoETS(season_length=12),
DynamicOptimizedTheta(season_length=12)
]

# Initialize the StatsForecast model
sf = StatsForecast(models=models, freq=‘1w’, n_jobs=-1)

# Perform cross-validation with a step size of 1 to mimic an expanding window
crossvalidation_df = sf.cross_validation(df=y_cl4_filtered, h=1, step_size=1, n_windows=8, sort_df=True)

# Calculate the ensemble mean
ensemble = crossvalidation_df[[‘AutoARIMA’, ‘AutoETS’, ‘DynamicOptimizedTheta’]].mean(axis=1)

# Create a Series for the ensemble mean
ensemble_series = pl.Series(‘Ensemble’, ensemble)

# Add the ensemble mean as a new column to the DataFrame
crossvalidation_df = crossvalidation_df.with_columns(ensemble_series)

def wmape(y_true, y_pred):
return np.abs(y_true - y_pred).sum() / np.abs(y_true).sum()

# Calculate the WMAPE for the ensemble model
wmape_value = wmape(crossvalidation_df[‘y’], crossvalidation_df[‘Ensemble’])
print('Average WMAPE for Ensemble: ', round(wmape_value, 4))

# Calculate the errors for the ensemble model
errors = crossvalidation_df[‘y’] - crossvalidation_df[‘Ensemble’]

# For an individual forecast
individual_accuracy = 1 - (abs(crossvalidation_df[‘y’] - crossvalidation_df[‘Ensemble’]) / crossvalidation_df[‘y’])
individual_bias = (crossvalidation_df[‘Ensemble’] / crossvalidation_df[‘y’]) - 1

# Add these calculations as new columns to DataFrame
crossvalidation_df = crossvalidation_df.with_columns([
individual_accuracy.alias(“individual_accuracy”),
individual_bias.alias(“individual_bias”)
])

# Print the individual accuracy and bias for each week
for row in crossvalidation_df.to_dicts():
id = row[‘unique_id’]
date = row[‘ds’]
accuracy = row[‘individual_accuracy’]
bias = row[‘individual_bias’]
print(f"{id}, {date}, Individual Accuracy: {accuracy:.4f}, Individual Bias: {bias:.4f}")

# For groups of forecasts
group_accuracy = 1 - (errors.abs().sum() / crossvalidation_df[‘y’].sum())
group_bias = (crossvalidation_df[‘Ensemble’].sum() / crossvalidation_df[‘y’].sum()) - 1

# Print the average group accuracy and group bias over all folds for the ensemble model
print('Average Group Accuracy: ', round(group_accuracy, 4))
print(‘Average Group Bias: ‘, round(group_bias, 4))


# Fit the models on the entire dataset
sf.fit(y_cl4_fit_1)

# Instantiate the ConformalIntervals class
prediction_intervals = ConformalIntervals()

# Generate 24 months forecasts
forecasts_df = sf.forecast(h=52*2, prediction_intervals=prediction_intervals, level=[95], id_col=‘unique_id’, sort_df=True)

# Apply the non-negative constraint to the forecasts of individual models
forecasts_df = forecasts_df.with_columns([
pl.when(pl.col(‘AutoARIMA’) < 0).then(0).otherwise(pl.col(‘AutoARIMA’)).alias(‘AutoARIMA’),
pl.when(pl.col(‘AutoETS’) < 0).then(0).otherwise(pl.col(‘AutoETS’)).alias(‘AutoETS’),
pl.when(pl.col(‘DynamicOptimizedTheta’) < 0).then(0).otherwise(pl.col(‘DynamicOptimizedTheta’)).alias(‘DynamicOptimizedTheta’),
])

# Calculate the ensemble forecast
ensemble_forecast = forecasts_df[[‘AutoARIMA’, ‘AutoETS’, ‘DynamicOptimizedTheta’]].mean(axis=1)

# Calculate the lower and upper prediction intervals for the ensemble forecast
ensemble_lo_95 = forecasts_df.select(
[
pl.when(pl.col(‘AutoARIMA-lo-95’) < 0).then(0).otherwise(pl.col(‘AutoARIMA-lo-95’)).alias(‘AutoARIMA-lo-95’),
pl.when(pl.col(‘AutoETS-lo-95’) < 0).then(0).otherwise(pl.col(‘AutoETS-lo-95’)).alias(‘AutoETS-lo-95’),
pl.when(pl.col(‘DynamicOptimizedTheta-lo-95’) < 0).then(0).otherwise(pl.col(‘DynamicOptimizedTheta-lo-95’)).alias(‘DynamicOptimizedTheta-lo-95’),
]
).mean(axis=1)
ensemble_hi_95 = forecasts_df[[‘AutoARIMA-hi-95’, ‘AutoETS-hi-95’, ‘DynamicOptimizedTheta-hi-95’]].mean(axis=1)

# Create Series for the ensemble forecast and its prediction intervals
ensemble_forecast_series = pl.Series(‘EnsembleForecast’, ensemble_forecast)
ensemble_lo_95_series = pl.Series(‘Ensemble-lo-95’, ensemble_lo_95)
ensemble_hi_95_series = pl.Series(‘Ensemble-hi-95’, ensemble_hi_95)

# Add the ensemble forecast and its prediction intervals as new columns to the DataFrame
forecasts_df = forecasts_df.with_columns([ensemble_forecast_series, ensemble_lo_95_series, ensemble_hi_95_series])

# Round the ensemble forecast and prediction intervals and convert to integer
forecasts_df = forecasts_df.with_columns([
pl.col(“EnsembleForecast”).round().cast(pl.Int32),
pl.col(“Ensemble-lo-95”).round().cast(pl.Int32),
pl.col(“Ensemble-hi-95”).round().cast(pl.Int32)
])

# Reorder the columns
forecasts_df = forecasts_df.select([
“unique_id”,
“ds”,
“EnsembleForecast”,
“Ensemble-lo-95”,
“Ensemble-hi-95”,
“AutoARIMA”,
“AutoARIMA-lo-95”,
“AutoARIMA-hi-95”,
“AutoETS”,
“AutoETS-lo-95”,
“AutoETS-hi-95”,
“DynamicOptimizedTheta”,
“DynamicOptimizedTheta-lo-95”,
“DynamicOptimizedTheta-hi-95”
])

# Create an empty list
forecasts_list = []

# Append each row to the list
for row in forecasts_df.to_dicts():
forecasts_list.append(row)

# Print the list
for forecast in forecasts_list:
print(forecast) you could see taht unique id was created using 4 columns concat # Concatenate ‘MaterialID’, ‘SalesOrg’, ‘DistrChan’, ‘CL4’ to a new column ‘unique_id’
y_cl4 = y_cl4.with_columns(
pl.concat_str([pl.col(‘MaterialID’), pl.col(‘SalesOrg’), pl.col(‘DistrChan’), pl.col(‘CL4’)], separator=’_’).alias(‘unique_id’)
)

# Drop the original columns
y_cl4 = y_cl4.drop([‘MaterialID’, ‘SalesOrg’, ‘DistrChan’, ‘CL4’])

y_cl4 = y_cl4.rename({‘WeekDate’: ‘ds’, ‘OrderQuantity’: ‘y’}), some of the output {‘unique_id’: ‘6922332’, ‘ds’: datetime.datetime(2025, 9, 22, 0, 0), ‘EnsembleForecast’: 5691, ‘Ensemble-lo-95’: 3171, ‘Ensemble-hi-95’: 8212, ‘AutoARIMA’: 5561.7099609375, ‘AutoARIMA-lo-95’: 3113.181396484375, ‘AutoARIMA-hi-95’: 8010.23876953125, ‘AutoETS’: 5528.330078125, ‘AutoETS-lo-95’: 4238.36279296875, ‘AutoETS-hi-95’: 6818.29736328125, ‘CES’: 6107.98779296875, ‘CES-lo-95’: 2193.064208984375, ‘CES-hi-95’: 10022.9111328125, ‘DynamicOptimizedTheta’: 5567.36328125, ‘DynamicOptimizedTheta-lo-95’: 3139.6455078125, ‘DynamicOptimizedTheta-hi-95’: 7995.0810546875}
{‘unique_id’: ‘6922332’, ‘ds’: datetime.datetime(2025, 9, 29, 0, 0), ‘EnsembleForecast’: 5260, ‘Ensemble-lo-95’: 2739, ‘Ensemble-hi-95’: 7780, ‘AutoARIMA’: 5561.7099609375, ‘AutoARIMA-lo-95’: 3113.181396484375, ‘AutoARIMA-hi-95’: 8010.23876953125, ‘AutoETS’: 5528.716796875, ‘AutoETS-lo-95’: 4238.74951171875, ‘AutoETS-hi-95’: 6818.68408203125, ‘CES’: 4380.20947265625, ‘CES-lo-95’: 465.2858581542969, ‘CES-hi-95’: 8295.1328125, ‘DynamicOptimizedTheta’: 5567.37158203125, ‘DynamicOptimizedTheta-lo-95’: 3139.65380859375, ‘DynamicOptimizedTheta-hi-95’: 7995.08935546875}
{‘unique_id’: ‘6922332’, ‘ds’: datetime.datetime(2025, 10, 6, 0, 0), ‘EnsembleForecast’: 5238, ‘Ensemble-lo-95’: 2717, ‘Ensemble-hi-95’: 7758, ‘AutoARIMA’: 5561.7099609375, ‘AutoARIMA-lo-95’: 3113.181396484375, ‘AutoARIMA-hi-95’: 8010.23876953125, ‘AutoETS’: 5529.08837890625, ‘AutoETS-lo-95’: 4239.12109375, ‘AutoETS-hi-95’: 6819.0556640625, ‘CES’: 4292.49072265625, ‘CES-lo-95’: 377.5670166015625, ‘CES-hi-95’: 8207.4140625, ‘DynamicOptimizedTheta’: 5567.3798828125, ‘DynamicOptimizedTheta-lo-95’: 3139.662109375, ‘DynamicOptimizedTheta-hi-95’: 7995.09765625}
{‘unique_id’: ‘6922332’, ‘ds’: datetime.datetime(2025, 10, 13, 0, 0), ‘EnsembleForecast’: 5165, ‘Ensemble-lo-95’: 2645, ‘Ensemble-hi-95’: 7685, ‘AutoARIMA’: 5561.7099609375, ‘AutoARIMA-lo-95’: 3113.181396484375, ‘AutoARIMA-hi-95’: 8010.23876953125, ‘AutoETS’: 5529.4462890625, ‘AutoETS-lo-95’: 4239.47900390625, ‘AutoETS-hi-95’: 6819.41357421875, ‘CES’: 4001.488037109375, ‘CES-lo-95’: 86.56428527832031, ‘CES-hi-95’: 7916.41162109375, ‘DynamicOptimizedTheta’: 5567.38818359375, ‘DynamicOptimizedTheta-lo-95’: 3139.67041015625, ‘DynamicOptimizedTheta-hi-95’: 7995.10595703125} is there a way to break the unqiue id in the 4 columns in the output again? so instead of ‘unique_id’: …, it would be material_id: …, sales_org:. …, distrchan: …, cl4: …, and also the ‘ds’ be rename back to ‘WeekDate’