Forecasting power consumption in Tetouan city using Deep Learning Time Series techniques

Introduction

In this notebook, we will forecast the power consumption of Tetouan city for one day in 10 minute increments using Deep Learning Series techniques. This short term time series forecasting can be crucial in optimizing grid operations, enhancing reliability, reducing costs, and facilitating the integration of renewable energy sources, and it can serve as a vital tool that will allow utilities to adapt to changing demand patterns and move towards a more sustainable future.

This process involves the use of advanced deep learning models to predict future electricity usage based on historical data. We'll explore three different methods of forecasting, each utilizing the following specialized timeseries backbones:

  • One-step univariate forecasting with Bidirectional LSTM
  • Multi-step multivariate forecasting with InceptionTime
  • One-step multivariate forecasting with Time Series Transformer

Imports

%matplotlib inline
import matplotlib.pyplot as plt

import numpy as np
import pandas as pd

from datetime import datetime as dt
from IPython.display import Image, HTML
import pandas as pd
from datetime import datetime

from sklearn.model_selection import train_test_split

from sklearn.preprocessing import MinMaxScaler

from pandas.plotting import autocorrelation_plot

from sklearn.metrics import r2_score
import sklearn.metrics as metrics

from arcgis.gis import GIS
from arcgis.learn import TimeSeriesModel, prepare_tabulardata
from arcgis.features import FeatureLayer, FeatureLayerCollection

Connecting to ArcGIS

gis = GIS("home")

Accessing & visualizing datasets

The dataset employed in this illustrative study consists of a multivariate time series comprising power consumption data recorded every 10 minutes in Tetouan city. The data spans from January 2017 to December 2017, encompassing each day within that timeframe. The multivariate time series consists of historical power consumption, temperature, humidity, wind speed, and other relevant variables. The following cell downloads the data:

data_table = gis.content.get("c16e532a57bf4900a201dfa5c6e6d1ab")
data_table
Tetouan_city_power_consumption1
city power consumption dataCSV by api_data_owner
Last Modified: October 15, 2023
0 comments, 56 views
# Download the csv and saving it in local folder
data_path = data_table.get_data()
# # Read the csv file
city_power_consumption_df = pd.read_csv(data_path).drop(["Unnamed: 0"], axis=1)
city_power_consumption_df['DateTime'] = pd.to_datetime(city_power_consumption_df['DateTime'])
city_power_consumption_df.head(5)
DateTimeTemperatureHumidityWind Speedgeneral diffuse flowsdiffuse flowsZone 1 Power ConsumptionZone 2 Power ConsumptionZone 3 Power ConsumptionTotal Power ConsumptionMonthweekdayhour
02017-01-01 00:00:006.55973.80.0830.0510.11934055.6962016128.8753820240.9638670425.53544JanuarySunday0.0
12017-01-01 00:10:006.41474.50.0830.0700.08529814.6835419375.0759920131.0843469320.84387JanuarySunday0.0
22017-01-01 00:20:006.31374.50.0800.0620.10029128.1012719006.6869319668.4337367803.22193JanuarySunday0.0
32017-01-01 00:30:006.12175.00.0830.0910.09628228.8607618361.0942218899.2771165489.23209JanuarySunday0.0
42017-01-01 00:40:005.92175.70.0810.0480.08527335.6962017872.3404318442.4096463650.44627JanuarySunday0.0
city_power_consumption_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 52416 entries, 0 to 52415
Data columns (total 13 columns):
 #   Column                     Non-Null Count  Dtype         
---  ------                     --------------  -----         
 0   DateTime                   52416 non-null  datetime64[ns]
 1   Temperature                52416 non-null  float64       
 2   Humidity                   52416 non-null  float64       
 3   Wind Speed                 52416 non-null  float64       
 4   general diffuse flows      52416 non-null  float64       
 5   diffuse flows              52416 non-null  float64       
 6   Zone 1 Power Consumption   52416 non-null  float64       
 7   Zone 2  Power Consumption  52416 non-null  float64       
 8   Zone 3  Power Consumption  52416 non-null  float64       
 9   Total Power Consumption    52416 non-null  float64       
 10  Month                      52416 non-null  object        
 11  weekday                    52416 non-null  object        
 12  hour                       52416 non-null  float64       
dtypes: datetime64[ns](1), float64(10), object(2)
memory usage: 5.2+ MB

One-step univariate forecasting

Once the data has been downloaded, we will first use one-step univariate forecasting. which we will use as a baseline for more complex forecasting models. In this approach, the model predicts one step ahead at a time. For this study of power consumption, this will mean predicting the usage for the next 10 minutes based solely on the historical values of that specific variable up to the current timestep. Thus, using past observations of the single variable Total Power Consumption, we will estimate future values for the given number of future timesteps. This method assumes that the future value depends only on the immediately preceding values of the same variable. This approach is relatively straightforward.

Data processing

Data processing for a timeseries consists of first splitting the dataset into a training dataset and a testing dataset as follows:

Train - Test split of timeseries dataset

