Skip to content

API Reference

Folder Config/

add_lag_features(df, forecast_horizon, max_lag_day)

Adds a lagged column to the dataframe based on the given horizon in minutes and max lag in days.

Args: df (pd.DataFrame): The input dataframe with a datetime index and a column 'y'. forecast_horizon (int): The horizon in minutes for the lag. max_lag_day (int): the number of days until the longest lag

Returns: pd.DataFrame: The dataframe with additional columns for the lags.

Source code in docs\notebooks\config\general_functions.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
def add_lag_features(df, forecast_horizon, max_lag_day):
    """
    Adds a lagged column to the dataframe based on the given horizon in minutes and max lag in days.

    Args:
    df (pd.DataFrame): The input dataframe with a datetime index and a column 'y'.
    forecast_horizon (int): The horizon in minutes for the lag.
    max_lag_day (int): the number of days until the longest lag

    Returns:
    pd.DataFrame: The dataframe with additional columns for the lags.
    """

    # Convert the horizon to a timedelta object
    horizon_timedelta = pd.Timedelta(minutes=forecast_horizon)
    consecutive_timedelta = df.index[1] - df.index[0]

    # Calculate the number of new columns
    n_new_cols = len(df[df.index < df.index[0] + pd.DateOffset(days=max_lag_day)])

    # List to hold all the new lagged columns
    new_cols = []

    # Generate lagged columns based on the horizon and max lag

    #Generate lagged columns not only based on net load but also based on weather data if available
    for column in df.columns:
    # Generate lagged columns for the current column
        for i in range(n_new_cols):
            shift_timedelta = horizon_timedelta + i * consecutive_timedelta
            new_col_name = f'{column}_lag_{shift_timedelta}m'
            new_cols.append(df[column].shift(freq=shift_timedelta).rename(new_col_name))


    # Concatenate the new lagged columns with the original dataframe
    df = pd.concat([df] + new_cols, axis=1)

    df.dropna(inplace=True)

    return df

compute_MAE(forecast, observation)

As the name suggest.

Parameters:

Name Type Description Default
forecast df

series of the forecast result from the model

required
observation df

series of the observed value (actual value)

required

Returns:

Type Description

error as the name suggest (float): as the name suggest

Source code in docs\notebooks\config\general_functions.py
834
835
836
837
838
839
840
841
842
843
844
def compute_MAE(forecast, observation):
    """As the name suggest.

    Args:
        forecast (df): series of the forecast result from the model
        observation (df): series of the observed value (actual value)

    Returns:
        error as the name suggest (float): as the name suggest
    """
    return round((abs(forecast - observation)).mean(), 3)

compute_MAPE(forecast, observation)

As the name suggest. Be careful with MAPE though because its value can go to inf since the observed value can be 0.

Parameters:

Name Type Description Default
forecast df

series of the forecast result from the model

required
observation df

series of the observed value (actual value)

required

Returns:

Type Description

error as the name suggest (float): as the name suggest

Source code in docs\notebooks\config\general_functions.py
860
861
862
863
864
865
866
867
868
869
870
def compute_MAPE(forecast, observation):
    """As the name suggest. Be careful with MAPE though because its value can go to inf since the observed value can be 0. 

    Args:
        forecast (df): series of the forecast result from the model
        observation (df): series of the observed value (actual value)

    Returns:
        error as the name suggest (float): as the name suggest
    """
    return round((abs((forecast - observation) / observation) * 100).mean(), 3)

compute_MASE(forecast, observation, train_result)

As the name suggest. MASE is first introduced by Rob Hyndman, used to handle MAPE problem being infinity. Instead of using observed value as denominator, MASE uses MAE of the naive forecast at the train set for denominator.

Parameters:

Name Type Description Default
forecast df

series of the forecast result from the model

required
observation df

series of the observed value (actual value)

required

Returns:

Type Description

error as the name suggest (float): as the name suggest

Source code in docs\notebooks\config\general_functions.py
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
def compute_MASE(forecast, observation, train_result):
    """As the name suggest. MASE is first introduced by Rob Hyndman, used to handle MAPE problem being infinity. 
    Instead of using observed value as denominator,
    MASE uses MAE of the naive forecast at the train set for denominator. 

    Args:
        forecast (df): series of the forecast result from the model
        observation (df): series of the observed value (actual value)

    Returns:
        error as the name suggest (float): as the name suggest
    """
    errors = abs(forecast - observation)
    MAE_naive = compute_MAE(train_result['naive'], train_result['observation'])

    MASE = errors.mean() / MAE_naive
    return round(MASE, 3)

compute_MBE(forecast, observation)

As the name suggest.

Parameters:

Name Type Description Default
forecast df

series of the forecast result from the model

required
observation df

series of the observed value (actual value)

required

Returns:

Type Description

error as the name suggest (float): as the name suggest

Source code in docs\notebooks\config\general_functions.py
821
822
823
824
825
826
827
828
829
830
831
def compute_MBE(forecast, observation):
    """As the name suggest.

    Args:
        forecast (df): series of the forecast result from the model
        observation (df): series of the observed value (actual value)

    Returns:
        error as the name suggest (float): as the name suggest
    """
    return round(((forecast - observation).sum()) / len(observation), 5)

compute_R2(forecast, observation)

As the name suggest. Be careful with R2 though because it is not a forecast evaluation. It is just used to show linearity on the scatter plot of forecast and observed value.

Parameters:

Name Type Description Default
forecast df

series of the forecast result from the model

required
observation df

series of the observed value (actual value)

required

Returns:

Type Description

error as the name suggest (float): as the name suggest

Source code in docs\notebooks\config\general_functions.py
906
907
908
909
910
911
912
913
914
915
916
917
def compute_R2(forecast, observation):
    """As the name suggest. Be careful with R2 though because it is not a forecast evaluation. 
    It is just used to show linearity on the scatter plot of forecast and observed value. 

    Args:
        forecast (df): series of the forecast result from the model
        observation (df): series of the observed value (actual value)

    Returns:
        error as the name suggest (float): as the name suggest
    """
    return round(forecast.corr(observation)**2, 3)

compute_RMSE(forecast, observation)

As the name suggest.

Parameters:

Name Type Description Default
forecast df

series of the forecast result from the model

required
observation df

series of the observed value (actual value)

required

Returns:

Type Description

error as the name suggest (float): as the name suggest

Source code in docs\notebooks\config\general_functions.py
847
848
849
850
851
852
853
854
855
856
857
def compute_RMSE(forecast, observation):
    """As the name suggest.

    Args:
        forecast (df): series of the forecast result from the model
        observation (df): series of the observed value (actual value)

    Returns:
        error as the name suggest (float): as the name suggest
    """
    return round(np.sqrt(((forecast - observation) ** 2).mean()), 3)

compute_exp_no(path_result)

Compute experiment number for folder & file naming based on the number of existing experiments that have been done. For example, if on the folder there are already 5 experiment folders, then the new experiment no. is E00006.

Parameters:

Name Type Description Default
path_result str

relative path of experiment folder, stored in config

required

Returns:

Name Type Description
int

exp no

str

exp no in str

Source code in docs\notebooks\config\general_functions.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def compute_exp_no(path_result):
    """Compute experiment number for folder & file naming based on the number of existing experiments that have been done.
    For example, if on the folder there are already 5 experiment folders, then the new experiment no. is E00006.

    Args:
        path_result (str): relative path of experiment folder, stored in config

    Returns:
        int: exp no 
        str: exp no in str
    """
    subfolders = os.listdir(path_result)
    number_of_folders = len(subfolders)
    experiment_no = number_of_folders - 1 # there is one archive folder
    experiment_no_str = f"E{str(experiment_no).zfill(5)}"

    return experiment_no, experiment_no_str

compute_folder_name(experiment_no_str, forecast_horizon, model_name, hyperparameter_no)

Folder name in the format of [exp number][exp date][dataset][forecast horizon][model]_[hyperparameter]

Parameters:

Name Type Description Default
experiment_no_str str

exp number

required
forecast_horizon int

forecast horizon in minutes

required
model_name str

for example, m1_naive

required
hyperparameter_no str

for example, hp1

required

Returns:

Name Type Description
str

folder name

Source code in docs\notebooks\config\general_functions.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def compute_folder_name(experiment_no_str, forecast_horizon, model_name, hyperparameter_no):
    """Folder name in the format of [exp number]_[exp date]_[dataset]_[forecast horizon]_[model]_[hyperparameter]

    Args:
        experiment_no_str (str): exp number
        forecast_horizon (int): forecast horizon in minutes
        model_name (str): for example, m1_naive
        hyperparameter_no (str): for example, hp1

    Returns:
        str: folder name
    """
    folder_name = \
        experiment_no_str + '_' +\
        datetime.today().date().strftime("%y%m%d") + '_' +\
        dataset.split('_')[0] + '_' +\
        'fh' + str(forecast_horizon) + '_' +\
        model_name + '_' +\
        hyperparameter_no
    return folder_name

compute_fskill(forecast, observation, naive)

As the name suggest. Forecast Skill is a relative measure seeing the improvement of the model performance over naive model.

Parameters:

Name Type Description Default
forecast df

series of the forecast result from the model

required
observation df

series of the observed value (actual value)

required

Returns:

Type Description

error as the name suggest (float): as the name suggest

Source code in docs\notebooks\config\general_functions.py
892
893
894
895
896
897
898
899
900
901
902
903
def compute_fskill(forecast, observation, naive):
    """As the name suggest. Forecast Skill is a relative measure seeing the improvement 
    of the model performance over naive model. 

    Args:
        forecast (df): series of the forecast result from the model
        observation (df): series of the observed value (actual value)

    Returns:
        error as the name suggest (float): as the name suggest
    """
    return round((1 - compute_RMSE(forecast, observation) / compute_RMSE(naive, observation)) * 100, 3)

export_result(filepath, df_a1_result, cross_val_result_df, hyperparameter)

Export experiment summary: 1. experiment result 2. hyperparameter 3. cross validation detailed result

Parameters:

Name Type Description Default
filepath dict

dictionary of filepaths for exporting result

required
Source code in docs\notebooks\config\general_functions.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
def export_result(filepath, df_a1_result, cross_val_result_df, hyperparameter):
    """Export experiment summary:
    1. experiment result
    2. hyperparameter
    3. cross validation detailed result

    Args:
        filepath (dict): dictionary of filepaths for exporting result
    """
    # Create a df of hyperparameter being used
    df_a2 = pd.DataFrame(hyperparameter)

    # EXPORT IT
    df_a1_result.to_csv(filepath['a1'], index=False)
    df_a2.to_csv(filepath['a2'])
    cross_val_result_df.to_csv(filepath['a3'])

histogram_residual(residual, df, pathname)

Produce histogiram of residual value and save it on the designated folder

Parameters:

Name Type Description Default
residual df

forecast - observation

required
pathname str

filepath to save the figure

required
Source code in docs\notebooks\config\general_functions.py
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
def histogram_residual(residual, df, pathname):
    """Produce histogiram of residual value and save it on the designated folder

    Args:
        residual (df): forecast - observation
        pathname (str): filepath to save the figure
    """
    # Create the figure with specified size
    plt.figure(figsize=(9, 9))

    # Set background color
    # plt.gcf().patch.set_facecolor(platinum)

    # Compute the range
    dataset_range = df['y'].max() - df['y'].min()
    bin_min = -dataset_range/7
    bin_max = dataset_range/7

    # Plot the actual and forecast data
    plt.hist(residual, bins=31, range=(bin_min, bin_max), color=dark_blue, edgecolor=dark_blue, alpha=0.7)


    # Set the font to Arial
    plt.rcParams['font.family'] = 'Arial'

    # Remove grid lines
    plt.grid(False)

    # Set tick marks for x and y axis
    plt.xticks(fontsize=12, color=dark_blue, alpha=0.5, rotation=0)
    plt.yticks(fontsize=12, color=dark_blue, alpha=0.5)

    # Add borders to the plot
    plt.gca().spines['top'].set_color(dark_blue)
    plt.gca().spines['right'].set_color(dark_blue)
    plt.gca().spines['bottom'].set_color(dark_blue)
    plt.gca().spines['left'].set_color(dark_blue)

    # Remove the tick markers (the small lines)
    plt.tick_params(axis='x', which='both', length=0)  # Remove x-axis tick markers
    plt.tick_params(axis='y', which='both', length=0)  # Remove y-axis tick markers

    # Set axis titles
    plt.xlabel('Forecast Residual (kW)', fontsize=14, color=dark_blue)
    plt.ylabel('Count', fontsize=14, color=dark_blue)

    # Remove title
    plt.title('')


    plt.savefig(pathname, format='png', bbox_inches='tight')
    plt.close()

input_and_process(path_data_cleaned, forecast_horizon, max_lag_day, n_block, hyperparameter)

read dataset, add calendar features, add lag features (which depends on the forecast horizon).

Parameters:

Name Type Description Default
path_data_cleaned str

path to the dataset chosen

required
forecast_horizon int

forecast horizon in minutes

required
max_lag_day int

how much lag data will be used, written in days. For example, 7 means lag data until d-7 is used.

required
n_block int

number of blocks to divide the original df. This includes the block for hold_out_df, so if k=10, this n_block = k+1 = 11

required
hyperparameter dict

hyperparameters for the model

required

Returns:

Name Type Description
block_length int

number of weeks per block

holdout_df df

unused df, can be used later for unbiased estimate of final model performance

df df

df that will be used for training and validation (test) set

Source code in docs\notebooks\config\general_functions.py
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
def input_and_process(path_data_cleaned, forecast_horizon, max_lag_day, n_block, hyperparameter):
    """read dataset, add calendar features, add lag features (which depends on the forecast horizon).

    Args:
        path_data_cleaned (str): path to the dataset chosen
        forecast_horizon (int): forecast horizon in minutes
        max_lag_day (int): how much lag data will be used, written in days. For example, 7 means lag data until d-7 is used. 
        n_block (int): number of blocks to divide the original df. This includes the block for hold_out_df, so if k=10, this n_block = k+1 = 11
        hyperparameter (dict): hyperparameters for the model

    Returns:
        block_length (int): number of weeks per block
        holdout_df (df): unused df, can be used later for unbiased estimate of final model performance
        df (df): df that will be used for training and validation (test) set
    """
    # MAKE THIS AS FUNCTION
    # ADD CALENDAR DATA (holiday to add)
    # columns_to_use = ['datetime', 'netload_kW']
    df = pd.read_csv(path_data_cleaned + dataset, index_col=0, parse_dates=True)
    df.rename(columns={'netload_kW': 'y'}, inplace=True)

    # 1. Check if forecast horizon is >= dataset frequency
    # for example, if dataset is daily, forecast horizon should be at least 1 day
    # compute dataset frequency in minutes based on the datetime index
    dataset_freq = (df.index[1] - df.index[0]).seconds / 60
    if forecast_horizon < dataset_freq:
        raise ValueError('Forecast horizon should be >= dataset frequency')
    else:
        print('Pass Test 1 - Forecast horizon is >= dataset frequency')

    # 2. Check if hyperparameter choice is possible given the forecast horizon
    # for example, with forecast horizon of 2 days, we cannot use 1 day as the hyperparameter of seasonal naive forecast.


    if model_name == 'm2_snaive':
        if int(hyperparameter['days'] * 24 * 60) < forecast_horizon:
            raise ValueError('Choice of seasonal naive hyperparameter needs to be >= forecast horizon! Please change the hyperparameter.')
    # if model_name == 'm4_sarima':
    #     if int(hyperparameter['seasonal_period_days'] * 24 * 60) < forecast_horizon:
    #         raise ValueError('Choice of seasonal_period_days in SARIMA hyperparameter >= forecast horizon! Please change the hyperparameter.')
    print('Pass Test 2 - Hyperparameter choice is possible given the forecast horizon')


# ADD LAG FEATURES
    df = add_lag_features(df, forecast_horizon, max_lag_day)