As suggested earlier, we will forecast power consumption every 10 minutes for an entire day, resulting in 144 data points (6 x 24). To validate the model, we will set aside these 144 data points as the test set, while the remaining data will be used for training.

test_size = 144
city_power_consumption_df.shape
(52416, 13)
train, test = train_test_split(city_power_consumption_df, test_size = test_size, shuffle=False)
train.tail(2)
DateTimeTemperatureHumidityWind Speedgeneral diffuse flowsdiffuse flowsZone 1 Power ConsumptionZone 2 Power ConsumptionZone 3 Power ConsumptionTotal Power ConsumptionMonthweekdayhour
522702017-12-29 23:40:0013.2753.810.0770.0550.09329067.6806125701.1353213207.2028867976.01881DecemberFriday23.0
522712017-12-29 23:50:0013.2753.740.0790.0590.06328544.4866925126.7259913017.0468266688.25950DecemberFriday23.0
# check the columns 
train.columns
Index(['DateTime', 'Temperature', 'Humidity', 'Wind Speed',
       'general diffuse flows', 'diffuse flows', 'Zone 1 Power Consumption',
       'Zone 2  Power Consumption', 'Zone 3  Power Consumption',
       'Total Power Consumption', 'Month', 'weekday', 'hour'],
      dtype='object')

Autocorrelation plot

When forecasting a single variable using only its past values, understanding its autocorrelation structure becomes crucial. Autocorrelation plots help visualize the relationship between a time series and its past values at various lags. This allows us to identify any significant autocorrelation patterns that can guide model selection and parameter tuning.

The autocorrelation plot below for the power consumption time series shows the correlation between the series and its lagged values at different time lags.

plt.figure(figsize=(30,10))
autocorrelation_plot(train["Total Power Consumption"])
plt.show()
<Figure size 3000x1000 with 1 Axes>

Here we can see that the autocorrelation plot shows maximum autocorrelation at lag zero and gradually decreases over subsequent lags, which suggests a strong immediate dependence between consecutive observations, potentially indicating underlying seasonality or trend components in the data. This indicates the suitability of this data for univariate timeseries forecasting.

Model building

Once the dataset has been divided into the training and testing datasets, we can use the training data for modelling.

Data preparation

In this method, we are using a single variable named Total Power Consumption to forecast the 144 timesteps of future total power consumption or electricity usage for every 10 minutes based on its historical data, without using any explanatory variables.

The preparation of the data is carried out by the prepare_tabulardata method from the arcgis.learn module in the ArcGIS API for Python. This function will take either a non spatial dataframe, a feature layer or a spatial dataframe containing the dataset as input and will return a TabularDataObject that can be fed into the model. Here we are using a non spatial dataframe.

The primary input parameters required for the tool are:

  • input_features : non spatial dataframe containing the primary dataset
  • variable_predict : field name Total Power Consumption as the y-variable to be forecasted from the input dataframe
  • explanatory_variables : Since there are none in this example, it is not required here.
  • index_field : field name containing the timestamp

Here, the preprocessor is used for scaling the data to improve the fit of the model.

# one step, univariate
preprocessors = [("Total Power Consumption", MinMaxScaler())] 

data = prepare_tabulardata(train, 
                           variable_predict="Total Power Consumption",
                           index_field="DateTime", 
                           preprocessors=preprocessors)
C:\Users\sup10432\AppData\Local\ESRI\conda\envs\pro3.3_automl_QA_jan24\lib\site-packages\arcgis\learn\_utils\tabular_data.py:1871: UserWarning:

Dataframe is not spatial, Rasters and distance layers will not work

# Visualize the entire timeseries data
data.show_batch(graph=True)
<Figure size 2500x500 with 1 Axes>

Here we can utilize the show_batch() function both for inspection and visualization. First, we use it to display the Total Power Consumption data, where each time series data instance is identified by an index corresponding to its specific datetime.

data.show_batch()
Total Power Consumption
1977053258.85754
28061116044.38323
3321491241.08204
3477576922.19575
5022886624.92361

Next, the sequence length of 288 is used, as it is the previous two days of power consumption data. This is an important parameter for fitting a timeseries forecasting model and usually indicates the seasonality of the data, which can be experimented with for a better fit.

Using this sequence length, we can use the show_batch() function for visualization. The graph below depicts the segmentation of univariate time series data into batches, where each batch aligns with the specified sequence length designated for the model. The x-axis delineates the data, organized in batches, with each ticker at a 6-day interval. The y-axis represents the absolute values of power consumption. Notably, the value on the top of the graph signifies the target variable for forecasting, denoting the value after the end of the respective sequence lengths. This value serves as the dependent variable during the training of the time series model.

# visualize the timeseries in batches
seq_len = 288
data.show_batch(rows=4,seq_len=seq_len)
<Figure size 1000x1000 with 16 Axes>

Model initialization

This is the most significant step for fitting a timeseries model. Here, along with the pre-procesed data, the backbone for training the model and the sequence length is passed as parameters. Out of these three, the sequence length must be selected carefully since it is a critical parameter. The sequence length is usually the cycle of the data. You can try with higher sequence lengths if there are sufficient computing resources available.

In terms of backbones, the available set of backbones encompasses various architectures specialized for handling time series data. These include models specifically designed for time series (InceptionTime, TimeSeriesTransformer), recurrent neural networks like LSTM and Bidirectional LSTM, Neural network (FCN), and adaptations of convolutional neural networks (ResNet, ResCNN) for effective time series analysis.

Here we will use the LSTM Bidirectional model.

# In model initialization, the data and the backbone is selected 
ts_model = TimeSeriesModel(data, seq_len=seq_len, model_arch='LSTM',bidirectional=True)

Learning rate search

# Finding the learning rate for training the model
l_rate = ts_model.lr_find()
l_rate
<Figure size 640x480 with 1 Axes>
0.009120108393559097

Model training

Finally, the model is now ready for training. To train the model, the model.fit function is called and provided with the number of epochs for training and the estimated learning rate suggested by lr_find in the previous step. We will use 2 epochs for training, as it was found that 2 epochs are sufficient for the model to converge due to the high quality of the data, the large size of the dataset, and good seasonality in the data. In other cases, we might have to train further and use more epochs:

ts_model.fit(2, lr=l_rate)
epochtrain_lossvalid_losstime
00.0001550.00016200:37
10.0000420.00003800:37
# the ground truth vs the predicted values by the trained model is visualized to check the quality of the trained model
ts_model.show_results(rows=5)
<Figure size 1000x1000 with 10 Axes>

Next, we will use show result to compare the actual vs the forecasted value to understand the performance of the model. The value on the top of the left side of the graph signifies the actual target variable for forecasting, denoting the value after the end of the sequence length, whereas the value on the top of the corresponding right side graph signifies the forecasted value by the trained model. The x-axis delineates the data, organized in batches, with each ticker at 6-day interval, and the y-axis represents the normalized values of the power consumption variable. The plot reveals that the ground truths are close to the forecasted values, indicating a good fit. This is further validated by checking the model score.

# check the trained model score
ts_model.score()
0.9987402371186077

Power consumption forecast & validation

Now to ensure the model's effectiveness, first the trained model is utilized to forecast power consumption, followed by validation against the actual power consumption data.

Forecasting using the trained timeseries model

Once the model is trained, the predict function is used to forecast for a period of the next 144 time steps after the last recorded time step in the training dataset. Here the model utilizes the same training dataset during the forecasting process. Specifically, the model extracts the last set of data points equivalent to the specified sequence length from the trailing portion of the dataset to predict the user-specified number of future data points. So, it will forecast 144 values for the day of 30th December, at every 10 minutes of power consumption, starting on 00:00, 00:10, 00:20 and so on, till 23:50 of the same day.

# Here the forecast is returned as a dataframe, since it is non spatial data, mentioned in the 'prediction_type'  
sdf_forecasted_univar = ts_model.predict(train, prediction_type='dataframe', number_of_predictions=test_size)
# checking the final forecasted result returned by the model
sdf_forecasted_univar.tail(2)
DateTimeTemperatureHumidityWind Speedgeneral diffuse flowsdiffuse flowsZone 1 Power ConsumptionZone 2 Power ConsumptionZone 3 Power ConsumptionTotal Power ConsumptionMonthweekdayhourTotal Power Consumption_results
524142017-12-30 23:40NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN68229.654023
524152017-12-30 23:50NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN66733.589550

Estimate model metric for actual vs. forecast validation

The accuracy of the forecasted values is measured by comparing the forecasted values against the actual values of the 144 time steps set aside at the beginning.

# Formating the forecasted result into actual vs the predicted columns
sdf_forecasted = sdf_forecasted_univar.tail(test_size).copy()
sdf_forecasted = sdf_forecasted[['DateTime','Total Power Consumption_results']]
sdf_forecasted['Actual_Total Power Consumption'] = test['Total Power Consumption'].values
sdf_forecasted = sdf_forecasted.set_index(sdf_forecasted.columns[0])
sdf_forecasted.head()
Total Power Consumption_resultsActual_Total Power Consumption
DateTime
2017-12-30 00:0065264.37313965061.74921
2017-12-30 00:1063824.83273563079.20846
2017-12-30 00:2062416.71620661256.28975
2017-12-30 00:3061075.37577460136.79086
2017-12-30 00:4059815.54492058771.21664
sdf_forecasted.shape
(144, 2)
# Bi-LSTM
r2_test = r2_score(sdf_forecasted['Actual_Total Power Consumption'],sdf_forecasted['Total Power Consumption_results'])
print('R-Square: ', round(r2_test, 2))
R-Square:  0.96

A considerably high r-squared value indicates a high similarity between the forecasted and the actual sales values.

Actual vs. forecast visualization

Finally, for measuring the trained model's performance, the actual and forecasted values are plotted to visualize their distribution over the 144 timesteps. This enables a visual comparison between forecasted and observed data, facilitating a quick assessment of the forecasting model's accuracy.