# ADD CALENDAR FEATURES    
    # 1. Numerical representation of the datetime (Excel-style)
    numeric_datetime = pd.Series((df.index - pd.Timestamp("1970-01-01")) / pd.Timedelta(days=1), index=df.index)

    # 2. Year
    year = pd.Series(df.index.year, index=df.index)

    # 3. One-hot encoding of month (is_jan, is_feb, ..., is_nov, excluding December)
    month_dummies = pd.get_dummies(df.index.month, prefix='is', drop_first=False)

    # Custom column names for months: is_jan, is_feb, ..., is_nov
    month_names = ['is_jan', 'is_feb', 'is_mar', 'is_apr', 'is_may', 'is_jun', 
                'is_jul', 'is_aug', 'is_sep', 'is_oct', 'is_nov', 'is_dec']  

    # Drop the last column (December) to avoid redundancy and rename the columns
    month_dummies = month_dummies.iloc[:, :-1]  # Exclude December column
    month_dummies.columns = month_names[:month_dummies.shape[1]]  # Apply custom column names
    month_dummies = month_dummies.astype(int)  # Convert to 1 and 0
    month_dummies.index = df.index

    # 4. One-hot encoding of hour (hour_0, hour_1, ..., hour_22, excluding hour_23)
    hour_dummies = pd.get_dummies(df.index.hour, prefix='hour', drop_first=False).iloc[:, :-1]
    hour_dummies = hour_dummies.astype(int)  # Convert to 1 and 0
    hour_dummies.index = df.index

    # 5. One-hot encoding of day of week (is_mon, is_tue, ..., is_sat, excluding Sunday)
    # Mapping day of week (0=Mon, 1=Tue, ..., 6=Sun)
    dayofweek_dummies = pd.get_dummies(df.index.dayofweek, prefix='is', drop_first=False).iloc[:, :-1]

    # Custom mapping for days of the week: is_mon, is_tue, ..., is_sat
    dayofweek_names = ['is_mon', 'is_tue', 'is_wed', 'is_thu', 'is_fri', 'is_sat']  # Custom day names
    dayofweek_dummies.columns = dayofweek_names[:dayofweek_dummies.shape[1]]  # Apply custom column names
    dayofweek_dummies = dayofweek_dummies.astype(int)  # Convert to 1 and 0
    dayofweek_dummies.index = df.index

    # 6. Is weekday (1 if Monday to Friday, 0 if Saturday/Sunday)
    is_weekday = pd.Series((df.index.dayofweek < 5).astype(int), index=df.index)


    # Concatenate all new features into the original dataframe at once
    df = pd.concat([df, 
                    numeric_datetime.rename('numeric_datetime'), 
                    year.rename('year'),
                    month_dummies, 
                    hour_dummies, 
                    dayofweek_dummies, 
                    is_weekday.rename('is_weekday')], axis=1)

    block_length, holdout_df, df = separate_holdout(df, n_block)

    return block_length, holdout_df, df

prepare_directory(path_result, forecast_horizon, model_name, hyperparameter_no)

Do two things, 1. Create folders inside the experiment result folder 2. Create some file names to be used when exporting result later

Parameters:

Name Type Description Default
path_result str

relative path to the experiment result folder

required
forecast_horizon int

forecast horizon in minutes

required
model_name str

eg m1_naive

required
hyperparameter_no str

eg

required

Returns:

Name Type Description
hyperparameter df

pd df series of hyperparameter chosen

experiment_no_str (str) : experiment number

filepath (dict) : dict of all filepaths that will be exported

Source code in docs\notebooks\config\general_functions.py
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
def prepare_directory(path_result, forecast_horizon, model_name, hyperparameter_no):
    """Do two things,
    1. Create folders inside the experiment result folder
    2. Create some file names to be used when exporting result later


    Args:
        path_result (str): relative path to the experiment result folder
        forecast_horizon (int): forecast horizon in minutes
        model_name (str): eg m1_naive
        hyperparameter_no (str): eg

    Returns:
        hyperparameter (df): pd df series of hyperparameter chosen
        experiment_no_str (str) : experiment number
        filepath (dict) : dict of all filepaths that will be exported
    """

    hyperparameter_table = globals()[f"{model_name.split('_')[0]}_hp_table"]
    hyperparameter = hyperparameter_table.loc[hyperparameter_no]

    experiment_no, experiment_no_str = compute_exp_no(path_result)
    folder_name = compute_folder_name(experiment_no_str, forecast_horizon, model_name, hyperparameter_no)

    # CREATE FOLDER
    cv_folder_train = experiment_no_str + '_cv_train'
    cv_folder_test = experiment_no_str + '_cv_test'
    cv1_plot_folder = experiment_no_str + '_cv1_plots'
    folder_model = experiment_no_str + '_models'

    path_result2 = path_result + folder_name +'/'
    path_result_train = path_result2 + cv_folder_train +'/'
    path_result_test = path_result2 + cv_folder_test +'/'
    path_result_plot = path_result2 + cv1_plot_folder +'/'
    path_model = path_result2 + folder_model +'/'

    # MAKE FOLDERS
    os.mkdir(path_result2)
    os.mkdir(path_result_train)
    os.mkdir(path_result_test)
    os.mkdir(path_result_plot)
    os.mkdir(path_model)

    # MAKE FILE PATH
    filepath = {
        'a1' : path_result2 + experiment_no_str + '_a1_experiment_result.csv',
        'a2' : path_result2 + experiment_no_str + '_a2_hyperparameter.csv',
        'a3' : path_result2 + experiment_no_str + '_a3_cross_validation_result.csv',
        'b1' : path_result_plot + experiment_no_str + '_b1_train_timeplot.png', # Time Plot of Forecast vs Observation
        'b2' : path_result_plot + experiment_no_str + '_b2_train_scatterplot.png', # Scatter Plot of Forecast vs Observation
        'b3' : path_result_plot + experiment_no_str + '_b3_train_residual_timeplot.png', # Time Plot of Residual
        'b4' : path_result_plot + experiment_no_str + '_b4_train_residual_histogram.png', # Histogram of Residual
        'b5' : path_result_plot + experiment_no_str + '_b5_train_learningcurve.png', # Learning Curve vs Epoch
        'c1' : path_result_plot + experiment_no_str + '_c1_test_timeplot.png',  # Time Plot of Forecast vs Observation
        'c2' : path_result_plot + experiment_no_str + '_c2_test_scatterplot.png',  # Scatter Plot of Forecast vs Observation
        'c3' : path_result_plot + experiment_no_str + '_c3_test_residual_timeplot.png',  # Time Plot of Residual
        'c4' : path_result_plot + experiment_no_str + '_c4_test_residual_histogram.png',  # Histogram of Residual
        'c5' : path_result_plot + experiment_no_str + '_c5_test_learningcurve.png',  # Learning Curve vs Epoch

        # B. FOLDER FOR CROSS VALIDATION TIME SERIES
        'train_cv' : {
            1 : path_result_train + experiment_no_str + '_cv1_train_result.csv',
            2 : path_result_train + experiment_no_str + '_cv2_train_result.csv',
            3 : path_result_train + experiment_no_str + '_cv3_train_result.csv',
            4 : path_result_train + experiment_no_str + '_cv4_train_result.csv',
            5 : path_result_train + experiment_no_str + '_cv5_train_result.csv',
            6 : path_result_train + experiment_no_str + '_cv6_train_result.csv',
            7 : path_result_train + experiment_no_str + '_cv7_train_result.csv',
            8 : path_result_train + experiment_no_str + '_cv8_train_result.csv',
            9 : path_result_train + experiment_no_str + '_cv9_train_result.csv',
            10 : path_result_train + experiment_no_str + '_cv10_train_result.csv'
        },

        'test_cv' : {
            1 : path_result_test + experiment_no_str + '_cv1_test_result.csv',
            2 : path_result_test + experiment_no_str + '_cv2_test_result.csv',
            3 : path_result_test + experiment_no_str + '_cv3_test_result.csv',
            4 : path_result_test + experiment_no_str + '_cv4_test_result.csv',
            5 : path_result_test + experiment_no_str + '_cv5_test_result.csv',
            6 : path_result_test + experiment_no_str + '_cv6_test_result.csv',
            7 : path_result_test + experiment_no_str + '_cv7_test_result.csv',
            8 : path_result_test + experiment_no_str + '_cv8_test_result.csv',
            9 : path_result_test + experiment_no_str + '_cv9_test_result.csv',
            10 : path_result_test + experiment_no_str + '_cv10_test_result.csv'
        },

        'model' : {
            1 : path_model + experiment_no_str + '_cv1_model.pkl',
            2 : path_model + experiment_no_str + '_cv2_model.pkl',
            3 : path_model + experiment_no_str + '_cv3_model.pkl',
            4 : path_model + experiment_no_str + '_cv4_model.pkl',
            5 : path_model + experiment_no_str + '_cv5_model.pkl',
            6 : path_model + experiment_no_str + '_cv6_model.pkl',
            7 : path_model + experiment_no_str + '_cv7_model.pkl',
            8 : path_model + experiment_no_str + '_cv8_model.pkl',
            9 : path_model + experiment_no_str + '_cv9_model.pkl',
            10 : path_model + experiment_no_str + '_cv10_model.pkl'
        }
    }
    return hyperparameter,experiment_no_str, filepath

produce_forecast(model_name, model, train_df_X, test_df_X, train_df_y, forecast_horizon)

Generate forecasts based on the model and its name.

Parameters:

Name Type Description Default
model_name string

Model identifier (e.g., 'm1_naive').

required
model dict

Trained model object containing all relevant features.

required
train_df_X DataFrame

Matrix of predictors for training set.

required
test_df_X DataFrame

Matrix of predictors for test set.

required
train_df_y DataFrame

Target forecast y for training set.

required
forecast_horizon int

Forecast horizon in minutes.

required

Returns:

Name Type Description
train_df_y_hat DataFrame

Forecasted values for training set.

test_df_y_hat DataFrame

Forecasted values for test set.

Source code in docs\notebooks\config\general_functions.py
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
def produce_forecast(model_name, model, train_df_X, test_df_X, train_df_y, forecast_horizon):
    """Generate forecasts based on the model and its name.

    Args:
        model_name (string): Model identifier (e.g., 'm1_naive').
        model (dict): Trained model object containing all relevant features.
        train_df_X (DataFrame): Matrix of predictors for training set.
        test_df_X (DataFrame): Matrix of predictors for test set.
        train_df_y (DataFrame): Target forecast y for training set.
        forecast_horizon (int): Forecast horizon in minutes.

    Returns:
        train_df_y_hat (DataFrame): Forecasted values for training set.
        test_df_y_hat (DataFrame): Forecasted values for test set.
    """

    if model_name == 'm1_naive':
        train_df_y_hat, test_df_y_hat = produce_forecast_m1_naive(model, train_df_X, test_df_X)
    elif model_name == 'm2_snaive':
        train_df_y_hat, test_df_y_hat = produce_forecast_m2_snaive(model, train_df_X, test_df_X)
    elif model_name == 'm3_ets':
        train_df_y_hat, test_df_y_hat = produce_forecast_m3_ets(model, train_df_X, test_df_X, forecast_horizon)
    elif model_name == 'm4_arima':
        train_df_y_hat, test_df_y_hat = produce_forecast_m4_arima(model, train_df_X, test_df_X, forecast_horizon)
    elif model_name == 'm5_sarima':
        train_df_y_hat, test_df_y_hat = produce_forecast_m5_sarima(model, train_df_X, test_df_X, forecast_horizon)
    elif model_name == 'm6_lr':
        train_df_y_hat, test_df_y_hat = produce_forecast_m6_lr(model, train_df_X, test_df_X)
    elif model_name == 'm7_ann':
        train_df_y_hat, test_df_y_hat = produce_forecast_m7_ann(model, train_df_X, test_df_X)
    elif model_name == 'm8_dnn':
        train_df_y_hat, test_df_y_hat = produce_forecast_m8_dnn(model, train_df_X, test_df_X)
    elif model_name == 'm9_rt':
        train_df_y_hat, test_df_y_hat = produce_forecast_m9_rt(model, train_df_X, test_df_X)
    elif model_name == 'm10_rf':
        train_df_y_hat, test_df_y_hat = produce_forecast_m10_rf(model, train_df_X, test_df_X)
    elif model_name == 'm11_svr':
        train_df_y_hat, test_df_y_hat = produce_forecast_m11_svr(model, train_df_X, test_df_X)
    elif model_name == 'm12_rnn':
        train_df_y_hat, test_df_y_hat = produce_forecast_m12_rnn(model, train_df_X, test_df_X)
    elif model_name == 'm13_lstm':
        train_df_y_hat, test_df_y_hat = produce_forecast_m13_lstm(model, train_df_X, test_df_X)
    elif model_name == 'm14_gru':
        train_df_y_hat, test_df_y_hat = produce_forecast_m14_gru(model, train_df_X, test_df_X)
    elif model_name == 'm15_transformer':
        train_df_y_hat, test_df_y_hat = produce_forecast_m15_transformer(model, train_df_X, test_df_X)
    elif model_name == 'm16_prophet':
        train_df_y_hat, test_df_y_hat = produce_forecast_m16_prophet(model, train_df_X, test_df_X, train_df_y, forecast_horizon)
    elif model_name == 'm17_xgb':
        train_df_y_hat, test_df_y_hat = produce_forecast_m17_xgb(model, train_df_X, test_df_X)
    elif model_name == 'm18_nbeats':
        train_df_y_hat, test_df_y_hat = produce_forecast_m18_nbeats(model, train_df_X, test_df_X)
    else:
        raise ValueError(
            "Wrong Model Choice! Available models are: m1_naive, m2_snaive, m3_ets, m4_arima, m5_sarima, m6_lr, m7_ann, m8_dnn, m9_rt, m10_rf, m11_svr, m12_rnn, m13_lstm, m14_gru, m15_transformer, m16_prophet, m17_xgb, m18_nbeats"
        )

    return train_df_y_hat, test_df_y_hat

remove_jump_df(train_df_y)

Remove jump in the time series data Parameters: train_df_y (pd.Series): Time series data

Returns:

Name Type Description
train_df_y_updated Series

Time series data with jump removed

Source code in docs\notebooks\config\general_functions.py
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
def remove_jump_df(train_df_y):
    #make docstring with the same format like other cells
    """
    Remove jump in the time series data
    Parameters:
        train_df_y (pd.Series): Time series data

    Returns:
        train_df_y_updated (pd.Series): Time series data with jump removed
    """

    time_diff = train_df_y.index.to_series().diff().dt.total_seconds()
    initial_freq = time_diff.iloc[1]
    jump_indices = time_diff[time_diff > initial_freq].index
    if not jump_indices.empty:
        jump_index = jump_indices[0]
        jump_pos = train_df_y.index.get_loc(jump_index)
        train_df_y_updated = train_df_y.iloc[:jump_pos]
    else:
        train_df_y_updated = train_df_y
    return train_df_y_updated

run_experiment(dataset, forecast_horizon, model_name, hyperparameter_no)

Run the experiment with the specified parameters. Args: dataset (str): Name of the dataset file. forecast_horizon (int): Forecast horizon in minutes. model_name (str): Model identifier (e.g., 'm6_lr'). hyperparameter_no (str): Hyperparameter set identifier.

Returns:

Type Description

None. Results and models are saved to disk.

Source code in docs\notebooks\config\general_functions.py
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
def run_experiment(dataset, forecast_horizon, model_name, hyperparameter_no):
    '''
    Run the experiment with the specified parameters.
    Args:
        dataset (str): Name of the dataset file.
        forecast_horizon (int): Forecast horizon in minutes.
        model_name (str): Model identifier (e.g., 'm6_lr').
        hyperparameter_no (str): Hyperparameter set identifier.

    Returns:
        None. Results and models are saved to disk.
    '''
    # PREPARE FOLDER
    hyperparameter, experiment_no_str, filepath = prepare_directory(path_result, forecast_horizon, model_name, hyperparameter_no)
    # INPUT DATA
    block_length, holdout_df, df = input_and_process(path_data_cleaned, forecast_horizon, max_lag_day, n_block, hyperparameter)
    # RUN MODEL
    run_model(df, model_name, hyperparameter, filepath, forecast_horizon, experiment_no_str, block_length)

run_model(df, model_name, hyperparameter, filepath, forecast_horizon, experiment_no_str, block_length)

Run model! This will be updated so that it can adapt to any model This consists of 1. Loop over all CV, then inside the loop, 2. Split df to train and test set 3. Train model on train set 4. Produce naive forecast on both trian and test set for benchmark 5. Produce forecast on both train and test set 6. Compute residual on both train and test set 7. Export the observation, naive, forecast, and residual of train and test set 8. Produce plots only on CV 1 9. Evaluate forecast performance on train and test set 10. Quit the loop, 11. Summarise the overall performance of the model using RMSE and its Stddev 12. Export all results

Parameters:

Name Type Description Default
df df

df that will be used for training and validation (test) set, consists of X and Y

required
model_name string

eg 'm06_lr'

required
hyperparameter pd series

list of hyperparameter for that model

required
filepath dict

dictionary of filepaths for exporting result

required
forecast_horizon

the forecast horizon

required
experiment_no

the experiment no. in string

required
block_length

block length of one cross validation set