sdf_forecasted.head(2)
Total Power Consumption_resultsActual_Total Power Consumption
DateTime
2017-12-30 00:0065264.37313965061.74921
2017-12-30 00:1063824.83273563079.20846
#sdf_forecasted = sdf_forecasted.reset_index()
sdf_forecasted['DateTime'] = (sdf_forecasted.index).to_timestamp()
sdf_forecasted.set_index('DateTime', inplace=True)
plt.figure(figsize=(10, 6))
plt.plot(sdf_forecasted.index, sdf_forecasted['Total Power Consumption_results'], label='Total Power Consumption_results')
plt.plot(sdf_forecasted.index, sdf_forecasted['Actual_Total Power Consumption'], label='Actual_Total Power Consumption')
plt.xlabel('DateTime')
plt.ylabel('Power Consumption')
plt.title('Power Consumption Results')
plt.legend()
plt.show()
<Figure size 1000x600 with 1 Axes>

The graphs indicate that the forecast is quite impressive, especially considering it's based on a univariate time series spanning 144 future time steps. Let's explore if the model could be further enhanced by incorporating multivariate data and employing additional methods.

Multi-step multivariate forecasting

Multivariate forecasting involves using multiple time series variables (e.g., historical power consumption, temperature, humidity, etc.) to make predictions. This allows the model to capture more complex relationships and dependencies; however, it can also be more computationally intensive. Multi-Step forecasting methods involve predicting multiple future time steps at once. For instance, forecasting the power consumption for the next several timesteps simultaneously at one go.

Here the Multi-Step Multivariate Forecasting method combines both the multi-step and multivariate approaches, where multiple future time steps are forecasted using a model using multiple explanatory variables.

Data processing

Data processing for timeseries consists of first splitting the dataset into a training dataset and a testing dataset as follows:

Train - Test split of timeseries dataset

As explained earlier, we will set aside the 144 (6 x 24) data points as the test set, while the remaining data will be used for training.

test_size = 144
city_power_consumption_df.shape
(52416, 13)
train, test = train_test_split(city_power_consumption_df, test_size = test_size, shuffle=False)
train.tail(2)
DateTimeTemperatureHumidityWind Speedgeneral diffuse flowsdiffuse flowsZone 1 Power ConsumptionZone 2 Power ConsumptionZone 3 Power ConsumptionTotal Power ConsumptionMonthweekdayhour
522702017-12-29 23:40:0013.2753.810.0770.0550.09329067.6806125701.1353213207.2028867976.01881DecemberFriday23.0
522712017-12-29 23:50:0013.2753.740.0790.0590.06328544.4866925126.7259913017.0468266688.25950DecemberFriday23.0
test.head(2)
DateTimeTemperatureHumidityWind Speedgeneral diffuse flowsdiffuse flowsZone 1 Power ConsumptionZone 2 Power ConsumptionZone 3 Power ConsumptionTotal Power ConsumptionMonthweekdayhour
522722017-12-30 00:00:0013.1752.670.0770.0620.12627692.7756724611.2304412757.743165061.74921DecemberSaturday0.0
522732017-12-30 00:10:0013.0752.670.0770.0370.11126792.3954423874.8082212412.004863079.20846DecemberSaturday0.0
train.columns
Index(['DateTime', 'Temperature', 'Humidity', 'Wind Speed',
       'general diffuse flows', 'diffuse flows', 'Zone 1 Power Consumption',
       'Zone 2  Power Consumption', 'Zone 3  Power Consumption',
       'Total Power Consumption', 'Month', 'weekday', 'hour'],
      dtype='object')

Model building

Once the dataset is divided into the training and testing datasets, the training data is ready to be used for modelling.

Data Preparation

Next we will be using the additional multivariate of Temperature, Humidity, Wind Speed, general diffuse flows, and diffuse flows, combined with related datetime information of month, weekday, and hour. Of these, month and weekday are used as categorical variables. As we did earlier, we will forecast the 144 timesteps of future total power consumption or electricity usage for every 10 minutes based on historical data, using these explanatory variables.

The preprocessing of the data is done by the prepare_tabulardata method from the arcgis.learn module in the ArcGIS API for Python.

Here, the additional parameter explanatory_variables will be used along with the parameters used earlier.

This function will take either a non spatial dataframe, a feature layer, or a spatial dataframe containing the dataset as input and will return a TabularDataObject that can be fed into the model. Here we are using a non spatial dataframe.

The additional input parameter required for the tool is:

explanatory_variables : We will pass the selected multiple variables in a list, along with declaring the relevant categorical variables    

The preprocessor is used for scaling the data, which usually improves the fit of the model.

# multistep multivariate
preprocessors = [("Temperature","Humidity","Wind Speed","general diffuse flows","diffuse flows",
                  "Total Power Consumption", MinMaxScaler())]
data = prepare_tabulardata(train, 
                           variable_predict="Total Power Consumption",                           
                           explanatory_variables=["Temperature","Humidity","Wind Speed",
                                                  "general diffuse flows","diffuse flows",('Month',True),
                                                  ('weekday',True), 'hour'],
                           index_field="DateTime", preprocessors=preprocessors)
C:\Users\sup10432\AppData\Local\ESRI\conda\envs\pro3.3_automl_QA_jan24\lib\site-packages\arcgis\learn\_utils\tabular_data.py:1871: UserWarning:

Dataframe is not spatial, Rasters and distance layers will not work

# Visualize the data distibution of all the variables 
data.show_batch(graph=True)
<Figure size 2500x4500 with 9 Axes>
data.show_batch()
HumidityMonthTemperatureTotal Power ConsumptionWind Speeddiffuse flowsgeneral diffuse flowshourweekday
1977078.8May18.8653258.857544.91685.30094.8007.0Thursday
2806177.2July25.66116044.383234.9130.2070.23420.0Friday
3321471.8August27.7591241.082044.923163.800700.00015.0Saturday
3477581.0August27.2376922.195754.920244.300398.20011.0Wednesday
5022870.6December15.0086624.923610.0820.1450.05119.0Friday

The sequence length used is 288, the same as earlier, which is the previous two days of power consumption data.

As explained earlier, with this sequence length, we use the show_batch() function for visualization. However, for multivariate modeling, the show_batch function is currently experimental, so only the graph of the forecasting variable (blue) is appropriate, and you can ignore the explanatory variable graphs, which will be updated soon. Notably, the value on the top of the graph signifies the target variable for forecasting, denoting the value after the end of the respective sequence lengths. This value serves as the dependent variable during the training of the time series model.

# half of seq len to be predicted, so if the test size is 144, then 288 should be seq len
seq_len = 288
data.show_batch(rows=4,seq_len=seq_len)
<Figure size 1000x1000 with 16 Axes>

Multi-step model initialization

Along with the sequence length and model architecture parameters we used earlier, we will also pass the additional parameter of multistep=True for initializing the model. For the model architecture, we will use InceptionTime which is a backbone specifically designed for time series.

# multistep
ts_model = TimeSeriesModel(data, seq_len=seq_len, model_arch='InceptionTime',multistep=True)

Learning rate search

# Finding the learning rate for training the model
l_rate = ts_model.lr_find()
l_rate
<Figure size 640x480 with 1 Axes>
0.001445439770745928

Model training

Finally, the model is now ready for training. To train the model, the model.fit method is called and provided with the number of epochs for training and the estimated learning rate suggested by lr_find in the previous step. As earlier, we will train it for two epochs.

ts_model.fit(2, lr=l_rate)
epochtrain_lossvalid_losstime
00.0025950.00239100:53
10.0010770.00076700:54
# the ground truth vs the predicted values by the trained model is visualized to check the quality of the trained model
ts_model.show_results(rows=5)
<Figure size 1000x1000 with 10 Axes>

Next show_result is used to visualize and compare the actual vs the forecasted values to understand the model's performance. However, for multivariate modeling, the show_result function is currently experimental. Therefore, only the values displayed at the top of the graphs are appropriate, while the graphs themselves can be disregarded. They will be updated soon. The value on the top of the left column graphs signifies the actual target variable for forecasting, denoting the value after the end of the sequence length, whereas the value on the top of the corresponding right column graphs signifies the forecasted value by the trained model. The x-axis delineates the data, organized in batches, with each ticker at 6-day interval. The plot reveals that the ground truths are considerably close to the forecasted values, indicating a good fit. This is further validated by checking the model score.

ts_model.score()
0.9726143454636047

Power consumption forecast & validation

Now as earlier, to ensure the model's effectiveness, first the trained model is utilized to forecast power consumption, followed by validation against the actual power consumption data.

Forecasting using the trained timeseries model

Once the model is trained, the predict function is used to forecast for a period of the next 144 time steps after the last recorded time steps in the training dataset. In cases of multi-step forecasting, we do not need to specify the number of future timesteps to forecast, and the predict function will automatically predict half of the sequence length used while preprocessing the data. The sequence length should be chosen with this in mind

Here, the model utilizes the same training dataset during the forecasting process and will use the last set of data points equivalent to the specified sequence length from the tail to predict future data points. So, it will forecast for the day of December 30th, at every 10 minutes of power consumption, starting on 00:00, 00:10, 00:20, etc., until 23:50 of the same day.