required
Source code in docs\notebooks\config\general_functions.py
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
def run_model(df, model_name, hyperparameter, filepath, forecast_horizon, experiment_no_str, block_length):
    """Run model! This will be updated so that it can adapt to any model 
    This consists of
    1. Loop over all CV, then inside the loop,
    2. Split df to train and test set
    3. Train model on train set
    4. Produce naive forecast on both trian and test set for benchmark
    5. Produce forecast on both train and test set
    6. Compute residual on both train and test set
    7. Export the observation, naive, forecast, and residual of train and test set
    8. Produce plots only on CV 1
    9. Evaluate forecast performance on train and test set
    10. Quit the loop,
    11. Summarise the overall performance of the model using RMSE and its Stddev
    12. Export all results     

    Args:
        df (df): df that will be used for training and validation (test) set, consists of X and Y
        model_name (string): eg 'm06_lr'
        hyperparameter (pd series): list of hyperparameter for that model
        filepath (dict): dictionary of filepaths for exporting result
        forecast_horizon : the forecast horizon
        experiment_no : the experiment no. in string
        block_length : block length of one cross validation set


    """

    import warnings
    warnings.filterwarnings("ignore", category=RuntimeWarning)

    cross_val_result_df = pd.DataFrame()

    # Compute max_y for normalization later
    max_y = df['y'].max()

    # DO CROSS VALIDATION
    for cv_no in range(1, k+1):
        print(f'Processing CV {cv_no} / {k}....')

        # SPLIT INTO TRAIN AND TEST X AND Y
        train_df, test_df = split_time_series(df, cv_no)
        train_df_X, train_df_y = split_xy(train_df)
        test_df_X, test_df_y = split_xy(test_df)

        # INITIALISE RESULT DF   
        train_result = train_df_y.copy()
        train_result = train_result.rename(columns={'y': 'observation'})

        test_result = test_df_y.copy()
        test_result = test_result.rename(columns={'y': 'observation'})

        # PRODUCE NAIVE FORECAST
        horizon_timedelta = pd.Timedelta(minutes=forecast_horizon)
        last_observation = f'y_lag_{horizon_timedelta}m'
        train_result['naive'] = train_df[last_observation]
        test_result['naive'] = test_df[last_observation]

        # TRAIN MODEL
        start_time = time.time()
        model = train_model(model_name, hyperparameter, train_df_X, train_df_y, forecast_horizon)
        save_model(filepath, cv_no, model)
        end_time = time.time()
        runtime_ms = (end_time - start_time) * 1000  # Convert to milliseconds

        # PRODUCE FORECAST
        train_df_y_hat, test_df_y_hat = produce_forecast(model_name, model, train_df_X, test_df_X, train_df_y, forecast_horizon)
        train_result['forecast'] = train_df_y_hat
        test_result['forecast'] = test_df_y_hat

        # EVALUATE FORECAST
        train_result['residual'] = train_result['forecast'] - train_result['observation']
        test_result['residual'] = test_result['forecast'] - test_result['observation']
        train_R2 = compute_R2(train_result['forecast'], train_result['observation'])
        test_R2 = compute_R2(test_result['forecast'], test_result['observation'])

        train_RMSE = compute_RMSE(train_result['forecast'], train_result['observation'])
        test_RMSE = compute_RMSE(test_result['forecast'], test_result['observation'])

        train_nRMSE = 100*train_RMSE / max_y # in percent
        test_nRMSE = 100*test_RMSE / max_y # in percent

        cross_val_result = pd.DataFrame(
        {
            "runtime_ms": runtime_ms,
            "train_MBE": compute_MBE(train_result['forecast'], train_result['observation']), 
            "train_MAE": compute_MAE(train_result['forecast'], train_result['observation']),
            "train_RMSE": train_RMSE,
            "train_MAPE": compute_MAPE(train_result['forecast'], train_result['observation']),
            "train_MASE": compute_MASE(train_result['forecast'], train_result['observation'], train_result),
            "train_fskill": compute_fskill(train_result['forecast'], train_result['observation'], train_result['naive']),
            "train_R2": train_R2,
            "test_MBE": compute_MBE(test_result['forecast'], test_result['observation']),
            "test_MAE": compute_MAE(test_result['forecast'], test_result['observation']),
            "test_RMSE": test_RMSE,
            "test_MAPE": compute_MAPE(test_result['forecast'], test_result['observation']),
            "test_MASE": compute_MASE(test_result['forecast'], test_result['observation'], train_result),
            "test_fskill": compute_fskill(test_result['forecast'], test_result['observation'], test_result['naive']),
            "test_R2": test_R2,
            "train_nRMSE": train_nRMSE,
            "test_nRMSE": test_nRMSE
        }, 
        index=[cv_no]
        )

        if cross_val_result_df.empty:
            cross_val_result_df = cross_val_result
        else:
            cross_val_result_df = pd.concat([cross_val_result_df, cross_val_result], ignore_index=False)
        cross_val_result_df.index.name = 'cv_no'

        # EXPORT RESULTS DF TO CSV
        train_result.to_csv(filepath['train_cv'][cv_no])
        test_result.to_csv(filepath['test_cv'][cv_no])

        # IF CV_NO = 1, ALSO EXPORT SOME PLOTS
        if cv_no == 1:
            timeplot_forecast(train_result['observation'], train_result['forecast'], filepath['b1'])
            timeplot_forecast(test_result['observation'], test_result['forecast'], filepath['c1'])
            scatterplot_forecast(train_result['observation'], train_result['forecast'], train_R2, filepath['b2'])
            scatterplot_forecast(test_result['observation'], test_result['forecast'], test_R2, filepath['c2'])
            timeplot_residual(train_result['residual'], filepath['b3'])
            timeplot_residual(test_result['residual'], filepath['c3'])
            histogram_residual(train_result['residual'], df, filepath['b4'])
            histogram_residual(test_result['residual'], df, filepath['c4'])
        print()


    cross_val_result = pd.DataFrame(
        {
            "runtime_ms": [cross_val_result_df['runtime_ms'].mean(), cross_val_result_df['runtime_ms'].std()],
            "train_MBE": [cross_val_result_df['train_MBE'].mean(), cross_val_result_df['train_MBE'].std()], 
            "train_MAE": [cross_val_result_df['train_MAE'].mean(), cross_val_result_df['train_MAE'].std()],
            "train_RMSE": [cross_val_result_df['train_RMSE'].mean(), cross_val_result_df['train_RMSE'].std()],
            "train_MAPE": [cross_val_result_df['train_MAPE'].mean(), cross_val_result_df['train_MAPE'].std()],
            "train_MASE": [cross_val_result_df['train_MASE'].mean(), cross_val_result_df['train_MASE'].std()],
            "train_fskill": [cross_val_result_df['train_fskill'].mean(), cross_val_result_df['train_fskill'].std()],
            "train_R2": [cross_val_result_df['train_R2'].mean(), cross_val_result_df['train_R2'].std()],
            "test_MBE": [cross_val_result_df['test_MBE'].mean(), cross_val_result_df['test_MBE'].std()],
            "test_MAE": [cross_val_result_df['test_MAE'].mean(), cross_val_result_df['test_MAE'].std()],
            "test_RMSE": [cross_val_result_df['test_RMSE'].mean(), cross_val_result_df['test_RMSE'].std()],
            "test_MAPE": [cross_val_result_df['test_MAPE'].mean(), cross_val_result_df['test_MAPE'].std()],
            "test_MASE": [cross_val_result_df['test_MASE'].mean(), cross_val_result_df['test_MASE'].std()],
            "test_fskill": [cross_val_result_df['test_fskill'].mean(), cross_val_result_df['test_fskill'].std()],
            "test_R2": [cross_val_result_df['test_R2'].mean(), cross_val_result_df['test_R2'].std()],
            "train_nRMSE": [cross_val_result_df['train_nRMSE'].mean(), cross_val_result_df['train_nRMSE'].std()],
            "test_nRMSE": [cross_val_result_df['test_nRMSE'].mean(), cross_val_result_df['test_nRMSE'].std()]
        }, 
        index=['mean', 'stddev']
        )

    cross_val_result_df = pd.concat([cross_val_result_df, cross_val_result], ignore_index=False)

    data_a1 = {
        "experiment_no": experiment_no_str,
        "exp_date": datetime.today().strftime('%Y-%m-%d'), #today date in YYYY-MM-DD format
        "dataset_no": dataset.split('_')[0],
        "dataset": dataset.split('_')[1].split('.')[0],
        "dataset_freq_min": int((df.index[1] - df.index[0]).total_seconds() / 60),
        "dataset_length_week": block_length * (n_block - 1),
        "forecast_horizon_min": forecast_horizon,
        "train_pct": train_pct,
        "test_pct": test_pct,
        "model_no": model_name.split('_')[0],
        "hyperparameter_no": hyperparameter_no,
        "model_name": model_name + '_' + hyperparameter_no,
        "hyperparamter": ', '.join(f"{k}: {v}" for k, v in hyperparameter.items()),  
        "runtime_ms": cross_val_result_df.loc['mean', 'runtime_ms'],
        "train_RMSE": cross_val_result_df.loc['mean', 'train_RMSE'],
        "train_RMSE_stddev": cross_val_result_df.loc['stddev', 'train_RMSE'],
        "test_RMSE": cross_val_result_df.loc['mean', 'test_RMSE'],
        "test_RMSE_stddev": cross_val_result_df.loc['stddev', 'test_RMSE'],
        "train_nRMSE": cross_val_result_df.loc['mean', 'train_nRMSE'],
        "train_nRMSE_stddev": cross_val_result_df.loc['stddev', 'train_nRMSE'],
        "test_nRMSE": cross_val_result_df.loc['mean', 'test_nRMSE'],
        "test_nRMSE_stddev": cross_val_result_df.loc['stddev', 'test_nRMSE']
    }

    # Create a df of experiment result
    df_a1_result = pd.DataFrame([data_a1])

    export_result(filepath, df_a1_result, cross_val_result_df, hyperparameter)

save_model(filepath, cv_no, model)

Export model into binary file using pickle to a designated file

Parameters:

Name Type Description Default
filepath dictionary

dictionary of the file path

required
cv_no (int)

cv number

required
model dictionary

trained model

required
Source code in docs\notebooks\config\general_functions.py
588
589
590
591
592
593
594
595
596
597
598
599
def save_model(filepath, cv_no, model):
    """Export model into binary file using pickle to a designated file

    Args:
        filepath (dictionary): dictionary of the file path
        cv_no (int) : cv number
        model (dictionary): trained model
    """

    with open(filepath['model'][cv_no], "wb") as model_file:
        # pickle.dump(model, model_file)
        dill.dump(model, model_file)

scatterplot_forecast(observation, forecast, R2, pathname)

Produce scatterplot observation vs forecast value and save it on the designated folder

Parameters:

Name Type Description Default
observation df

observed value

required
forecast df

forecast value

required
pathname str

filepath to save the figure

required
Source code in docs\notebooks\config\general_functions.py
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
def scatterplot_forecast(observation, forecast, R2, pathname):
    """Produce scatterplot observation vs forecast value and save it on the designated folder

    Args:
        observation (df): observed value
        forecast (df): forecast value
        pathname (str): filepath to save the figure
    """
    # Create the figure with specified size
    plt.figure(figsize=(9, 9))

    # Set background color
    # plt.gcf().patch.set_facecolor(platinum)

    # Plot the actual and forecast data
    plt.scatter(forecast, observation, color=dark_blue, label='Actual', s=40, alpha=0.7)  # 's' sets the size of the points


    # Set the font to Arial
    plt.rcParams['font.family'] = 'Arial'

    # Remove grid lines
    plt.grid(False)

    # Set tick marks for x and y axis
    plt.xticks(fontsize=12, color=dark_blue, alpha=0.5, rotation=0)
    plt.yticks(fontsize=12, color=dark_blue, alpha=0.5)

    # Add borders to the plot
    plt.gca().spines['top'].set_color(dark_blue)
    plt.gca().spines['right'].set_color(dark_blue)
    plt.gca().spines['bottom'].set_color(dark_blue)
    plt.gca().spines['left'].set_color(dark_blue)

    # Remove the tick markers (the small lines)
    plt.tick_params(axis='x', which='both', length=0)  # Remove x-axis tick markers
    plt.tick_params(axis='y', which='both', length=0)  # Remove y-axis tick markers

    # Set axis titles
    plt.xlabel('Net Load Forecast (kW)', fontsize=14, color=dark_blue)
    plt.ylabel('Net Load Observation (kW)', fontsize=14, color=dark_blue)

    # Remove title
    plt.title('')

    # Add R² value at the top-left corner
    plt.text(0.95, 0.05, f'R² = {R2:.3f}', transform=plt.gca().transAxes, 
         fontsize=14, color=dark_blue, verticalalignment='bottom', horizontalalignment='right',
         bbox=dict(facecolor='white', edgecolor=dark_blue, boxstyle='round,pad=0.5', linewidth=1))


    plt.savefig(pathname, format='png', bbox_inches='tight')
    plt.close()

separate_holdout(df, n_block)

Separating df into two parts: 1. df : df that will be used for training and blocked k-fold cross validation. The block is a multiple of a week because net load data has weekly seasonality 2. hold_out_df : this section is not used for now, but can be useful for final test of the chosen model if wanted, to show the generalized error. This is at least 1 block of data.

By default, the chosen k for k-fold cross validation is 10.

For example, the original df has 12 weeks worth of data. In this case, new df is week 1-10, hold_out_df is week 11-12,

the new df will be used for cross validation, for example CV1: training: week 1-9, validation (test) week 10 CV2: training: week 1-8, week 10, validation (test) week 9, etc.

Parameters:

Name Type Description Default
df df

cleaned df consisting of y and all predictors

required
n_block int

number of blocks to divide the original df. This includes the block for hold_out_df, so if k=10, this n_block = k+1 = 11

required

Returns:

Type Description

block_length (int) : number of weeks per block

hodout_df (df) : unused df, can be used later for unbiased estimate of final model performance

df (df) : df that will be used for training and validation (test) set

Source code in docs\notebooks\config\general_functions.py
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
def separate_holdout(df, n_block):    
    """Separating df into two parts:
    1. df : df that will be used for training and blocked k-fold cross validation. 
    The block is a multiple of a week because net load data has weekly seasonality
    2. hold_out_df : this section is not used for now, but can be useful for final test of the chosen model
    if wanted, to show the generalized error. This is at least 1 block of data. 

    By default, the chosen k for k-fold cross validation is 10.

    For example, the original df has 12 weeks worth of data. 
    In this case,
    new df is week 1-10,
    hold_out_df is week 11-12,

    the new df will be used for cross validation, for example
    CV1: training: week 1-9, validation (test) week 10
    CV2: training: week 1-8, week 10, validation (test) week 9,
    etc. 

    Args:
        df (df): cleaned df consisting of y and all predictors
        n_block (int): number of blocks to divide the original df. This includes the block for hold_out_df, so if k=10, this n_block = k+1 = 11

    Returns:
        block_length (int) : number of weeks per block
        hodout_df (df) : unused df, can be used later for unbiased estimate of final model performance
        df (df) : df that will be used for training and validation (test) set
    """

    dataset_length_week= ((df.index[-1] - df.index[0]).total_seconds() / 86400/7)
    block_length = int(dataset_length_week / n_block)
    consecutive_timedelta = df.index[1] - df.index[0]
    n_timestep_per_week = int(one_week / consecutive_timedelta)
    holdout_start = (n_block - 1)* block_length * n_timestep_per_week
    holdout_df = df.iloc[holdout_start:]
    df = df.drop(df.index[holdout_start:])

    return block_length, holdout_df, df

split_time_series(df, cv_no)

Split df to train and test set using blocked cross validation.

Parameters:

Name Type Description Default
df df

df that will be used for training and validation (test) set, consists of X and Y

required
cv_no int

number of current cv order. cv_no=1 means the test set is at the last, cv_no = k means the test set is at the beginning

required

Returns:

Type Description

train_df (df) : df used for training

test_df (df) : df used for validation, formal name is validation set / dev set.

Source code in docs\notebooks\config\general_functions.py
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
def split_time_series(df, cv_no):
    """Split df to train and test set using blocked cross validation.

    Args:
        df (df): df that will be used for training and validation (test) set, consists of X and Y
        cv_no (int): number of current cv order. 
                     cv_no=1 means the test set is at the last, cv_no = k means the test set is at the beginning

    Returns:
        train_df (df) : df used for training
        test_df (df) : df used for validation, formal name is validation set / dev set. 
    """

    n = len(df)
    test_start = int(n*(1 - cv_no*test_pct))
    test_end = int(n*(1 - (cv_no-1)*test_pct))

    test_df = df.iloc[test_start:test_end]
    train_df = df.drop(df.index[test_start:test_end])

    return train_df, test_df

split_xy(df)

separate forecast target y and all predictors X into two dfs

Parameters:

Name Type Description Default
df df

df containing the forecast target y and all predictors X

required
Return

df_X (df): df of all predictors X df_y (df): df of target forecast y

Source code in docs\notebooks\config\general_functions.py
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
def split_xy(df):
    """separate forecast target y and all predictors X into two dfs

    Args:
        df (df): df containing the forecast target y and all predictors X

    Return:
        df_X (df): df of all predictors X
        df_y (df): df of target forecast y
    """

    df_y = df[['y']]
    df_X = df.drop("y", axis=1)

    return df_X, df_y

timeplot_forecast(observation, forecast, pathname)

Produce time plot of observation vs forecast value and save it on the designated folder

Parameters:

Name Type Description Default
observation df

observed value

required
forecast df

forecast value

required
pathname str

filepath to save the figure