# multistep - half of sequence length will be forecasted and the it is returned as a dataframe
sdf_forecasted = ts_model.predict(train,prediction_type='dataframe')
sdf_forecasted.tail()
DateTimeTemperatureHumidityWind Speedgeneral diffuse flowsdiffuse flowsZone 1 Power ConsumptionZone 2 Power ConsumptionZone 3 Power ConsumptionTotal Power ConsumptionMonthweekdayhourTotal Power Consumption_results
524112017-12-30 23:10NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN70913.327861
524122017-12-30 23:20NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN70036.664752
524132017-12-30 23:30NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN68180.983745
524142017-12-30 23:40NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN66850.461512
524152017-12-30 23:50NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN63218.897502
sdf_forecasted.shape
(52416, 14)
train.shape
(52272, 13)
test.shape
(144, 13)
test.tail()
DateTimeTemperatureHumidityWind Speedgeneral diffuse flowsdiffuse flowsZone 1 Power ConsumptionZone 2 Power ConsumptionZone 3 Power ConsumptionTotal Power ConsumptionMonthweekdayhour
524112017-12-30 23:10:007.01072.40.0800.0400.09631160.4562726857.3182014780.3121272798.08659DecemberSaturday23.0
524122017-12-30 23:20:006.94772.60.0820.0510.09330430.4182526124.5780914428.8115270983.80786DecemberSaturday23.0
524132017-12-30 23:30:006.90072.80.0860.0840.07429590.8745225277.6925413806.4825968675.04965DecemberSaturday23.0
524142017-12-30 23:40:006.75873.00.0800.0660.08928958.1749024692.2368813512.6050467163.01682DecemberSaturday23.0
524152017-12-30 23:50:006.58074.10.0810.0620.11128349.8098924055.2316713345.4982065750.53976DecemberSaturday23.0

Estimate model metric for actual vs. forecast validation

The accuracy of the forecasted values is measured by comparing the forecasted values against the actual values of the 144 time steps set aside at the beginning.

sdf_forecasted_slice_test = sdf_forecasted.tail(test_size).copy()
sdf_forecasted_slice_test = sdf_forecasted_slice_test[['DateTime','Total Power Consumption_results']]
sdf_forecasted_slice_test['DateTime'] = pd.to_datetime(sdf_forecasted_slice_test['DateTime'].astype(str))
sdf_forecasted_slice_test['DateTime'] = pd.to_datetime(sdf_forecasted_slice_test['DateTime'])
sdf_forecasted_slice_test.tail(2)
DateTimeTotal Power Consumption_results
524142017-12-30 23:40:0066850.461512
524152017-12-30 23:50:0063218.897502
sdf_forecasted_slice_test.shape
(144, 2)
sdf_forecasted_slice_test.info()
<class 'pandas.core.frame.DataFrame'>
Index: 144 entries, 52272 to 52415
Data columns (total 2 columns):
 #   Column                           Non-Null Count  Dtype         
---  ------                           --------------  -----         
 0   DateTime                         144 non-null    datetime64[ns]
 1   Total Power Consumption_results  144 non-null    float64       
dtypes: datetime64[ns](1), float64(1)
memory usage: 3.4 KB
new_forecast = test[['Total Power Consumption','DateTime']]
new_forecast.tail(2)
Total Power ConsumptionDateTime
5241467163.016822017-12-30 23:40:00
5241565750.539762017-12-30 23:50:00
df_merge = pd.merge(sdf_forecasted_slice_test, new_forecast)
df_merge.head(5)
DateTimeTotal Power Consumption_resultsTotal Power Consumption
02017-12-30 00:00:0063282.27658165061.74921
12017-12-30 00:10:0062549.59480463079.20846
22017-12-30 00:20:0061420.43448561256.28975
32017-12-30 00:30:0059806.53411060136.79086
42017-12-30 00:40:0056842.92705658771.21664
# bi-lstm
r2_test = r2_score(df_merge['Total Power Consumption'],df_merge['Total Power Consumption_results'])
print('R-Square: ', round(r2_test, 2))
R-Square:  0.97

The r-squared value has improved compared to the one step univariate method.

One-step multivariate forecasting

Finally, we will try one more method of multivariate forecasting but with one step instead of multistep, to see if this method performs better than the multi-step forecasting, while using multiple variables.

This combines both the one-step and multivariate approaches, which involves predicting a single timestep in future in a time series, but instead of using just one variable's historical data, we will consider the multiple variables as used in the previous step. This means that it considers the past values of several different factors of temperature, humidity, wind speed etc., when making a single-step prediction. As suggested earlier, a multivariate approach is useful when there are multiple variables that may collectively influence the future value being predicted.

Data processing

Data processing for timeseries consists of first formatting the input dataframe to be used for forecasting using a One step Multivariate model, followed by splitting the dataset into training and testing datasets.

Formatting the input dataframe

While forecasting using the trained one-step multivariate forecasting model the input dataframe must be formatted appropriately. For this dataframe, we need to fill the to-be-forecasted variable with NaN values for the number of timesteps to be predicted. Additionally, the corresponding multivariate data for those future timesteps should be present for forecasting.

# Formatting input dataframe to be used for forecasting uisng One step Multivariate
city_power_consumption_df['pred_Total Power Consumption'] = city_power_consumption_df['Total Power Consumption']
city_power_consumption_df.iloc[-test_size:, city_power_consumption_df.columns.get_loc('pred_Total Power Consumption')] = np.nan

Train - Test split of timeseries dataset

First, we will set aside the 144 (6 x 24) data points as the test set, while the remaining data will be used for training. Here the training data will be the same as earlier; however, the test data will have NaN values for the forecast variable.

train, test = train_test_split(city_power_consumption_df, test_size = test_size, shuffle=False)
# visualize the train data
train.tail(2)
DateTimeTemperatureHumidityWind Speedgeneral diffuse flowsdiffuse flowsZone 1 Power ConsumptionZone 2 Power ConsumptionZone 3 Power ConsumptionTotal Power ConsumptionMonthweekdayhourpred_Total Power Consumption
522702017-12-29 23:40:0013.2753.810.0770.0550.09329067.6806125701.1353213207.2028867976.01881DecemberFriday23.067976.01881
522712017-12-29 23:50:0013.2753.740.0790.0590.06328544.4866925126.7259913017.0468266688.25950DecemberFriday23.066688.25950
train.columns
Index(['DateTime', 'Temperature', 'Humidity', 'Wind Speed',
       'general diffuse flows', 'diffuse flows', 'Zone 1 Power Consumption',
       'Zone 2  Power Consumption', 'Zone 3  Power Consumption',
       'Total Power Consumption', 'Month', 'weekday', 'hour',
       'pred_Total Power Consumption'],
      dtype='object')
# visualize the test data
test.head(2)
DateTimeTemperatureHumidityWind Speedgeneral diffuse flowsdiffuse flowsZone 1 Power ConsumptionZone 2 Power ConsumptionZone 3 Power ConsumptionTotal Power ConsumptionMonthweekdayhourpred_Total Power Consumption
522722017-12-30 00:00:0013.1752.670.0770.0620.12627692.7756724611.2304412757.743165061.74921DecemberSaturday0.0NaN
522732017-12-30 00:10:0013.0752.670.0770.0370.11126792.3954423874.8082212412.004863079.20846DecemberSaturday0.0NaN

We can see the test data that we will use in the predict function for forecasting has the forecast variable filled with NaN values, with the corresponding multivariate for the future timesteps.

Model building

Once the dataset is divided into the training and test datasets, the training data is ready to be used for modeling.

Data Preparation

Here we will be using the same additional multivariate as used before, with month and weekday as categorical variables. And as earlier we will forecast the 144 timesteps of future electricity usage for every 10 minutes using these explanatory variables.

# one step multivariate
preprocessors = [("Temperature","Humidity","Wind Speed","general diffuse flows","diffuse flows", 
                  'hour',"pred_Total Power Consumption", MinMaxScaler())]

data = prepare_tabulardata(train, variable_predict="pred_Total Power Consumption",                            
                           explanatory_variables=["Temperature","Humidity","Wind Speed", "general diffuse flows",
                                                  "diffuse flows", ('Month',True), ('weekday',True),"hour"],
                           index_field="DateTime", preprocessors=preprocessors)
C:\Users\sup10432\AppData\Local\ESRI\conda\envs\pro3.3_automl_QA_jan24\lib\site-packages\arcgis\learn\_utils\tabular_data.py:1871: UserWarning:

Dataframe is not spatial, Rasters and distance layers will not work

# Visualize the data distibution of all the variables
data.show_batch(graph=True)
<Figure size 2500x4500 with 9 Axes>
data.show_batch()
HumidityMonthTemperatureWind Speeddiffuse flowsgeneral diffuse flowshourpred_Total Power Consumptionweekday
1977078.8May18.864.91685.30094.8007.053258.85754Thursday
2806177.2July25.664.9130.2070.23420.0116044.38323Friday
3321471.8August27.754.923163.800700.00015.091241.08204Saturday
3477581.0August27.234.920244.300398.20011.076922.19575Wednesday
5022870.6December15.000.0820.1450.05119.086624.92361Friday

Using this sequence length, we use the show_batch() function for visualization. As suggested earlier, here only the graph of the forecasting variable (blue) is appropriate, and you can ignore the explanatory variable graphs.

seq_len = 288
data.show_batch(rows=4,seq_len=seq_len)
<Figure size 1000x1000 with 16 Axes>

One-step multivariate model initialization

Next, we will initialize a one-step model with the input parameter of data, sequence length, and model architecture. For the model architecture, we will use InceptionTime which is a specifically designed backbone for time series. Users can experiment with the various options available. The difference here is in the preparation of the data, which has the multivariate.

# one step - multivariate
ts_model = TimeSeriesModel(data, seq_len=seq_len, model_arch='InceptionTime')

Learning rate search

# Finding the learning rate for training the model
l_rate = ts_model.lr_find()
l_rate
<Figure size 640x480 with 1 Axes>
0.00019054607179632462

Model training

Finally, we will train the model using model.fit, providing the number of epochs and the estimated learning rate suggested by lr_find in the previous step. As previously, we will train it for two epochs.

ts_model.fit(2, lr=l_rate)
epochtrain_lossvalid_losstime
00.0021170.00172100:55
10.0007330.00049100:55
# the ground truth vs the predicted values by the trained model is visualized to check the quality of the trained model
ts_model.show_results(rows=5)
<Figure size 1000x1000 with 10 Axes>

show_results is used to visualize and compare the actual vs the forecasted value. This function is experimental and only the values on the top of the graphs are appropriate; you can ignore the graphs. We can see the ground truths are close to the forecasted values by the trained model, indicating a good fit. This is further validated by checking the model score.