required
Source code in docs\notebooks\config\general_functions.py
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
def timeplot_forecast(observation, forecast, pathname):
    """Produce time plot of observation vs forecast value and save it on the designated folder

    Args:
        observation (df): observed value
        forecast (df): forecast value
        pathname (str): filepath to save the figure
    """
    consecutive_timedelta = observation.index[-1] - observation.index[-2]
    # Calculate total minutes in a week
    minutes_per_week = 7 * 24 * 60  # 7 days * 24 hours * 60 minutes

    # Calculate the number of minutes per timestep
    minutes_per_timestep = consecutive_timedelta.total_seconds() / 60  # convert seconds to minutes

    # Compute the number of timesteps in a week
    timesteps_per_week = int(minutes_per_week / minutes_per_timestep)

    # Create the figure with specified size
    plt.figure(figsize=(9, 9))

    # Set background color
    # plt.gcf().patch.set_facecolor(platinum)

    # Plot the actual and forecast data
    plt.plot(observation[-timesteps_per_week:], color=dark_blue, label='Actual')
    plt.plot(forecast[-timesteps_per_week:], color=orange, label='Forecast')

    # Set the font to Arial
    plt.rcParams['font.family'] = 'Arial'

    # Remove grid lines
    plt.grid(False)

    # Set tick marks for x and y axis
    plt.xticks(fontsize=12, color=dark_blue, alpha=0.5, rotation=30)
    plt.yticks(fontsize=12, color=dark_blue, alpha=0.5)

    # Add borders to the plot
    plt.gca().spines['top'].set_color(dark_blue)
    plt.gca().spines['right'].set_color(dark_blue)
    plt.gca().spines['bottom'].set_color(dark_blue)
    plt.gca().spines['left'].set_color(dark_blue)

    # Remove the tick markers (the small lines)
    plt.tick_params(axis='x', which='both', length=0)  # Remove x-axis tick markers
    plt.tick_params(axis='y', which='both', length=0)  # Remove y-axis tick markers

    # Set axis titles
    plt.xlabel('Time', fontsize=14, color=dark_blue)
    plt.ylabel('Net Load (kW)', fontsize=14, color=dark_blue)

    # Remove title
    plt.title('')

    plt.legend(loc='upper left', fontsize=12, frameon=False, labelspacing=1, bbox_to_anchor=(1, 1))

    plt.savefig(pathname, format='png', bbox_inches='tight')
    plt.close()

timeplot_residual(residual, pathname)

Produce time plot of resodia; value and save it on the designated folder

Parameters:

Name Type Description Default
residual df

forecast - observation

required
pathname str

filepath to save the figure

required
Source code in docs\notebooks\config\general_functions.py
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
def timeplot_residual(residual, pathname):
    """Produce time plot of resodia; value and save it on the designated folder

    Args:
        residual (df): forecast - observation
        pathname (str): filepath to save the figure
    """
    consecutive_timedelta = residual.index[-1] - residual.index[-2]
    # Calculate total minutes in a week
    minutes_per_week = 7 * 24 * 60  # 7 days * 24 hours * 60 minutes

    # Calculate the number of minutes per timestep
    minutes_per_timestep = consecutive_timedelta.total_seconds() / 60  # convert seconds to minutes

    # Compute the number of timesteps in a week
    timesteps_per_week = int(minutes_per_week / minutes_per_timestep)

    # Create the figure with specified size
    plt.figure(figsize=(9, 9))

    # Set background color
    # plt.gcf().patch.set_facecolor(platinum)

    # Plot the actual and forecast data
    plt.plot(residual[-timesteps_per_week:], color=dark_blue, label='Actual')

    # Set the font to Arial
    plt.rcParams['font.family'] = 'Arial'

    # Remove grid lines
    plt.grid(False)

    # Set tick marks for x and y axis
    plt.xticks(fontsize=12, color=dark_blue, alpha=0.5, rotation=30)
    plt.yticks(fontsize=12, color=dark_blue, alpha=0.5)

    # Add borders to the plot
    plt.gca().spines['top'].set_color(dark_blue)
    plt.gca().spines['right'].set_color(dark_blue)
    plt.gca().spines['bottom'].set_color(dark_blue)
    plt.gca().spines['left'].set_color(dark_blue)

    # Remove the tick markers (the small lines)
    plt.tick_params(axis='x', which='both', length=0)  # Remove x-axis tick markers
    plt.tick_params(axis='y', which='both', length=0)  # Remove y-axis tick markers

    # Set axis titles
    plt.xlabel('Time', fontsize=14, color=dark_blue)
    plt.ylabel('Forecast Residual (kW)', fontsize=14, color=dark_blue)

    # Remove title
    plt.title('')

    plt.savefig(pathname, format='png', bbox_inches='tight')
    plt.close()

train_model(model_name, hyperparameter, train_df_X, train_df_y, forecast_horizon)

train model based on the model choice, hyperparamter, predictors X and target y

Parameters:

Name Type Description Default
model_name string

eg 'm06_lr'

required
hyperparameter pd series

list of hyperparameter for that model

required
train_df_X df

matrix of predictors

required
train_df_y df

target forecast y

required
forecast_horizon int

Forecast horizon in minutes.

required

Returns:

Type Description

model (dict) : general object storing all models info including the predictor, feature selection,

and all other relevant features