ts_model.score()
0.9838986613728246

Power consumption forecast & validation

As earlier to ensure the model's effectiveness, first the trained model is utilized to forecast power consumption, followed by validation against the actual power consumption data.

Forecasting using the trained timeseries model

The predict function is used again to forecast for a period of the next 144 time steps after the last recorded time steps in the training dataset. In the predict function, we need to input the dataset that we prepared earlier, with the to be forecasted rows set to NaN. With these NaN values, we do not need to specify the number of time steps to forecast, as it will automatically forecast for the NaN filled rows. It will forecast for the day of December 30th, at every 10 minutes of power consumption, starting on 00:00, 00:10, 00:20, etc., until 23:50 of the same day.

# forecasted values is returned as a dataframe
sdf_forecasted = ts_model.predict(city_power_consumption_df, prediction_type='dataframe')
train.tail(2)
DateTimeTemperatureHumidityWind Speedgeneral diffuse flowsdiffuse flowsZone 1 Power ConsumptionZone 2 Power ConsumptionZone 3 Power ConsumptionTotal Power ConsumptionMonthweekdayhourpred_Total Power Consumption
522702017-12-29 23:40:0013.2753.810.0770.0550.09329067.6806125701.1353213207.2028867976.01881DecemberFriday23.067976.01881
522712017-12-29 23:50:0013.2753.740.0790.0590.06328544.4866925126.7259913017.0468266688.25950DecemberFriday23.066688.25950
sdf_forecasted.tail(2)
DateTimeTemperatureHumidityWind Speedgeneral diffuse flowsdiffuse flowsZone 1 Power ConsumptionZone 2 Power ConsumptionZone 3 Power ConsumptionTotal Power ConsumptionMonthweekdayhourpred_Total Power Consumptionpred_Total Power Consumption_results
524142017-12-30 23:40:006.75873.00.0800.0660.08928958.1749024692.2368813512.6050467163.01682DecemberSaturday23.0NaN65436.404555
524152017-12-30 23:50:006.58074.10.0810.0620.11128349.8098924055.2316713345.4982065750.53976DecemberSaturday23.0NaN63924.179564

Estimate model metric for actual vs. forecast validation

The accuracy of the forecasted values is measured by comparing the forecasted values against the actual values of the 144 time steps set aside earlier.

sdf_forecasted_slice = sdf_forecasted.tail(test_size).copy()
sdf_forecasted_final = sdf_forecasted_slice.loc[:, ['DateTime','Total Power Consumption','pred_Total Power Consumption_results']]
sdf_forecasted_final.head(2)
DateTimeTotal Power Consumptionpred_Total Power Consumption_results
522722017-12-30 00:00:0065061.7492162768.194411
522732017-12-30 00:10:0063079.2084661810.423850
r2_test = r2_score(sdf_forecasted_final['Total Power Consumption'],sdf_forecasted_final['pred_Total Power Consumption_results'])
print('R-Square: ', round(r2_test, 2))
R-Square:  0.98

The r-squared value has improved further compared to multi-step multivariate method. This suggests that both the one-step and multi-step multivariate approaches outperform the univariate method, which is expected.

Actual vs. forecast visualization

Finally, the actual and forecasted values are plotted to visualize their distribution. The plot showing forecasted values and actual values are a close match.

# Plot the "Total Power Consumption" and "pred_Total Power Consumption_results" columns aagint the index
sdf_forecasted_final.plot(y=['Total Power Consumption', 'pred_Total Power Consumption_results'], kind="line", figsize=(20, 5))
plt.ylabel("Total Power")
plt.title( 'Total Power Consumption')
# Display the plot
plt.show()
<Figure size 2000x500 with 1 Axes>

Conclusion

In this deep learning time series notebook, we utilized newly implemented methods from the arcgis.learn library to forecast power consumption for the city of Tetouan at 10-minute intervals for an entire day. This involved predicting 144 future time steps.

These approaches included one-step univariate, one-step multivariate, and multi-step multivariate methods. The notebook provided detailed explanations for each methodology, including data processing and application for time series forecasting.

Further, the notebook introduced several novel deep learning architectures, including some specially designed for modelling timeseries data, that significantly enhanced the model's performance, evident in the high accuracy of the forecasted values compared to the actual values.

Overall, this notebook demonstrated the improvement of both multivariate approaches over the univariate approach, aligning with expectations.

Time series modeling is typically intricate, often requiring fine-tuning of numerous hyperparameters to achieve accurate results. However, this current implementation in the time series module encapsulates and simplifies these complexities, offering users an intuitive and flexible approach.

Data resources

DatasetCitationLink
Power consumption of Tetouan citySalam, A., & El Hibaoui, A. (2018, December). Comparison of Machine Learning Algorithms for the Power Consumption Prediction:-Case Study of Tetouan city“. In 2018 6th International Renewable and Sustainable Energy Conference (IRSEC) (pp. 1-5). IEEE.https://archive.ics.uci.edu/dataset/849/power+consumption+of+tetouan+city
                                                       ------End-----

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.