Source code in docs\notebooks\config\general_functions.py
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
def train_model(model_name, hyperparameter, train_df_X, train_df_y, forecast_horizon):
    """train model based on the model choice, hyperparamter, predictors X and target y

    Args:
        model_name (string): eg 'm06_lr'
        hyperparameter (pd series): list of hyperparameter for that model
        train_df_X (df): matrix of predictors
        train_df_y (df): target forecast y
        forecast_horizon (int): Forecast horizon in minutes.

    Returns:
        model (dict) : general object storing all models info including the predictor, feature selection, 
        and all other relevant features
    """

    if model_name == 'm1_naive':
        model = train_model_m1_naive(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm2_snaive':
        model = train_model_m2_snaive(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm3_ets':
        model = train_model_m3_ets(hyperparameter, train_df_X, train_df_y, forecast_horizon)
    elif model_name == 'm4_arima':
        model = train_model_m4_arima(hyperparameter, train_df_X, train_df_y, forecast_horizon)
    elif model_name == 'm5_sarima':
        model = train_model_m5_sarima(hyperparameter, train_df_X, train_df_y, forecast_horizon)
    elif model_name == 'm6_lr':
        model = train_model_m6_lr(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm7_ann':
        model = train_model_m7_ann(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm8_dnn':
        model = train_model_m8_dnn(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm9_rt':
        model = train_model_m9_rt(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm10_rf':
        model = train_model_m10_rf(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm11_svr':
        model = train_model_m11_svr(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm12_rnn':
        model = train_model_m12_rnn(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm13_lstm':
        model = train_model_m13_lstm(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm14_gru':
        model = train_model_m14_gru(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm15_transformer':
        model = train_model_m15_transformer(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm16_prophet':
        model = train_model_m16_prophet(hyperparameter, train_df_X, train_df_y, forecast_horizon)
    elif model_name == 'm17_xgb':
        model = train_model_m17_xgb(hyperparameter, train_df_X, train_df_y)
    elif model_name == 'm18_nbeats':
        model = train_model_m18_nbeats(hyperparameter, train_df_X, train_df_y)
    else:
        raise ValueError(
            "Wrong Model Choice! Available models are: m1_naive, m2_snaive, m3_ets, m4_arima, m5_sarima, m6_lr, m7_ann, m8_dnn, m9_rt, m10_rf, m11_svr, m12_rnn, m13_lstm, m14_gru, m15_transformer, m16_prophet, m17_xgb, m18_nbeats"
        )

    return model

Folder Model/

produce_forecast_m1_naive(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m1_naive.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def produce_forecast_m1_naive(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """

    # PRODUCE FORECAST
    horizon_timedelta = pd.Timedelta(minutes=forecast_horizon)
    last_observation = f'y_lag_{horizon_timedelta}m'
    train_df_y_hat = train_df_X[last_observation]
    test_df_y_hat = test_df_X[last_observation]

    return train_df_y_hat, test_df_y_hat

train_model_m1_naive(hyperparameter, train_df_X, train_df_y)

Train and test a naive model for point forecasting.

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m1_naive.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def train_model_m1_naive(hyperparameter, train_df_X, train_df_y):
    ''' Train and test a naive model for point forecasting. 

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training


    Returns:
        model (model) : trained model with all features
    '''

    #UNPACK HYPERPARAMETER
    #no hyperparameter for naive model

    #TRAIN MODEL
    #no training is required for naive model

    # PACK MODEL
    model = {}


    return model

produce_forecast_m2_snaive(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m2_snaive.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def produce_forecast_m2_snaive(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """

    # UNPACK MODEL
    col_name = model['col_name']  #this depends on the lag day

    # PRODUCE FORECAST
    train_df_y_hat = train_df_X[col_name]
    test_df_y_hat = test_df_X[col_name]

    return train_df_y_hat, test_df_y_hat

train_model_m2_snaive(hyperparameter, train_df_X, train_df_y)

Train and test a seasonal model for point forecasting.

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m2_snaive.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def train_model_m2_snaive(hyperparameter, train_df_X, train_df_y):
    ''' Train and test a seasonal model for point forecasting. 

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training


    Returns:
        model (model) : trained model with all features
    '''

    #UNPACK HYPERPARAMETER
    days = hyperparameter['days']
    col_name = f'y_lag_{days} days 00:00:00m'

    #TRAIN MODEL
    #no training is required for seasonal naive model

    # PACK MODEL
    model = {"col_name": col_name }


    return model

produce_forecast_m3_ets(model, train_df_X, test_df_X, forecast_horizon)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required
forecast_horizon int

forecast horizon in mins

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m3_ets.py
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def produce_forecast_m3_ets(model, train_df_X, test_df_X, forecast_horizon):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set
        forecast_horizon (int): forecast horizon in mins

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """

    timestep_frequency = test_df_X.index[1] - test_df_X.index[0]
    n_timestep_forecast_horizon = int(forecast_horizon / (timestep_frequency.total_seconds() / 60))


    train_df_X_updated = remove_jump_df(train_df_X)
    test_df_X_updated = remove_jump_df(test_df_X)

    # UNPACK MODEL
    model_fitted = model['model_fitted']

    # PRODUCE FORECAST FOR TRAIN SET
    train_df_y_hat = pd.DataFrame(model_fitted.fittedvalues)
    train_df_y_hat.columns = ['y']

    # train_df_y_hat_2 = pd.DataFrame(model_fitted.forecast(n_timestep_forecast_horizon-1))
    # train_df_y_hat_2.columns = ['y']
    # train_df_y_hat = pd.concat([train_df_y_hat, train_df_y_hat_2])

    train_df_y_hat.index.name = 'datetime'

    # TRANSFORM test_df_X to a series with only the last lag
    horizon_timedelta = pd.Timedelta(minutes=forecast_horizon)
    last_observation = f'y_lag_{horizon_timedelta}m'
    test_df_y_last = test_df_X[last_observation]


    # REFIT THE MODEL AND PRODUCE NEW FORECAST FOR TEST SET
    # THIS CODE RESULTS IN 2 MINS
    test_df_y_hat = pd.DataFrame(index = test_df_X.index)
    test_df_y_hat['y_hat'] = np.nan


    # in the case of CV 10, which is when test df < train df
    # don't compute the test forecast
    if (test_df_X.index[-1] < train_df_X.index[0]):
    # this is the case when we use CV10, where the test set is before the train set
        print("Test set is before train set / CV 10, no test forecast can be made")
        return train_df_y_hat, test_df_y_hat

    for i in range(len(test_df_y_last)):
    # for i in range(2): #for test only
        print('Processing i = ', i + 1, ' out of ', len(test_df_y_last)),
        if i == 0:
            test_df_y_hat.iloc[i, 0] = model_fitted.forecast(steps=n_timestep_forecast_horizon).iloc[-1]
        else:
            new_row = pd.DataFrame([test_df_y_last.values[i]], columns=['y'], index=[test_df_y_last.index[i] - dt.timedelta(minutes=forecast_horizon)])
            new_row = new_row.asfreq(test_df_X_updated.index.freq)

            model_fitted = model_fitted.append(new_row)
            test_df_y_hat.iloc[i, 0] = model_fitted.forecast(steps=n_timestep_forecast_horizon).iloc[-1] # to update based on the forecast horizon

    # test_df_y_hat = m06_lr.predict(test_df_X)

    return train_df_y_hat, test_df_y_hat

train_model_m3_ets(hyperparameter, train_df_X, train_df_y, forecast_horizon)

Train and test a linear model for point forecasting. https://www.statsmodels.org/dev/generated/statsmodels.tsa.statespace.exponential_smoothing.ExponentialSmoothing.html

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required
forecast_horizon (int)

forecast horizon in mins

required

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m3_ets.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def train_model_m3_ets(hyperparameter, train_df_X, train_df_y, forecast_horizon):
    ''' Train and test a linear model for point forecasting. 
    https://www.statsmodels.org/dev/generated/statsmodels.tsa.statespace.exponential_smoothing.ExponentialSmoothing.html

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training
        forecast_horizon (int) : forecast horizon in mins

    Returns:
        model (model) : trained model with all features
    '''

    #UNPACK HYPERPARAMETER
    trend = hyperparameter['trend']
    damped_trend = hyperparameter['damped_trend']
    seasonal_periods_days = hyperparameter['seasonal_periods_days']

    # UPDATE train_df_y to exclude all rows after a sudden jump in the timestep
    train_df_y_updated = remove_jump_df(train_df_y)

    # TRAIN MODEL
    # Calculate the frequency of the timesteps using the first and second index values
    timestep_frequency = train_df_y_updated.index[1] - train_df_y_updated.index[0]
    inferred_frequency = pd.infer_freq(train_df_y_updated.index)
    train_df_y_updated = train_df_y_updated.asfreq(inferred_frequency) 

    # INTRODUCE GAP BETWEEN TRAIN AND TEST SET TO AVOID DATA LEAKAGE
    n_timestep_forecast_horizon = int(forecast_horizon / (timestep_frequency.total_seconds() / 60))
    if n_timestep_forecast_horizon == 1:
        pass
    else:
        train_df_y_updated = train_df_y_updated[:-(n_timestep_forecast_horizon - 1)]

    # Assuming train_df_y_updated is your dataframe and 'y' is the column with the training series
    y = train_df_y_updated['y']

   # Build and fit the state-space Exponential Smoothing model
    model_fitted = ExponentialSmoothing(
        y,
        trend=trend,
        seasonal=None, #can be updated later
        damped_trend=damped_trend
    ).fit()


    # Print the model summary
    # print(model_fitted.summary())

    # PACK MODEL
    model = {"model_fitted": model_fitted}


    return model

produce_forecast_m4_arima(model, train_df_X, test_df_X, forecast_horizon)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required
forecast_horizon int

forecast horizon in mins

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m4_arima.py
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def produce_forecast_m4_arima(model, train_df_X, test_df_X, forecast_horizon):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set
        forecast_horizon (int): forecast horizon in mins

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """
    timestep_frequency = test_df_X.index[1] - test_df_X.index[0]
    n_timestep_forecast_horizon = int(forecast_horizon / (timestep_frequency.total_seconds() / 60))

    train_df_X_updated = remove_jump_df(train_df_X)
    test_df_X_updated = remove_jump_df(test_df_X)

    # UNPACK MODEL
    model_fitted = model['model_fitted']

    # PRODUCE FORECAST FOR TRAIN SET
    train_df_y_hat = pd.DataFrame(model_fitted.fittedvalues)
    train_df_y_hat.columns = ['y']

    # train_df_y_hat_2 = pd.DataFrame(model_fitted.forecast(n_timestep_forecast_horizon-1))
    # train_df_y_hat_2.columns = ['y']
    # train_df_y_hat = pd.concat([train_df_y_hat, train_df_y_hat_2])

    train_df_y_hat.index.name = 'datetime'

    # TRANSFORM test_df_X to a series with only the last lag
    horizon_timedelta = pd.Timedelta(minutes=forecast_horizon)
    last_observation = f'y_lag_{horizon_timedelta}m'
    test_df_y_last = test_df_X[last_observation]


    # REFIT THE MODEL AND PRODUCE NEW FORECAST FOR TEST SET
    # THIS CODE RESULTS IN 2 MINS
    test_df_y_hat = pd.DataFrame(index = test_df_X.index)
    test_df_y_hat['y_hat'] = np.nan

    # in the case of CV 10, which is when test df < train df
    # don't compute the test forecast
    if (test_df_X.index[-1] < train_df_X.index[0]):
    # this is the case when we use CV10, where the test set is before the train set
        print("Test set is before train set / CV 10, no test forecast can be made")
        return train_df_y_hat, test_df_y_hat

    for i in range(len(test_df_y_last)):
    # for i in range(2): #for test only
        print('Processing i = ', i + 1, ' out of ', len(test_df_y_last)),
        if i == 0:
            test_df_y_hat.iloc[i, 0] = model_fitted.forecast(steps=n_timestep_forecast_horizon).iloc[-1]
        else:
            new_row = pd.DataFrame([test_df_y_last.values[i]], columns=['y'], index=[test_df_y_last.index[i] - dt.timedelta(minutes=forecast_horizon)])
            new_row = new_row.asfreq(test_df_X_updated.index.freq)

            model_fitted = model_fitted.append(new_row)
            test_df_y_hat.iloc[i, 0] = model_fitted.forecast(steps=n_timestep_forecast_horizon).iloc[-1] # to update based on the forecast horizon


    # test_df_y_hat = m06_lr.predict(test_df_X)

    return train_df_y_hat, test_df_y_hat

train_model_m4_arima(hyperparameter, train_df_X, train_df_y, forecast_horizon)

Train and test a linear model for point forecasting.

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required
forecast_horizon (int)

forecast horizon in mins

required

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m4_arima.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def train_model_m4_arima(hyperparameter, train_df_X, train_df_y, forecast_horizon):
    ''' Train and test a linear model for point forecasting. 

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training
        forecast_horizon (int) : forecast horizon in mins


    Returns:
        model (model) : trained model with all features
    '''

    #UNPACK HYPERPARAMETER
    p = hyperparameter['p']
    d = hyperparameter['d']
    q = hyperparameter['q']


    # UPDATE train_df_y to exclude all rows after a sudden jump in the timestep
    train_df_y_updated = remove_jump_df(train_df_y)

    # TRAIN MODEL
    # Calculate the frequency of the timesteps using the first and second index values
    timestep_frequency = train_df_y_updated.index[1] - train_df_y_updated.index[0]
    inferred_frequency = pd.infer_freq(train_df_y_updated.index)
    train_df_y_updated = train_df_y_updated.asfreq(inferred_frequency)

    # INTRODUCE GAP BETWEEN TRAIN AND TEST SET TO AVOID DATA LEAKAGE
    n_timestep_forecast_horizon = int(forecast_horizon / (timestep_frequency.total_seconds() / 60))
    if n_timestep_forecast_horizon == 1:
        pass
    else:
        train_df_y_updated = train_df_y_updated[:-(n_timestep_forecast_horizon - 1)]

    # Assuming train_df_y_updated is your dataframe and 'y' is the column with the training series
    y = train_df_y_updated['y']

    # Build and fit the state-space ARIMA model
    model_fitted = ARIMA(y, order=(p, d, q), freq=inferred_frequency).fit()

    # PACK MODEL
    model = {"model_fitted": model_fitted}


    return model

produce_forecast_m5_sarima(model, train_df_X, test_df_X, forecast_horizon)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required
forecast_horizon int

forecast horizon in mins

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m5_sarima.py
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def produce_forecast_m5_sarima(model, train_df_X, test_df_X, forecast_horizon):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set
        forecast_horizon (int): forecast horizon in mins

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """
    timestep_frequency = test_df_X.index[1] - test_df_X.index[0]
    n_timestep_forecast_horizon = int(forecast_horizon / (timestep_frequency.total_seconds() / 60))

    train_df_X_updated = remove_jump_df(train_df_X)
    test_df_X_updated = remove_jump_df(test_df_X)

    # UNPACK MODEL
    model_fitted = model['model_fitted']

    # PRODUCE FORECAST FOR TRAIN SET
    train_df_y_hat = pd.DataFrame(model_fitted.fittedvalues)
    train_df_y_hat.columns = ['y']

    # train_df_y_hat_2 = pd.DataFrame(model_fitted.forecast(n_timestep_forecast_horizon-1))
    # train_df_y_hat_2.columns = ['y']
    # train_df_y_hat = pd.concat([train_df_y_hat, train_df_y_hat_2])

    train_df_y_hat.index.name = 'datetime'

    # TRANSFORM test_df_X to a series with only the last lag
    horizon_timedelta = pd.Timedelta(minutes=forecast_horizon)
    last_observation = f'y_lag_{horizon_timedelta}m'
    test_df_y_last = test_df_X[last_observation]


    # REFIT THE MODEL AND PRODUCE NEW FORECAST FOR TEST SET
    # THIS CODE RESULTS IN 2 MINS
    test_df_y_hat = pd.DataFrame(index = test_df_X.index)
    test_df_y_hat['y_hat'] = np.nan

    # in the case of CV 10, which is when test df < train df
    # don't compute the test forecast
    if (test_df_X.index[-1] < train_df_X.index[0]):
    # this is the case when we use CV10, where the test set is before the train set
        print("Test set is before train set / CV 10, no test forecast can be made")
        return train_df_y_hat, test_df_y_hat

    for i in range(len(test_df_y_last)):
    # for i in range(2): #for test only
        print('Processing i = ', i + 1, ' out of ', len(test_df_y_last)),
        if i == 0:
            test_df_y_hat.iloc[i, 0] = model_fitted.forecast(steps=n_timestep_forecast_horizon).iloc[-1]
        else:
            new_row = pd.DataFrame([test_df_y_last.values[i]], columns=['y'], index=[test_df_y_last.index[i] - dt.timedelta(minutes=forecast_horizon)])
            new_row = new_row.asfreq(test_df_X_updated.index.freq)

            model_fitted = model_fitted.append(new_row)
            test_df_y_hat.iloc[i, 0] = model_fitted.forecast(steps=n_timestep_forecast_horizon).iloc[-1] # to update based on the forecast horizon


    # test_df_y_hat = m06_lr.predict(test_df_X)

    return train_df_y_hat, test_df_y_hat

train_model_m5_sarima(hyperparameter, train_df_X, train_df_y, forecast_horizon)

Train and test a linear model for point forecasting.

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required
forecast_horizon (int)

forecast horizon in mins

required

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m5_sarima.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def train_model_m5_sarima(hyperparameter, train_df_X, train_df_y, forecast_horizon):
    ''' Train and test a linear model for point forecasting. 

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training
        forecast_horizon (int) : forecast horizon in mins


    Returns:
        model (model) : trained model with all features
    '''

    #UNPACK HYPERPARAMETER
    p = hyperparameter['p']
    d = hyperparameter['d']
    q = hyperparameter['q']
    P = hyperparameter['P']
    D = hyperparameter['D']
    Q = hyperparameter['Q']
    seasonal_period_days = hyperparameter['seasonal_period_days']


    # UPDATE train_df_y to exclude all rows after a sudden jump in the timestep
    train_df_y_updated = remove_jump_df(train_df_y)

    # TRAIN MODEL
    # Calculate the frequency of the timesteps using the first and second index values
    timestep_frequency = train_df_y_updated.index[1] - train_df_y_updated.index[0]
    s = int(seasonal_period_days * 24 * 60 / (timestep_frequency.seconds / 60))
    inferred_frequency = pd.infer_freq(train_df_y_updated.index)
    train_df_y_updated = train_df_y_updated.asfreq(inferred_frequency)

    # INTRODUCE GAP BETWEEN TRAIN AND TEST SET TO AVOID DATA LEAKAGE
    n_timestep_forecast_horizon = int(forecast_horizon / (timestep_frequency.total_seconds() / 60))
    if n_timestep_forecast_horizon == 1:
        pass
    else:
        train_df_y_updated = train_df_y_updated[:-(n_timestep_forecast_horizon - 1)]

    # Assuming train_df_y_updated is your dataframe and 'y' is the column with the training series
    y = train_df_y_updated['y']

    # Build and fit the state-space ARIMA model
    model_fitted = SARIMAX(y, order=(p, d, q), seasonal_order = (P, D, Q, s), freq=inferred_frequency).fit()

    # PACK MODEL
    model = {"model_fitted": model_fitted}


    return model

produce_forecast_m6_lr(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m6_lr.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def produce_forecast_m6_lr(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """
    fs_lr = model['feature_selector']
    m06_lr = model['regression_model']

    # SELECT K BEST FEATURES
    train_df_X = fs_lr.transform(train_df_X)
    test_df_X = fs_lr.transform(test_df_X)

    # PRODUCE FORECAST
    train_df_y_hat = m06_lr.predict(train_df_X)
    test_df_y_hat = m06_lr.predict(test_df_X)

    return train_df_y_hat, test_df_y_hat

train_model_m6_lr(hyperparameter, train_df_X, train_df_y)

Train and test a linear model for point forecasting.

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required

Returns:

Type Description

model (dictionary) : trained model with all features

Source code in docs\notebooks\model\m6_lr.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def train_model_m6_lr(hyperparameter, train_df_X, train_df_y):
    ''' Train and test a linear model for point forecasting. 

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training


    Returns:
        model (dictionary) : trained model with all features
    '''

    #UNPACK HYPERPARAMETER
    num_feature = int(hyperparameter['num_features'])

    # FEATURE SELECTOR
    def select_features(train_df_X, train_df_y, num_feature):
        ''' Make model to select K best feature. 

        Args:
            train_df_X (df) : features matrix for training
            train_df_y (df) : target matrix for training

        Returns:
            fs_lr (model) : feature selector
        '''

        train_df_y = train_df_y.values.ravel()
        fs_lr = SelectKBest(f_regression, k = num_feature)
        fs_lr.fit(train_df_X, train_df_y)

        return fs_lr

    fs_lr = select_features(train_df_X, train_df_y, num_feature)

    #TRAIN MODEL
    train_df_X = fs_lr.transform(train_df_X)
    m06_lr = LinearRegression()
    m06_lr.fit(train_df_X, train_df_y)

    # PACK MODEL
    model = {"feature_selector": fs_lr, "regression_model": m06_lr}    

    return model

produce_forecast_m7_ann(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m7_ann.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def produce_forecast_m7_ann(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """

    # UNPACK MODEL
    model_ann = model["model_ann"]

    # PREPARE FORMAT
    train_df_X_tensor = torch.tensor(train_df_X.values, dtype=torch.float32)
    test_df_X_tensor = torch.tensor(test_df_X.values, dtype=torch.float32)

    # PRODUCE FORECAST
    # Switch model to evaluation mode for inference
    model_ann.eval()

    # TRAIN SET FORECAST
    with torch.no_grad():  # Disable gradient calculation to save memory
        train_df_y_hat_tensor = model_ann(train_df_X_tensor)

    # TEST SET FORECAST
    with torch.no_grad():  # Disable gradient calculation to save memory
        test_df_y_hat_tensor = model_ann(test_df_X_tensor)

    # Create DataFrames of result
    train_df_y_hat = pd.DataFrame(train_df_y_hat_tensor, index=train_df_X.index, columns=['y_hat'])
    test_df_y_hat = pd.DataFrame(test_df_y_hat_tensor, index=test_df_X.index, columns=['y_hat'])

    return train_df_y_hat, test_df_y_hat

train_model_m7_ann(hyperparameter, train_df_X, train_df_y)

Train and test a linear model for point forecasting.

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m7_ann.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def train_model_m7_ann(hyperparameter, train_df_X, train_df_y):
    ''' Train and test a linear model for point forecasting. 

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training


    Returns:
        model (model) : trained model with all features
    '''

    #UNPACK HYPERPARAMETER

    # Set random seed for reproducibility
    def set_seed(seed):
        random.seed(seed)
        os.environ["PYTHONHASHSEED"] = str(seed)
        np.random.seed(seed)
        torch.manual_seed(seed)
        torch.cuda.manual_seed(seed)
        torch.backends.cudnn.deterministic = True

    seed = int(hyperparameter['seed'])

    hidden_size = hyperparameter['hidden_size']
    activation_function = hyperparameter['activation_function']
    learning_rate = hyperparameter['learning_rate']
    # learning_rate = 0.001
    solver = hyperparameter['solver']
    epochs = hyperparameter['epochs']

    # Use proper format for X and y
    X = torch.tensor(train_df_X.values, dtype=torch.float32)
    y = torch.tensor(train_df_y.values, dtype=torch.float32).view(-1, 1) 

    # Define the ANN model
    class ANNModel(nn.Module):
        def __init__(self, input_size, hidden_size, output_size):
            super(ANNModel, self).__init__()
            self.fc1 = nn.Linear(input_size, hidden_size)
            self.fc2 = nn.Linear(hidden_size, output_size)
            self.relu = nn.ReLU()  # Activation function

        def forward(self, x):
            x = self.fc1(x)
            if activation_function == 'relu':
                x = self.relu(x)
            elif activation_function == 'sigmoid':
                x = torch.sigmoid(x)
            else:
                x = torch.tanh(x)
            x = self.fc2(x)
            return x

    # Model initialization
    input_size = X.shape[1]
    output_size = y.shape[1]

    set_seed(seed)

    model_ann = ANNModel(input_size, hidden_size, output_size)
    if solver == 'adam':
        optimizer = optim.Adam(model_ann.parameters(), lr=learning_rate)
    elif solver == 'sgd':
        optimizer = optim.SGD(model_ann.parameters(), lr=learning_rate)
    else:
        raise ValueError('Solver not found')

    # Loss function
    criterion = nn.MSELoss()  # Mean Squared Error loss for regression

    #TRAIN MODEL
    # Training loop
    for epoch in range(epochs):
        model_ann.train()

        # Forward pass
        output = model_ann(X)
        loss = criterion(output, y)

        # Backward pass
        optimizer.zero_grad()
        loss.backward()

        # Update weights
        optimizer.step()

        if epoch % 10 == 0:
            print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')

    # PACK MODEL
    model = {"model_ann": model_ann}


    return model

produce_forecast_m8_dnn(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dict

all parameters of the trained model

required
train_df_X DataFrame

predictors of train set

required
test_df_X DataFrame

predictors of test set

required

Returns:

Type Description

train_df_y_hat (DataFrame) : forecast result at train set

test_df_y_hat (DataFrame) : forecast result at test set

Source code in docs\notebooks\model\m8_dnn.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
def produce_forecast_m8_dnn(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dict): all parameters of the trained model
        train_df_X (DataFrame): predictors of train set
        test_df_X (DataFrame): predictors of test set

    Returns:
        train_df_y_hat (DataFrame) : forecast result at train set
        test_df_y_hat (DataFrame) : forecast result at test set
    """

    # UNPACK MODEL
    model_dnn = model["model_dnn"]

    # PREPARE FORMAT
    train_df_X_tensor = torch.tensor(train_df_X.values, dtype=torch.float32)
    test_df_X_tensor = torch.tensor(test_df_X.values, dtype=torch.float32)

    # PRODUCE FORECAST
    # Switch model to evaluation mode for inference
    model_dnn.eval()

    # TRAIN SET FORECAST
    with torch.no_grad():  # Disable gradient calculation to save memory
        train_df_y_hat_tensor = model_dnn(train_df_X_tensor)

    # TEST SET FORECAST
    with torch.no_grad():  # Disable gradient calculation to save memory
        test_df_y_hat_tensor = model_dnn(test_df_X_tensor)

    # Create DataFrames of result
    train_df_y_hat = pd.DataFrame(train_df_y_hat_tensor.numpy(), index=train_df_X.index, columns=['y_hat'])
    test_df_y_hat = pd.DataFrame(test_df_y_hat_tensor.numpy(), index=test_df_X.index, columns=['y_hat'])

    return train_df_y_hat, test_df_y_hat

train_model_m8_dnn(hyperparameter, train_df_X, train_df_y)

Train and test a linear model for point forecasting.

Parameters:

Name Type Description Default
hyperparameter (dict)

hyperparameter value of the model consisting of number of features

required
train_df_X (DataFrame)

features matrix for training

required
train_df_y (DataFrame)

target matrix for training

required

Returns:

Type Description

model (dict) : trained model with all features

Source code in docs\notebooks\model\m8_dnn.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def train_model_m8_dnn(hyperparameter, train_df_X, train_df_y):
    ''' Train and test a linear model for point forecasting. 

    Args:
        hyperparameter (dict) : hyperparameter value of the model consisting of number of features
        train_df_X (DataFrame) : features matrix for training
        train_df_y (DataFrame) : target matrix for training

    Returns:
        model (dict) : trained model with all features
    '''

    # UNPACK HYPERPARAMETER
    seed = hyperparameter['seed']
    torch.manual_seed(seed)  # Set seed for PyTorch

    n_hidden = hyperparameter['n_hidden']
    hidden_size = hyperparameter['hidden_size']
    activation_function = hyperparameter['activation_function']
    learning_rate = hyperparameter['learning_rate']
    solver = hyperparameter['solver']
    epochs = hyperparameter['epochs']

    # Use proper format for X and y
    X = torch.tensor(train_df_X.values, dtype=torch.float32)
    y = torch.tensor(train_df_y.values, dtype=torch.float32).view(-1, 1) 

    # Define the DNN model
    class DNNModel(nn.Module):
        def __init__(self, input_size, hidden_size, output_size, n_hidden, activation_function):
            super(DNNModel, self).__init__()
            self.layers = nn.ModuleList()
            self.activation_function = activation_function

            # Input layer
            self.layers.append(nn.Linear(input_size, hidden_size))

            # Hidden layers
            for _ in range(n_hidden - 1):
                self.layers.append(nn.Linear(hidden_size, hidden_size))

            # Output layer
            self.layers.append(nn.Linear(hidden_size, output_size))

        def forward(self, x):
            for i, layer in enumerate(self.layers[:-1]):  # Iterate through hidden layers
                x = layer(x)
                if self.activation_function == 'relu':
                    x = nn.ReLU()(x)
                elif self.activation_function == 'sigmoid':
                    x = torch.sigmoid(x)
                elif self.activation_function == 'tanh':
                    x = torch.tanh(x)

            # Apply the output layer without activation function
            x = self.layers[-1](x)
            return x

    # Model initialization
    input_size = X.shape[1]
    output_size = y.shape[1]
    model_dnn = DNNModel(input_size, hidden_size, output_size, n_hidden, activation_function)

    if solver == 'adam':
        optimizer = optim.Adam(model_dnn.parameters(), lr=learning_rate)
    elif solver == 'sgd':
        optimizer = optim.SGD(model_dnn.parameters(), lr=learning_rate)
    else:
        raise ValueError('Solver not found')

    # Loss function
    criterion = nn.MSELoss()  # Mean Squared Error loss for regression

    # TRAIN MODEL
    # Training loop
    for epoch in range(epochs):
        model_dnn.train()

        # Forward pass
        output = model_dnn(X)
        loss = criterion(output, y)

        # Backward pass
        optimizer.zero_grad()
        loss.backward()

        # Update weights
        optimizer.step()

        if epoch % 10 == 0:
            print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')

    # PACK MODEL
    model = {"model_dnn": model_dnn}

    return model

produce_forecast_m9_rt(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m9_rt.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def produce_forecast_m9_rt(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """

    # UNPACK MODEL
    regressor = model['rt']

    # PRODUCE FORECAST
    train_df_y_hat = pd.DataFrame(regressor.predict(train_df_X), index = train_df_X.index, columns = ['y_hat'])
    test_df_y_hat = pd.DataFrame(regressor.predict(test_df_X), index = test_df_X.index, columns = ['y_hat'])

    # print('I am here after training the model')
    # print('train_df_y_hat', train_df_y_hat)
    # print('test_df_y_hat', test_df_y_hat)

    return train_df_y_hat, test_df_y_hat

train_model_m9_rt(hyperparameter, train_df_X, train_df_y)

Train and test a regression tree model for point forecasting.

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m9_rt.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def train_model_m9_rt(hyperparameter, train_df_X, train_df_y):
    ''' Train and test a regression tree model for point forecasting. 

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training


    Returns:
        model (model) : trained model with all features
    '''

    #UNPACK HYPERPARAMETER
    seed = hyperparameter['seed']
    max_depth = hyperparameter['max_depth']
    min_samples_split = hyperparameter['min_samples_split']
    min_samples_leaf = hyperparameter['min_samples_leaf']
    max_features = hyperparameter['max_features']

    #TRAIN MODEL
    # Initialize the regression tree model with important hyperparameters
    regressor = DecisionTreeRegressor(
        criterion='squared_error',
        max_depth=max_depth,
        min_samples_split = min_samples_split,
        min_samples_leaf = min_samples_leaf,
        max_features = max_features,
        random_state = seed
    )

    # Train the model
    regressor.fit(train_df_X, train_df_y)

    # PACK MODEL
    model = {"rt": regressor}

    # print('I am here after training the model')

    return model

produce_forecast_m10_rf(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m10_rf.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def produce_forecast_m10_rf(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """

    # UNPACK MODEL
    rf = model['rf']

    # PRODUCE FORECAST
    train_df_y_hat = pd.DataFrame(rf.predict(train_df_X), index = train_df_X.index, columns = ['y_hat'])
    test_df_y_hat = pd.DataFrame(rf.predict(test_df_X), index = test_df_X.index, columns = ['y_hat'])

    return train_df_y_hat, test_df_y_hat

train_model_m10_rf(hyperparameter, train_df_X, train_df_y)

Train and test a random forest model for point forecasting.

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m10_rf.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def train_model_m10_rf(hyperparameter, train_df_X, train_df_y):
    ''' Train and test a random forest model for point forecasting. 

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training


    Returns:
        model (model) : trained model with all features
    '''

    #UNPACK HYPERPARAMETER
    seed = int(hyperparameter['seed'])
    n_estimators = int(hyperparameter['n_estimators'])
    max_depth = int(hyperparameter['max_depth'])
    min_samples_split = int(hyperparameter['min_samples_split'])
    min_samples_leaf = int(hyperparameter['min_samples_leaf'])


    #TRAIN MODEL
    rf = RandomForestRegressor(
        n_estimators=n_estimators,       # number of trees
        max_depth=max_depth,           # maximum depth of a tree
        min_samples_split=min_samples_split,    # min samples to split a node
        min_samples_leaf=min_samples_leaf,     # min samples in a leaf
        random_state=seed
    )

    rf.fit(train_df_X, train_df_y) # fit the model to the training data

    # PACK MODEL
    model = {"rf": rf}


    return model

produce_forecast_m11_svr(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m11_svr.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def produce_forecast_m11_svr(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """

    svr = model['svr']
    train_df_y_hat = svr.predict(train_df_X)
    test_df_y_hat = svr.predict(test_df_X)

    return train_df_y_hat, test_df_y_hat

train_model_m11_svr(hyperparameter, train_df_X, train_df_y)

Train and test a linear model for point forecasting.

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m11_svr.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def train_model_m11_svr(hyperparameter, train_df_X, train_df_y):
    ''' Train and test a linear model for point forecasting. 

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training


    Returns:
        model (model) : trained model with all features
    '''

    from sklearn.svm import SVR

    #UNPACK HYPERPARAMETER
    seed = hyperparameter['seed'] #seem we can't use this using sklearn
    kernel = hyperparameter['kernel']
    C = hyperparameter['C']
    gamma = hyperparameter['gamma']
    epsilon = hyperparameter['epsilon']

    #TRAIN MODEL
    train_df_y = train_df_y.values.ravel()  # Flatten the target array if necessary
    svr = SVR(kernel=kernel, C=C, gamma=gamma, epsilon=epsilon)
    svr.fit(train_df_X, train_df_y)

    # PACK MODEL
    model = {"svr": svr}


    return model

produce_forecast_m12_rnn(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained RNN model

Source code in docs\notebooks\model\m12_rnn.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
def produce_forecast_m12_rnn(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained RNN model"""

    # UNPACK MODEL
    rnn = model['rnn']
    hyperparameter = model['hyperparameter']

    # UNPACK HYPERPARAMETER
    input_size = int(hyperparameter['input_size'])
    batch_size = int(hyperparameter['batch_size'])

    # PRODUCE FORECAST
    def produce_forecast(rnn, X):
        X_lags, X_exog = separate_lag_and_exogenous_features(X)
        X_lags_tensor = torch.tensor(X_lags.values, dtype=torch.float32)
        X_exog_tensor = torch.tensor(X_exog.values, dtype=torch.float32)

        total_lag_features = X_lags_tensor.shape[1]
        sequence_length = total_lag_features // input_size
        X_lags_tensor = X_lags_tensor.view(-1, sequence_length, input_size)

        predictions = []
        for i in range(0, len(X_lags_tensor), batch_size):
            batch_X_lags = X_lags_tensor[i:i+batch_size]
            batch_X_exog = X_exog_tensor[i:i+batch_size]

            with torch.no_grad():
                batch_pred = rnn(batch_X_lags, batch_X_exog)

            predictions.append(batch_pred)

        predictions = torch.cat(predictions, dim=0)
        return predictions.detach().numpy()

    train_df_y_hat = produce_forecast(rnn, train_df_X)
    test_df_y_hat = produce_forecast(rnn, test_df_X)

    return train_df_y_hat, test_df_y_hat

separate_lag_and_exogenous_features(train_df_X, target_column='y', lag_prefix='y_lag')

This function separates the lag features and exogenous variables from the training dataframe.

Parameters:

Name Type Description Default
train_df_X DataFrame

The dataframe containing both lag features and exogenous variables.

required
target_column str

The name of the target column (e.g., 'y').

'y'
lag_prefix str

The prefix used for lag columns (e.g., 'y_lag').

'y_lag'

Returns:

Name Type Description
X_lags DataFrame

DataFrame containing only the lag features.

X_exog DataFrame

DataFrame containing only the exogenous variables.

Source code in docs\notebooks\model\m12_rnn.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def separate_lag_and_exogenous_features(train_df_X, target_column='y', lag_prefix='y_lag'):
    '''
    This function separates the lag features and exogenous variables from the training dataframe.

    Args:
        train_df_X (pd.DataFrame): The dataframe containing both lag features and exogenous variables.
        target_column (str): The name of the target column (e.g., 'y').
        lag_prefix (str): The prefix used for lag columns (e.g., 'y_lag').

    Returns:
        X_lags (pd.DataFrame): DataFrame containing only the lag features.
        X_exog (pd.DataFrame): DataFrame containing only the exogenous variables.
    '''

    # Identify lag features (columns that start with 'y_lag')
    lag_features = [col for col in train_df_X.columns if col.startswith(lag_prefix)]

    # Identify exogenous variables (everything except the target and lag features)
    exog_features = [col for col in train_df_X.columns if col not in [target_column] + lag_features]

    # Create dataframes for lag features and exogenous features
    X_lags = train_df_X[lag_features]
    X_exog = train_df_X[exog_features]

    return X_lags, X_exog

train_model_m12_rnn(hyperparameter, train_df_X, train_df_y)

Train and test an RNN model for point forecasting. Essentially we use the RNN block to learn the temporal patterns of the time series, and then use a fully connected layer to learn the relationship between the lag features. We take the last hidden state of the RNN as the output, and concatenate it with the exogenous features (like calendar) to make the final prediction using a fully connected layer.

Source code in docs\notebooks\model\m12_rnn.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def train_model_m12_rnn(hyperparameter, train_df_X, train_df_y):
    ''' Train and test an RNN model for point forecasting. 
    Essentially we use the RNN block to learn the temporal patterns of the time series, 
    and then use a fully connected layer to learn the relationship between the lag features.
    We take the last hidden state of the RNN as the output, and concatenate it with the exogenous features 
    (like calendar) to make the final prediction using a fully connected layer.
    '''

    # UNPACK HYPERPARAMETER
    seed = int(hyperparameter['seed'])
    input_size = int(hyperparameter['input_size'])
    hidden_size = int(hyperparameter['hidden_size'])
    num_layers = int(hyperparameter['num_layers'])
    output_size = int(hyperparameter['output_size'])
    batch_size = int(hyperparameter['batch_size'])
    epochs = int(hyperparameter['epochs'])
    learning_rate = hyperparameter['learning_rate']

    # DEFINE MODEL AND TRAINING FUNCTION
    class RNNModel(nn.Module):
        def __init__(self, input_size, hidden_size, num_layers, exog_size, output_size=1):
            super(RNNModel, self).__init__()

            # Define the RNN layer
            self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)

            # Define the Fully Connected (FC) layer
            self.fc = nn.Linear(hidden_size + exog_size, output_size)

        def forward(self, x, exogenous_data):
            # Pass the input through the RNN
            out, h_n = self.rnn(x)

            # Get the last timestep hidden state
            last_hidden_state = out[:, -1, :]  # Shape: (batch_size, hidden_size)

            # Concatenate hidden state with exogenous vars
            combined_input = torch.cat((last_hidden_state, exogenous_data), dim=1)

            # Pass through FC
            out = self.fc(combined_input)
            return out

    def train_rnn_with_minibatches(model, train_loader, epochs, learning_rate=0.001):
        criterion = nn.MSELoss()
        optimizer = optim.Adam(model.parameters(), lr=learning_rate)

        for epoch in range(epochs):
            print(f'Epoch [{epoch+1}/{epochs}]')
            start_time = time.time()

            model.train()
            batch_no = 1
            for X_lags_batch, X_exog_batch, y_batch in train_loader:
                print(f'Epoch [{epoch+1}/{epochs}] and batch [{batch_no}/{len(train_loader)}]')
                batch_no += 1

                # Forward pass
                predictions = model(X_lags_batch, X_exog_batch)
                loss = criterion(predictions, y_batch)

                # Backward pass
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()

            end_time = time.time()
            epoch_time = end_time - start_time
            print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}, time taken: {epoch_time:.2f} seconds')

    def set_seed(seed=seed):
        random.seed(seed)
        np.random.seed(seed)
        torch.manual_seed(seed)
        os.environ["PYTHONHASHSEED"] = str(seed)

    # PREPARE TRAIN DATA
    X_lags, X_exog = separate_lag_and_exogenous_features(train_df_X)
    X_lags_tensor = torch.tensor(X_lags.values, dtype=torch.float32)
    X_exog_tensor = torch.tensor(X_exog.values, dtype=torch.float32)
    y_tensor = torch.tensor(train_df_y.values, dtype=torch.float32).view(-1, 1)

    total_lag_features = X_lags_tensor.shape[1]
    sequence_length = total_lag_features // input_size
    exog_size = X_exog_tensor.shape[1]

    # Reshape to 3D
    X_lags_tensor = X_lags_tensor.view(-1, sequence_length, input_size)

    # INITIALIZE MODEL + DATALOADER
    set_seed(seed=seed)
    rnn = RNNModel(input_size, hidden_size, num_layers, exog_size, output_size)
    train_data = TensorDataset(X_lags_tensor, X_exog_tensor, y_tensor)
    train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True)

    # TRAIN MODEL
    train_rnn_with_minibatches(rnn, train_loader, epochs=epochs, learning_rate=learning_rate)

    # PACK MODEL
    model = {"rnn": rnn, 'hyperparameter': hyperparameter, "train_df_X": train_df_X, "train_df_y": train_df_y}
    return model

produce_forecast_m13_lstm(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m13_lstm.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
def produce_forecast_m13_lstm(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """

    # UNPACK MODEL
    lstm = model['lstm']
    hyperparameter = model['hyperparameter']

    #UNPACK HYPERPARAMETER
    seed = int(hyperparameter['seed'])
    input_size = int(hyperparameter['input_size']) #this is one since we only use lag features to be fed into the LSTM. The exogenous features like calenndar are fed to the fully connected layer, together with the last hidden state of LSTM.
    hidden_size = int(hyperparameter['hidden_size']) #this is the size of hidden state, and we aim to use many to one architecture. Meaning we only take the last hidden state as output, and fed into the fully connected layer.
    num_layers = int(hyperparameter['num_layers']) # we use 1 by default to make it simple. 
    output_size = int(hyperparameter['output_size']) #this is one since we only predict one value.
    batch_size = int(hyperparameter['batch_size']) #using minibatch is important cuz if we train all samples at once, the memory is not enough.
    epochs = int(hyperparameter['epochs'])
    learning_rate = hyperparameter['learning_rate']  # No change for learning rate

    # PRODUCE FORECAST
    def produce_forecast(lstm, X):
        # Convert X into X_lag and X_exog
        X_lags, X_exog = separate_lag_and_exogenous_features(X)
        X_lags_tensor = torch.tensor(X_lags.values, dtype=torch.float32)  # Shape: (batch_size, sequence_length, input_size)
        X_exog_tensor = torch.tensor(X_exog.values, dtype=torch.float32)  # Shape: (batch_size, exog_size)
        # y_tensor = torch.tensor(train_df_y.values, dtype=torch.float32).view(-1, 1) to be deleted.

        total_lag_features = X_lags_tensor.shape[1]  # Number of lag features (columns)
        sequence_length = total_lag_features // input_size
        exog_size = X_exog_tensor.shape[1]  # Number of exogenous features

        # Reshaping X_lags_tensor to 3D: (batch_size, sequence_length, input_size)
        X_lags_tensor = X_lags_tensor.view(-1, sequence_length, input_size)

        #predictions = lstm(X_lags_tensor, X_exog_tensor) #this doesn't work because of the batch size is too big, not enough memory.
        predictions = []
        for i in range(0, len(X_lags_tensor), batch_size):
            # Get the current minibatch for both X_lags_tensor and X_exog_tensor
            batch_X_lags = X_lags_tensor[i:i+batch_size]
            batch_X_exog = X_exog_tensor[i:i+batch_size]

            with torch.no_grad():
                # Make predictions for the minibatch
                batch_pred = lstm(batch_X_lags, batch_X_exog)

            # Store the predictions for the current batch
            predictions.append(batch_pred)

        # Concatenate all predictions to get the full result
        predictions = torch.cat(predictions, dim=0)


        return predictions.detach().numpy()

    train_df_y_hat = produce_forecast(lstm, train_df_X)
    test_df_y_hat = produce_forecast(lstm, test_df_X)

    return train_df_y_hat, test_df_y_hat

separate_lag_and_exogenous_features(train_df_X, target_column='y', lag_prefix='y_lag')

This function separates the lag features and exogenous variables from the training dataframe.

Parameters:

Name Type Description Default
train_df_X DataFrame

The dataframe containing both lag features and exogenous variables.

required
target_column str

The name of the target column (e.g., 'y').

'y'
lag_prefix str

The prefix used for lag columns (e.g., 'y_lag').

'y_lag'

Returns:

Name Type Description
X_lags DataFrame

DataFrame containing only the lag features.

X_exog DataFrame

DataFrame containing only the exogenous variables.

Source code in docs\notebooks\model\m13_lstm.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
def separate_lag_and_exogenous_features(train_df_X, target_column='y', lag_prefix='y_lag'):
    '''
    This function separates the lag features and exogenous variables from the training dataframe.

    Args:
        train_df_X (pd.DataFrame): The dataframe containing both lag features and exogenous variables.
        target_column (str): The name of the target column (e.g., 'y').
        lag_prefix (str): The prefix used for lag columns (e.g., 'y_lag').

    Returns:
        X_lags (pd.DataFrame): DataFrame containing only the lag features.
        X_exog (pd.DataFrame): DataFrame containing only the exogenous variables.
    '''

    # Identify lag features (columns that start with 'y_lag')
    lag_features = [col for col in train_df_X.columns if col.startswith(lag_prefix)]

    # Identify exogenous variables (everything except the target and lag features)
    exog_features = [col for col in train_df_X.columns if col not in [target_column] + lag_features]

    # Create dataframes for lag features and exogenous features
    X_lags = train_df_X[lag_features]
    X_exog = train_df_X[exog_features]

    return X_lags, X_exog

train_model_m13_lstm(hyperparameter, train_df_X, train_df_y)

Train and test an LSTM model for point forecasting. Essentially we use the LSTM block to learn the temporal patterns of the time series, and then use a fully connected layer to learn the relationship between the lag features. We take the last hidden state of the LSTM as the output, and concatenate it with the exogenous features (like calendar) to make the final prediction using a fully connected layer. Future imporvement maybe to improve the architecture of the fully connected layer after LSTM.

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m13_lstm.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
def train_model_m13_lstm(hyperparameter, train_df_X, train_df_y):
    ''' Train and test an LSTM model for point forecasting. 
    Essentially we use the LSTM block to learn the temporal patterns of the time series, and then use a fully connected layer to learn the relationship between the lag features.
    We take the last hidden state of the LSTM as the output, and concatenate it with the exogenous features (like calendar) to make the final prediction using a fully connected layer.
    Future imporvement maybe to improve the architecture of the fully connected layer after LSTM. 

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training


    Returns:
        model (model) : trained model with all features
    '''

    #UNPACK HYPERPARAMETER
    seed = int(hyperparameter['seed'])
    input_size = int(hyperparameter['input_size']) #this is one since we only use lag features to be fed into the LSTM. The exogenous features like calenndar are fed to the fully connected layer, together with the last hidden state of LSTM.
    hidden_size = int(hyperparameter['hidden_size']) #this is the size of hidden state, and we aim to use many to one architecture. Meaning we only take the last hidden state as output, and fed into the fully connected layer.
    num_layers = int(hyperparameter['num_layers']) # we use 1 by default to make it simple. 
    output_size = int(hyperparameter['output_size']) #this is one since we only predict one value.
    batch_size = int(hyperparameter['batch_size']) #using minibatch is important cuz if we train all samples at once, the memory is not enough.
    epochs = int(hyperparameter['epochs'])
    learning_rate = hyperparameter['learning_rate']  # No change for learning rate


    #DEFINE MODEL AND TRAINING FUNCTION
    class LSTMModel(nn.Module):
        def __init__(self, input_size, hidden_size, num_layers, exog_size, output_size=1):
            super(LSTMModel, self).__init__()

            # Define the LSTM layer
            self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)

            # Define the Fully Connected (FC) layer
            # The FC layer input size is the concatenation of LSTM output and exogenous variables
            self.fc = nn.Linear(hidden_size + exog_size, output_size)  # exog_size is the number of exogenous features

        def forward(self, x, exogenous_data):
            # Pass the input through the LSTM
            out, (h_n, c_n) = self.lstm(x)

            # Get the last timestep hidden state (h3)
            last_hidden_state = out[:, -1, :]  # Shape: (batch_size, hidden_size)

            # Concatenate the LSTM output (h3) with the exogenous variables (for timestep t+100)
            combined_input = torch.cat((last_hidden_state, exogenous_data), dim=1)  # Shape: (batch_size, hidden_size + exog_size)

            # Pass the combined input through the FC layer
            out = self.fc(combined_input)
            return out

    def train_lstm_with_minibatches(model, train_loader, epochs, learning_rate=0.001):
        # Define the loss function (Mean Squared Error)
        criterion = nn.MSELoss()

        # Define the optimizer (Adam)
        optimizer = optim.Adam(model.parameters(), lr=learning_rate)

        for epoch in range(epochs):
            print(f'Epoch [{epoch+1}/{epochs}]')
            start_time = time.time()

            model.train()  # Set model to training mode
            # print(f'I am here')

            # Iterate over mini-batches
            batch_no = 1
            for X_lags_batch, X_exog_batch, y_batch in train_loader:
                # print(f'I am here now')
                # Print the loss and time taken for this epoch
                print(f'Epoch [{epoch+1}/{epochs}] and batch [{batch_no}/{len(train_loader)}]')
                batch_no += 1
                # Forward pass
                predictions = model(X_lags_batch, X_exog_batch)
                loss = criterion(predictions, y_batch)

                # Backward pass
                optimizer.zero_grad()  # Zero gradients from previous step
                loss.backward()  # Backpropagate the error
                optimizer.step()  # Update the model's weights



            end_time = time.time()
            epoch_time = end_time - start_time
            print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}, time taken: {epoch_time:.2f} seconds')

    def set_seed(seed=seed):
        random.seed(seed)
        np.random.seed(seed)
        torch.manual_seed(seed)
        os.environ["PYTHONHASHSEED"] = str(seed)

    # PREPARE TRAIN DATA
    # SEPARATE LAG AND EXOGENOUS FEATURES
    X_lags, X_exog = separate_lag_and_exogenous_features(train_df_X)
    X_lags_tensor = torch.tensor(X_lags.values, dtype=torch.float32)  # Shape: (batch_size, sequence_length, input_size)
    X_exog_tensor = torch.tensor(X_exog.values, dtype=torch.float32)  # Shape: (batch_size, exog_size)
    y_tensor = torch.tensor(train_df_y.values, dtype=torch.float32).view(-1, 1)

    total_lag_features = X_lags_tensor.shape[1]  # Number of lag features (columns)
    sequence_length = total_lag_features // input_size
    exog_size = X_exog_tensor.shape[1]  # Number of exogenous features

    # Reshaping X_lags_tensor to 3D: (batch_size, sequence_length, input_size)
    X_lags_tensor = X_lags_tensor.view(-1, sequence_length, input_size)


    # INITIALIZE MODEL and MAKE TRAINING BATCHES
    set_seed(seed = seed) # Set random seed for reproducibility
    lstm = LSTMModel(input_size, hidden_size, num_layers, exog_size, output_size)
    # Create a TensorDataset with your features and target
    train_data = TensorDataset(X_lags_tensor, X_exog_tensor, y_tensor)
    # Create a DataLoader to handle mini-batching
    train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True)

    # TRAIN MODEL
    train_lstm_with_minibatches(lstm, train_loader, epochs=epochs, learning_rate=learning_rate)


    # PACK MODEL
    model = {"lstm": lstm, 'hyperparameter': hyperparameter, "train_df_X": train_df_X, "train_df_y": train_df_y}


    return model

produce_forecast_m14_gru(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained GRU model Args: model (dictionary): all parameters of the trained model train_df_X (df): predictors of train set test_df_X (df): predictors of test set

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m14_gru.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def produce_forecast_m14_gru(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained GRU model
    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set
    """
    gru = model['gru']
    hyperparameter = model['hyperparameter']
    input_size = int(hyperparameter['input_size'])
    batch_size = int(hyperparameter['batch_size'])

    def produce_forecast(gru, X):
        X_lags, X_exog = separate_lag_and_exogenous_features(X)
        X_lags_tensor = torch.tensor(X_lags.values, dtype=torch.float32)
        X_exog_tensor = torch.tensor(X_exog.values, dtype=torch.float32)

        total_lag_features = X_lags_tensor.shape[1]
        sequence_length = total_lag_features // input_size
        X_lags_tensor = X_lags_tensor.view(-1, sequence_length, input_size)

        predictions = []
        for i in range(0, len(X_lags_tensor), batch_size):
            batch_X_lags = X_lags_tensor[i:i+batch_size]
            batch_X_exog = X_exog_tensor[i:i+batch_size]
            with torch.no_grad():
                batch_pred = gru(batch_X_lags, batch_X_exog)
            predictions.append(batch_pred)
        return torch.cat(predictions, dim=0).detach().numpy()

    train_df_y_hat = produce_forecast(gru, train_df_X)
    test_df_y_hat = produce_forecast(gru, test_df_X)
    return train_df_y_hat, test_df_y_hat

train_model_m14_gru(hyperparameter, train_df_X, train_df_y)

Train and test a GRU model for point forecasting. Uses GRU for temporal patterns, FC layer for lag+exogenous features. Args: hyperparameter (df) : hyperparameter value of the model consisting of number of features train_df_X (df) : features matrix for training train_df_y (df) : target matrix for training

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m14_gru.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def train_model_m14_gru(hyperparameter, train_df_X, train_df_y):
    ''' Train and test a GRU model for point forecasting. 
    Uses GRU for temporal patterns, FC layer for lag+exogenous features.
    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training


    Returns:
        model (model) : trained model with all features
    '''

    # UNPACK HYPERPARAMETER
    seed = int(hyperparameter['seed'])
    input_size = int(hyperparameter['input_size'])
    hidden_size = int(hyperparameter['hidden_size'])
    num_layers = int(hyperparameter['num_layers'])
    output_size = int(hyperparameter['output_size'])
    batch_size = int(hyperparameter['batch_size'])
    epochs = int(hyperparameter['epochs'])
    learning_rate = hyperparameter['learning_rate']

    # DEFINE MODEL
    class GRUModel(nn.Module):
        def __init__(self, input_size, hidden_size, num_layers, exog_size, output_size=1):
            super(GRUModel, self).__init__()
            self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)
            self.fc = nn.Linear(hidden_size + exog_size, output_size)

        def forward(self, x, exogenous_data):
            out, h_n = self.gru(x)
            last_hidden_state = out[:, -1, :]
            combined_input = torch.cat((last_hidden_state, exogenous_data), dim=1)
            out = self.fc(combined_input)
            return out

    def train_gru_with_minibatches(model, train_loader, epochs, learning_rate=0.001):
        criterion = nn.MSELoss()
        optimizer = optim.Adam(model.parameters(), lr=learning_rate)

        for epoch in range(epochs):
            print(f'Epoch [{epoch+1}/{epochs}]')
            start_time = time.time()
            model.train()
            batch_no = 1
            for X_lags_batch, X_exog_batch, y_batch in train_loader:
                print(f'Epoch [{epoch+1}/{epochs}] and batch [{batch_no}/{len(train_loader)}]')
                batch_no += 1

                predictions = model(X_lags_batch, X_exog_batch)
                loss = criterion(predictions, y_batch)

                optimizer.zero_grad()
                loss.backward()
                optimizer.step()

            print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}, time taken: {time.time() - start_time:.2f}s')

    def set_seed(seed=seed):
        random.seed(seed)
        np.random.seed(seed)
        torch.manual_seed(seed)
        os.environ["PYTHONHASHSEED"] = str(seed)

    # PREPARE TRAIN DATA
    X_lags, X_exog = separate_lag_and_exogenous_features(train_df_X)
    X_lags_tensor = torch.tensor(X_lags.values, dtype=torch.float32)
    X_exog_tensor = torch.tensor(X_exog.values, dtype=torch.float32)
    y_tensor = torch.tensor(train_df_y.values, dtype=torch.float32).view(-1, 1)

    total_lag_features = X_lags_tensor.shape[1]
    sequence_length = total_lag_features // input_size
    exog_size = X_exog_tensor.shape[1]

    X_lags_tensor = X_lags_tensor.view(-1, sequence_length, input_size)

    # INITIALIZE MODEL + DATALOADER
    set_seed(seed=seed)
    gru = GRUModel(input_size, hidden_size, num_layers, exog_size, output_size)
    train_data = TensorDataset(X_lags_tensor, X_exog_tensor, y_tensor)
    train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True)

    # TRAIN MODEL
    train_gru_with_minibatches(gru, train_loader, epochs=epochs, learning_rate=learning_rate)

    # PACK MODEL
    model = {"gru": gru, 'hyperparameter': hyperparameter, "train_df_X": train_df_X, "train_df_y": train_df_y}
    return model

produce_forecast_m15_transformer(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained Transformer model Args: model (dictionary): all parameters of the trained model train_df_X (df): predictors of train set test_df_X (df): predictors of test set

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m15_transformer.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
def produce_forecast_m15_transformer(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained Transformer model
    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set
    """
    transformer = model['transformer']
    hyperparameter = model['hyperparameter']
    batch_size = int(hyperparameter['batch_size'])
    input_size = int(hyperparameter['input_size'])

    def produce_forecast(transformer, X):
        X_lags, X_exog = separate_lag_and_exogenous_features(X)
        X_lags_tensor = torch.tensor(X_lags.values, dtype=torch.float32)
        X_exog_tensor = torch.tensor(X_exog.values, dtype=torch.float32)
        total_lag_features = X_lags_tensor.shape[1]
        sequence_length = total_lag_features // input_size
        X_lags_tensor = X_lags_tensor.view(-1, sequence_length, input_size)
        predictions = []
        for i in range(0, len(X_lags_tensor), batch_size):
            batch_X_lags = X_lags_tensor[i:i+batch_size]
            batch_X_exog = X_exog_tensor[i:i+batch_size]
            with torch.no_grad():
                batch_pred = transformer(batch_X_lags, batch_X_exog)
            predictions.append(batch_pred)
        return torch.cat(predictions, dim=0).detach().numpy()

    train_df_y_hat = produce_forecast(transformer, train_df_X)
    test_df_y_hat = produce_forecast(transformer, test_df_X)
    return train_df_y_hat, test_df_y_hat

train_model_m15_transformer(hyperparameter, train_df_X, train_df_y)

Train and test a Transformer model for point forecasting. Uses Transformer for temporal patterns, FC layer for lag+exogenous features. Args: hyperparameter (df) : hyperparameter value of the model consisting of number of features train_df_X (df) : features matrix for training train_df_y (df) : target matrix for training

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m15_transformer.py
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def train_model_m15_transformer(hyperparameter, train_df_X, train_df_y):
    ''' Train and test a Transformer model for point forecasting. 
    Uses Transformer for temporal patterns, FC layer for lag+exogenous features.
    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training

    Returns:
        model (model) : trained model with all features
    '''

    # UNPACK HYPERPARAMETER
    seed = int(hyperparameter['seed'])
    input_size = int(hyperparameter['input_size'])
    hidden_size = int(hyperparameter['hidden_size'])
    num_layers = int(hyperparameter['num_layers'])
    output_size = int(hyperparameter['output_size'])
    batch_size = int(hyperparameter['batch_size'])
    epochs = int(hyperparameter['epochs'])
    nhead = int(hyperparameter['nhead'])
    learning_rate = hyperparameter['learning_rate']

    import torch
    import torch.nn as nn
    import torch.optim as optim
    import random, numpy as np, os, time
    from torch.utils.data import DataLoader, TensorDataset

    # TRANSFORMER MODEL
    class TransformerModel(nn.Module):
        def __init__(self, input_size, hidden_size, num_layers, exog_size, output_size=1):
            super(TransformerModel, self).__init__()
            # Transformer embedding
            self.embedding = nn.Linear(input_size, hidden_size)
            encoder_layer = nn.TransformerEncoderLayer(
                d_model=hidden_size,
                nhead=nhead,
                dim_feedforward=hidden_size * 2,
                batch_first=True
            )
            self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
            # Fully connected output layer
            self.fc = nn.Linear(hidden_size + exog_size, output_size)

        def forward(self, x, exogenous_data):
            x = self.embedding(x) 
            x = self.transformer_encoder(x)
            last_hidden_state = x[:, -1, :]
            combined_input = torch.cat((last_hidden_state, exogenous_data), dim=1)
            out = self.fc(combined_input)
            return out

    def train_transformer_with_minibatches(model, train_loader, epochs, learning_rate=learning_rate):
        criterion = nn.MSELoss()
        optimizer = optim.Adam(model.parameters(), lr=learning_rate)

        for epoch in range(epochs):
            print(f'Epoch [{epoch+1}/{epochs}]')
            start_time = time.time()
            model.train()
            batch_no = 1
            for X_lags_batch, X_exog_batch, y_batch in train_loader:
                print(f'Epoch [{epoch+1}/{epochs}] batch [{batch_no}/{len(train_loader)}]')
                batch_no += 1
                predictions = model(X_lags_batch, X_exog_batch)
                loss = criterion(predictions, y_batch)
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()
            end_time = time.time()
            print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}, time: {end_time - start_time:.2f}s')

    def set_seed(seed=seed):
        random.seed(seed)
        np.random.seed(seed)
        torch.manual_seed(seed)
        os.environ["PYTHONHASHSEED"] = str(seed)

    # --- DATA PREP ---
    X_lags, X_exog = separate_lag_and_exogenous_features(train_df_X)
    X_lags_tensor = torch.tensor(X_lags.values, dtype=torch.float32)
    X_exog_tensor = torch.tensor(X_exog.values, dtype=torch.float32)
    y_tensor = torch.tensor(train_df_y.values, dtype=torch.float32).view(-1, 1)
    total_lag_features = X_lags_tensor.shape[1]
    sequence_length = total_lag_features // input_size
    exog_size = X_exog_tensor.shape[1]
    X_lags_tensor = X_lags_tensor.view(-1, sequence_length, input_size)

    # --- INIT MODEL AND DATALOADER ---
    set_seed(seed)
    transformer = TransformerModel(input_size, hidden_size, num_layers, exog_size, output_size)
    train_data = TensorDataset(X_lags_tensor, X_exog_tensor, y_tensor)
    train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True)
    train_transformer_with_minibatches(transformer, train_loader, epochs=epochs, learning_rate=learning_rate)

    model = {"transformer": transformer, 'hyperparameter': hyperparameter, "train_df_X": train_df_X, "train_df_y": train_df_y}
    return model

produce_forecast_m16_prophet(model, train_df_X, test_df_X, train_df_y, forecast_horizon)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required
train_df_y df

target of train set

required
forecast_horizon int

forecast horizon for the model

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m16_prophet.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
def produce_forecast_m16_prophet(model, train_df_X, test_df_X, train_df_y, forecast_horizon):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set
        train_df_y (df): target of train set
        forecast_horizon (int): forecast horizon for the model

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """

    # UNPACK MODEL
    prophet_model = model['prophet']
    y = model['y']
    hyperparameter = model['hyperparameter']

    #UNPACK HYPERPARAMETER
    seasonality_prior_scale = hyperparameter["seasonality_prior_scale"]
    seasonality_mode = hyperparameter["seasonality_mode"]
    weekly_seasonality = hyperparameter["weekly_seasonality"]
    daily_seasonality = hyperparameter["daily_seasonality"]
    growth = hyperparameter["growth"]

    # Set up X_exog which is used for prediction
    timestep_frequency = test_df_X.index[1] - test_df_X.index[0]
    n_timestep_forecast_horizon = int(forecast_horizon / (timestep_frequency.total_seconds() / 60))

    train_df_X_updated = remove_jump_df(train_df_X)
    test_df_X_updated = remove_jump_df(test_df_X)

    X_lags, X_exog = separate_lag_and_exogenous_features(train_df_X_updated)

    X_exog.reset_index(inplace=True)
    X_exog.rename(columns={'datetime': 'ds'}, inplace=True)

    # Forecast train set
    train_df_y_hat = prophet_model.predict(X_exog)

    train_df_y_hat = train_df_y_hat[['ds', 'yhat']]

    train_df_y_hat.set_index('ds', inplace=True)
    train_df_y_hat.index.name = 'datetime'

    # Set up function to warm start the model for updating the fit
    def warm_start_params(m):
        """
        Retrieve parameters from a trained model in the format used to initialize a new Stan model.
        Note that the new Stan model must have these same settings:
            n_changepoints, seasonality features, mcmc sampling
        for the retrieved parameters to be valid for the new model.

        Parameters
        ----------
        m: A trained model of the Prophet class.

        Returns
        -------
        A Dictionary containing retrieved parameters of m.
        """
        res = {}
        for pname in ['k', 'm', 'sigma_obs']:
            if m.mcmc_samples == 0:
                res[pname] = m.params[pname][0][0]
            else:
                res[pname] = np.mean(m.params[pname])
        for pname in ['delta', 'beta']:
            if m.mcmc_samples == 0:
                res[pname] = m.params[pname][0]
            else:
                res[pname] = np.mean(m.params[pname], axis=0)
        return res

    # PRODUCE FORECASTFOR TEST SET

    # REFIT THE MODEL AND PRODUCE NEW FORECAST FOR TEST SET
    # The model is refitted for 100 times only so there will be only 100 forecast results. 

    test_df_y_hat = pd.DataFrame(index = test_df_X.index)
    test_df_y_hat['y_hat'] = np.nan


    # in the case of CV 10, which is when test df < train df
    # don't compute the test forecast
    if (test_df_X.index[-1] < train_df_X.index[0]):
    # this is the case when we use CV10, where the test set is before the train set
        print("Test set is before train set / CV 10, no test forecast can be made")
        return train_df_y_hat, test_df_y_hat

    _, X_test = separate_lag_and_exogenous_features(test_df_X)
    X_test.reset_index(inplace=True)
    X_test.rename(columns={'datetime': 'ds'}, inplace=True)

    n_update = 100
    n_timesteps_per_update = int(len(test_df_y_hat) / (n_update + 1))

    # TRANSFORM test_df_X to a series with only the last lag
    horizon_timedelta = pd.Timedelta(minutes=forecast_horizon)
    last_observation = f'y_lag_{horizon_timedelta}m'
    test_df_y_last = test_df_X[last_observation]

    new_y = pd.DataFrame(test_df_y_last)
    new_y.rename(columns={new_y.columns[0]: 'y'}, inplace=True)
    new_y.insert(0, 'ds', new_y.index - pd.Timedelta(minutes=forecast_horizon))
    new_y.reset_index(drop = True, inplace=True)

    new_y = new_y.drop(0, axis=0).reset_index(drop=True)
    X_exog_complete = pd.concat([X_exog, X_test], axis=0)
    X_exog_complete = X_exog_complete.drop(0, axis=0).reset_index(drop=True)
    new_y = pd.merge(new_y, X_exog_complete, on='ds', how='left')

    for i in range(n_update):
    # for i in range(2): #for test only
        print('Processing i = ', i + 1, ' out of ', n_update),
        if i == 0:
            X_test_curr = X_test.iloc[:1,:]
            test_df_y_hat.iloc[i, 0] = prophet_model.predict(X_test_curr)['yhat'].values[0]
        else:
            new_rows = new_y.iloc[(i-1)*n_timesteps_per_update : i*n_timesteps_per_update, :]
            y = pd.concat([y, new_rows], ignore_index=True)

            current_params = warm_start_params(prophet_model)

            prophet_model = Prophet(
                seasonality_prior_scale=seasonality_prior_scale,  # Example hyperparameter for seasonality strength
                seasonality_mode=seasonality_mode,  # Use multiplicative seasonality
                weekly_seasonality=weekly_seasonality,  # Enable weekly seasonality
                daily_seasonality=daily_seasonality,  # Enable daily seasonality
                growth=growth,  # Choose between 'linear' or 'logistic' growth
            )

            prophet_model = prophet_model.fit(y, init=current_params)  # Adding the last day, warm-starting from the prev model
            X_test_curr = X_test.iloc[i*n_timesteps_per_update : (1+i*n_timesteps_per_update),:]
            test_df_y_hat.iloc[i*n_timesteps_per_update, 0] = prophet_model.predict(X_test_curr)['yhat'].values[0]

    return train_df_y_hat, test_df_y_hat

train_model_m16_prophet(hyperparameter, train_df_X, train_df_y, forecast_horizon)

Train a prophet model for point forecasting.

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required
forecast_horizon (int)

forecast horizon for the model

required

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m16_prophet.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def train_model_m16_prophet(hyperparameter, train_df_X, train_df_y, forecast_horizon):
    ''' Train a prophet model for point forecasting. 

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training
        forecast_horizon (int) : forecast horizon for the model

    Returns:
        model (model) : trained model with all features
    '''

    #UNPACK HYPERPARAMETER
    seed = hyperparameter["seed"]
    seasonality_prior_scale = hyperparameter["seasonality_prior_scale"]
    seasonality_mode = hyperparameter["seasonality_mode"]
    weekly_seasonality = hyperparameter["weekly_seasonality"]
    daily_seasonality = hyperparameter["daily_seasonality"]
    growth = hyperparameter["growth"]


    # UPDATE train_df to exclude all rows after a sudden jump in the timestep
    train_df_y_updated = remove_jump_df(train_df_y)
    train_df_X_updated = remove_jump_df(train_df_X)

    # Calculate the frequency of the timesteps using the first and second index values
    timestep_frequency = train_df_y_updated.index[1] - train_df_y_updated.index[0]
    inferred_frequency = pd.infer_freq(train_df_y_updated.index)
    train_df_y_updated = train_df_y_updated.asfreq(inferred_frequency) 

    # INTRODUCE GAP BETWEEN TRAIN AND TEST SET TO AVOID DATA LEAKAGE
    n_timestep_forecast_horizon = int(forecast_horizon / (timestep_frequency.total_seconds() / 60))
    if n_timestep_forecast_horizon == 1:
        pass
    else:
        train_df_y_updated = train_df_y_updated[:-(n_timestep_forecast_horizon - 1)]
        train_df_X_updated = train_df_X_updated[:-(n_timestep_forecast_horizon - 1)]

    # Assuming train_df_y_updated is your dataframe and 'y' is the column with the training series
    y = train_df_y_updated.copy()
    X_lags, X_exog = separate_lag_and_exogenous_features(train_df_X_updated)

    #Initialize the Prophet model with hyperparameters
    prophet_model = Prophet(
        seasonality_prior_scale=seasonality_prior_scale,  # Example hyperparameter for seasonality strength
        seasonality_mode=seasonality_mode,  # Use multiplicative seasonality
        weekly_seasonality=weekly_seasonality,  # Enable weekly seasonality
        daily_seasonality=daily_seasonality,  # Enable daily seasonality
        growth=growth  # Choose between 'linear' or 'logistic' growth
        # random_state =  seed,  # cannot set seed in prophet
    )
    for col in X_exog.columns:
        prophet_model.add_regressor(col)

    # Add exogenous features to the y DataFrame
    y = y.merge(X_exog, on='datetime')
    y.reset_index(inplace=True)
    y.rename(columns={'datetime': 'ds'}, inplace=True)

    # Train model
    prophet_model.fit(y)

    # PACK MODEL
    model = {"prophet": prophet_model, "y": y, "hyperparameter": hyperparameter}


    return model

produce_forecast_m17_xgb(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained model

Parameters:

Name Type Description Default
model dictionary

all parameters of the trained model

required
train_df_X df

predictors of train set

required
test_df_X df

predictors of test set

required

Returns:

Type Description

train_df_y_hat (df) : forecast result at train set

test_df_y_hat (df) : forecast result at test set

Source code in docs\notebooks\model\m17_xgb.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def produce_forecast_m17_xgb(model, train_df_X, test_df_X):
    """Create forecast at the train and test set using the trained model

    Args:
        model (dictionary): all parameters of the trained model
        train_df_X (df): predictors of train set
        test_df_X (df): predictors of test set

    Returns:
        train_df_y_hat (df) : forecast result at train set
        test_df_y_hat (df) : forecast result at test set

    """

    # UNPACK MODEL
    xgb = model["xgb"]

    # PRODUCE FORECAST
    train_df_y_hat = xgb.predict(train_df_X)
    test_df_y_hat = xgb.predict(test_df_X)

    return train_df_y_hat, test_df_y_hat

train_model_m17_xgb(hyperparameter, train_df_X, train_df_y)

Train and test a xgb model for point forecasting.

Parameters:

Name Type Description Default
hyperparameter (df)

hyperparameter value of the model consisting of number of features

required
train_df_X (df)

features matrix for training

required
train_df_y (df)

target matrix for training

required

Returns:

Type Description

model (model) : trained model with all features

Source code in docs\notebooks\model\m17_xgb.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def train_model_m17_xgb(hyperparameter, train_df_X, train_df_y):
    ''' Train and test a xgb model for point forecasting. 

    Args:
        hyperparameter (df) : hyperparameter value of the model consisting of number of features
        train_df_X (df) : features matrix for training
        train_df_y (df) : target matrix for training


    Returns:
        model (model) : trained model with all features
    '''

    #UNPACK HYPERPARAMETER
    xgb_seed = int(hyperparameter["xgb_seed"])
    n_estimators=hyperparameter["n_estimators"]
    learning_rate=hyperparameter["learning_rate"]
    max_depth=hyperparameter["max_depth"]
    subsample=hyperparameter["subsample"]
    colsample_bytree=hyperparameter["colsample_bytree"]

    #INITIALIZE AND TRAIN MODEL
    xgb = XGBRegressor(n_estimators=200, learning_rate=0.1, max_depth=6, subsample=0.8, colsample_bytree=0.8, random_state=xgb_seed)   
    xgb.fit(train_df_X, train_df_y)   

    # PACK MODEL
    model = {"xgb": xgb}


    return model

produce_forecast_m18_nbeats(model, train_df_X, test_df_X)

Create forecast at the train and test set using the trained NBeats model.

Parameters:

Name Type Description Default
model

trained NBeats PyTorch model

required
train_df_X (DataFrame)

predictors of train set

required
test_df_X (DataFrame)

predictors of test set

required

Returns:

Type Description

train_df_y_hat (DataFrame) : forecast result at train set

test_df_y_hat (DataFrame) : forecast result at test set

Source code in docs\notebooks\model\m18_nbeats.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def produce_forecast_m18_nbeats(model, train_df_X, test_df_X):
    """
    Create forecast at the train and test set using the trained NBeats model.

    Args:
        model : trained NBeats PyTorch model
        train_df_X (DataFrame) : predictors of train set
        test_df_X (DataFrame) : predictors of test set

    Returns:
        train_df_y_hat (DataFrame) : forecast result at train set
        test_df_y_hat (DataFrame) : forecast result at test set
    """
    import torch
    model.eval()

    with torch.no_grad():
        X_train_tensor = torch.tensor(train_df_X.values, dtype=torch.float32)
        X_test_tensor = torch.tensor(test_df_X.values, dtype=torch.float32)

        y_train_hat = model(X_train_tensor).numpy()
        y_test_hat = model(X_test_tensor).numpy()

    import pandas as pd
    train_df_y_hat = pd.DataFrame(y_train_hat, index=train_df_X.index, columns=['y_hat'])
    test_df_y_hat = pd.DataFrame(y_test_hat, index=test_df_X.index, columns=['y_hat'])

    return train_df_y_hat, test_df_y_hat

train_model_m18_nbeats(hyperparameter, train_df_X, train_df_y)

Train and test an NBeats model for point forecasting. Uses NBeats architecture for predicting time series with lag+exogenous features.

Parameters:

Name Type Description Default
hyperparameter (dict)

model hyperparameters

required
train_df_X (DataFrame)

predictors for training

required
train_df_y (DataFrame)

target for training

required

Returns:

Name Type Description
model

trained PyTorch NBeats model

Source code in docs\notebooks\model\m18_nbeats.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def train_model_m18_nbeats(hyperparameter, train_df_X, train_df_y):
    """
    Train and test an NBeats model for point forecasting.
    Uses NBeats architecture for predicting time series with lag+exogenous features.

    Args:
        hyperparameter (dict) : model hyperparameters
        train_df_X (DataFrame) : predictors for training
        train_df_y (DataFrame) : target for training

    Returns:
        model : trained PyTorch NBeats model
    """
    # ---- Unpack hyperparameters ----
    input_size = train_df_X.shape[1]
    output_size = int(hyperparameter['output_size'])
    hidden_size = int(hyperparameter['hidden_size'])
    num_blocks = int(hyperparameter['num_blocks'])
    num_layers = int(hyperparameter['num_layers'])
    lr = hyperparameter['lr']
    epochs = int(hyperparameter['epochs'])
    seed = int(hyperparameter['seed'])

    # ---- Set seeds for reproducibility ----
    import torch, numpy as np, random
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False

    # ---- Define NBeats model inside the function ----
    import torch.nn as nn
    class NBeatsModel(nn.Module):
        def __init__(self, input_size, output_size, hidden_size, num_blocks, num_layers):
            super(NBeatsModel, self).__init__()
            blocks = []
            for _ in range(num_blocks):
                block = []
                for l in range(num_layers):
                    block.append(nn.Linear(input_size if l==0 else hidden_size, hidden_size))
                    block.append(nn.ReLU())
                block.append(nn.Linear(hidden_size, output_size))
                blocks.append(nn.Sequential(*block))
            self.blocks = nn.ModuleList(blocks)

        def forward(self, x):
            out = 0
            for block in self.blocks:
                out += block(x)
            return out

    model = NBeatsModel(input_size, output_size, hidden_size, num_blocks, num_layers)

    # ---- Training setup ----
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)
    criterion = nn.MSELoss()
    X_tensor = torch.tensor(train_df_X.values, dtype=torch.float32)
    y_tensor = torch.tensor(train_df_y.values, dtype=torch.float32)

    # ---- Training loop ----
    model.train()
    for epoch in range(epochs):
        optimizer.zero_grad()
        output = model(X_tensor)
        loss = criterion(output, y_tensor)
        loss.backward()
        optimizer.step()

    return model