Prediction of energy generation from Solar Photovoltaic Power Plants using weather variables

Introduction

Recently, there has been a great emphasis on reducing the carbon footprint of our cities by moving away from fossil fuels to renewable energy sources. City governments across the world, in this case the City of Calgary, Canada, are leading this change by becoming energy independent through solar power plants, either implemented on rooftops or within city utility sites.

This notebook aims to further aid this move to renewable solar energy by predicting the annual solar energy likely to be generated by a solar power station through local weather information and site characteristics. The hypothesis proposed by this notebook is that various weather parameters, such as temperature, wind speed, vapor pressure, solar radiation, day length, precipitation, snowfall, along with the altitude of a solar power station site, will impact the daily generation of solar energy.

Accordingly, these variables are used to train a model on actual solar power generated by solar power stations located in Calgary. This trained model will then be used to predict solar generation from potential solar power plant sites. Beyond the weather and altitude variables, the total energy generated from a solar station will also depend on the capacity of that station. For instance, a 100kwp solar plant will generate more energy than a 50kwp plant, and the final output will therefore also take into consideration the capacity of each solar power plant.

Imports

%matplotlib inline
import matplotlib.pyplot as plt

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

from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import Normalizer
from sklearn.preprocessing import MinMaxScaler  
from sklearn.compose import make_column_transformer 
from sklearn.metrics import r2_score

import arcgis
from arcgis.gis import GIS
from arcgis.learn import FullyConnectedNetwork, MLModel, prepare_tabulardata

Connecting to ArcGIS

gis = GIS('home')

Accessing & Visualizing datasets

The primary data used for this sample are as follows:

Out of the several solar photovoltaic power plants in the City of Calgary, 11 were selected for the study. The dataset contains two components:

1) Daily solar energy production for each power plant from September 2015 to December 2019.

2) Corresponding daily weather measurements for the given sites.

The datasets were obtained from multiple sources, as mentioned here (Data resources), and preprocessed to obtain the main dataset used in this sample. Two feature layers were subsequently created from this dataset, one dataset for training and the other for validating.

Training Set

The training dataset consists of data from 10 solar sites for training the model. The feature layer containing the data is accessed here from Arcgis portal and visualized as follows:

# Access Solar Dataset feature layer for Training, without the Southland Solar Plant which is hold out for validation
calgary_no_southland_solar = gis.content.get('adaead8cb3174ac6a89f0c14ae70aadd')
calgary_no_southland_solar
calgary_no_southland_solar
Feature Layer Collection by api_data_owner
Last Modified: July 07, 2020
0 comments, 2 views
# Access the layer from the feature layer
calgary_no_southland_solar_layer = calgary_no_southland_solar.layers[0]
# Plot location of the 10 Solar sites in Calgary to be used for training
m1 = gis.map('calgary', zoomlevel=10)
m1.add_layer(calgary_no_southland_solar_layer)
m1

The map above shows the 10 power plant locations that are used for collecting the training data.

# Visualize the dataframe
calgary_no_southland_solar_layer_sdf = calgary_no_southland_solar_layer.query().sdf
calgary_no_southland_solar_layer_sdf=calgary_no_southland_solar_layer_sdf[['FID','date','ID','solar_plan','altitude_m',
                                                                           'latitude','longitude','wind_speed','dayl__s_',
                                                                           'prcp__mm_d','srad__W_m_','swe__kg_m_', 'tmax__deg',
                                                                           'tmin__deg','vp__Pa_','kWh_filled','capacity_f',
                                                                           'SHAPE']]
calgary_no_southland_solar_layer_sdf.head()
FIDdateIDsolar_planaltitude_mlatitudelongitudewind_speeddayl__s_prcp__mm_dsrad__W_m_swe__kg_m_tmax__degtmin__degvp__Pa_kWh_filledcapacity_fSHAPE
012017-12-24355827Glenmore Water Treatment Plant109551.003078-114.1005717.20467027648.01108.80000312-10.5-21.01201.2423570.000177{"x": -12701617.407282012, "y": 6621838.159138...
122017-12-25355827Glenmore Water Treatment Plant109551.003078-114.1005713.38523527648.01115.19999712-18.0-29.5402.4777140.000354{"x": -12701617.407282012, "y": 6621838.159138...
232017-12-26355827Glenmore Water Treatment Plant109551.003078-114.1005715.07631627648.00118.40000212-20.0-32.0403.7130710.000530{"x": -12701617.407282012, "y": 6621838.159138...
342017-12-27355827Glenmore Water Treatment Plant109551.003078-114.1005715.61762327648.0096.00000012-18.0-26.5804.9484290.000707{"x": -12701617.407282012, "y": 6621838.159138...
452017-12-28355827Glenmore Water Treatment Plant109551.003078-114.1005712.56151227648.00118.40000212-17.0-28.5406.1837860.000883{"x": -12701617.407282012, "y": 6621838.159138...

In the table above, each row represents each individual day from September 2015 to December 2019, with the corresponding date shown in the field named date and the name of the solar site in the field named solar_plan.

The primary information consists of the daily generation of energy in kilowatt-hour(KWh) given here in the field name kWh_filled for each of the selected 10 solar photovoltaic power plants in the City of Calgary. The field capacity_f indicates the capacity factor, which is obtained after normalizing the kWh_filled by the peak capacity of each solar photovoltaic sites, and will be used here as the dependent variable.

In addition, it also contains data about weather variables for each day for the related solar plant, all of which, except wind speed, were obtained from MODIS, Daymet observations. These variables are as follows:

  • wind_speed : wind speed(m/sec)
  • dayl_s : Day length (sec/day)
  • prcp__mm_d : Precipitation (mm/day)
  • srad_W_m : Shortwave radiation (W/m^2)
  • swe_kg_m : Snow water equivalent (kg/m^2)
  • tmax__deg : Maximum air temperature (degrees C)
  • tmin__deg : Minimum air temperature (degrees C)
  • vp_Pa : Water vapor pressure (Pa)

To understand the distribution of the variables over the last few years and their respective relationship with the dependent variable of daily energy produced for a station, data from one of the stations is plotted as follows:

# plot and Visualize the variables from the training set for one solar station - Hillhurst Sunnyside Community Association 
hillhurst_solar = calgary_no_southland_solar_layer_sdf[calgary_no_southland_solar_layer_sdf['solar_plan']=='Hillhurst Sunnyside Community Association'].copy()
hillhurst_datetime = hillhurst_solar.set_index(hillhurst_solar['date'])
hillhurst_datetime = hillhurst_datetime.sort_index() 
for i in range(7,hillhurst_datetime.shape[1]-1):
        plt.figure(figsize=(20,3))
        plt.title(hillhurst_datetime.columns[i])
        plt.plot(hillhurst_datetime[hillhurst_datetime.columns[i]])
        plt.show()
<Figure size 1440x216 with 1 Axes><Figure size 1440x216 with 1 Axes><Figure size 1440x216 with 1 Axes><Figure size 1440x216 with 1 Axes><Figure size 1440x216 with 1 Axes><Figure size 1440x216 with 1 Axes><Figure size 1440x216 with 1 Axes><Figure size 1440x216 with 1 Axes><Figure size 1440x216 with 1 Axes><Figure size 1440x216 with 1 Axes>

In the plots above, it can be seen that each of the variables has a high seasonality, and it seems that there is a relationship between the dependent variable kWh_filled and the explanatory variables. As such, a correlation plot should be created to check the correlation between the variables.

# checking the correlation matrix between the predictors and the dependent variable of capacity_factor
corr_test = calgary_no_southland_solar_layer_sdf.drop(['FID','date','ID','latitude','longitude','solar_plan','kWh_filled'], axis=1)
corr = corr_test.corr()
corr.style.background_gradient(cmap='Greens').set_precision(2)
altitude_mwind_speeddayl__s_prcp__mm_dsrad__W_m_swe__kg_m_tmax__degtmin__degvp__Pa_capacity_f
altitude_m1.00-0.010.040.010.030.020.020.020.020.03
wind_speed-0.011.00-0.41-0.17-0.260.02-0.03-0.06-0.13-0.24
dayl__s_0.04-0.411.000.200.78-0.180.720.730.600.77
prcp__mm_d0.01-0.170.201.00-0.18-0.07-0.030.100.20-0.04
srad__W_m_0.03-0.260.78-0.181.000.040.690.500.280.82
swe__kg_m_0.020.02-0.18-0.070.041.00-0.45-0.48-0.46-0.19
tmax__deg0.02-0.030.72-0.030.69-0.451.000.930.750.75
tmin__deg0.02-0.060.730.100.50-0.480.931.000.850.65
vp__Pa_0.02-0.130.600.200.28-0.460.750.851.000.45
capacity_f0.03-0.240.77-0.040.82-0.190.750.650.451.00

The resulting correlation plot shows that the variable of shortwave radiation per meter square (srad_W_m) has the largest correlation with the dependent variable of total solar energy produced expressed in terms of capacity factor(capacity_f). This is followed by the variable of day length(dayl_s), as longer days are likely to produce more solar energy. These two are closely followed by max(tmax__deg) and min(tmin__deg) daily temperatures, and lastly the remaining variables with weaker correlation values.

Validation Set

The validation set consists of daily solar generation data from September 2015 to December 2019 for one solar site, known as Southland Leisure Centre, and will be used to validate the trained model.

# Access the Southland Solar Plant Dataset feature layer for validation
southland_solar = gis.content.get('af78423949b94c1783fa43d707df6d45')
southland_solar
southland_solar
Feature Layer Collection by api_data_owner
Last Modified: April 27, 2020
0 comments, 5 views
# Access the layer from the feature layer
southland_solar_layer = southland_solar.layers[0]
#  Plot location of the Southalnd Solar site in Calgary to be used for validation
m1 = gis.map('calgary', zoomlevel=10)
m1.add_layer(southland_solar_layer)
m1
# visualize the southland dataframe here
southland_solar_layer_sdf = southland_solar_layer.query().sdf
southland_solar_layer_sdf.head(2)
FIDField1IDsolar_planaltitude_mlatitudelongitudewind_speeddayl__s_prcp__mm_d...tmin__degvp__Pa_kWh_filledcapacity_fGlobalIDCreationDateCreatorEditDateEditorSHAPE
012019-10-03164440Southland Leisure Centre110050.962485-114.1084725.33223940089.6015620...-3.0480309.6440.084326e9b0f671-d6ba-4560-b912-d635a0a129f82020-04-27 11:58:02.992000103arcgis_python2020-04-27 11:58:02.992000103arcgis_python{"x": -12702497.020502415, "y": 6614660.374377...
122019-10-04164440Southland Leisure Centre110050.962485-114.1084726.30482940089.6015620...-1.0560679.7850.1851277bde5210-a8c2-4731-9c23-e5f77c1ebc562020-04-27 11:58:02.992000103arcgis_python2020-04-27 11:58:02.992000103arcgis_python{"x": -12702497.020502415, "y": 6614660.374377...

2 rows × 23 columns

# check the total number of samples
southland_solar_layer_sdf.shape
(1590, 23)

Model Building

Once the training and the validation dataset have been processed and analyzed, they are ready to be used for modeling.

In this sample, two methods are used for modeling:

1) FullyConnectedNetwork - First a deep learning framework called FullyConnectedNetwork, available in the arcgis.learn module in ArcGIS API for Python, is used.

2) MLModel - In the second method, a regression model from scikit-learn is implemented via the MLModel framework in arcgis.learn. This framework can deploy any regression or classification model from the library by passing the name of the algorithm and its relevant parameters as keyword arguments.

Finally, performance between the two methods will be compared in terms of model training and validation accuracy.

Further details on FullyConnectedNetwork & MLModel are available here in the Deep Learning with ArcGIS section.

1 — FullyConnectedNetwork

This is an Artificial Neural Network model from the arcgis.learn module, which is used here for modeling.

Data Preprocessing

First, a list is made that consists of the feature data that will be used for predicting daily solar energy generation. By default, it will receive continuous variables, and in the case of categorical variables, the True value should be passed inside a tuple along with the variable. In this example, all of the variables are continuous.

# Here a list is created naming all fields containing the predictors from the input feature layer
X = ['altitude_m', 'wind_speed', 'dayl__s_', 'prcp__mm_d','srad__W_m_','swe__kg_m_','tmax__deg','tmin__deg','vp__Pa_']
# importing the libraries from arcgis.learn for data preprocessing
from arcgis.learn import prepare_tabulardata

Once the explanatory variables are identified, the main preprocessing 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 feature layer or a spatial dataframe containing the dataset as input and will return a TabularDataObject that can be fed into the model.

The input parameters required for the tool are:

  • input_features : feature layer or spatial dataframe containing the primary dataset
  • variable_predict : field name containing the y-variable from the input feature layer/dataframe
  • explanatory_variables : list of the field names as 2-sized tuples containing the explanatory variables as mentioned above
# precrocessing data using prepare data method - it handles imputing missing values, normalization and train-test split
data = prepare_tabulardata(calgary_no_southland_solar_layer,
                           'capacity_f',
                           explanatory_variables=X)
# visualizing the prepared data 
data.show_batch()
altitude_mcapacity_fdayl__s_prcp__mm_dsrad__W_m_swe__kg_m_tmax__degtmin__degvp__Pa_wind_speed
14610950.00805138707.1992190329.60000683.0-13.52004.242083
17010950.02592133177.601562370.40000203.5-2.55205.934033
39110950.27363757715.1992190515.200012026.54.54806.532143
52210950.01634332140.8007810147.1999970-11.5-20.01204.157886
82510950.01935429376.0000000102.400002410.01.56806.422836
.................................
839010510.28530756332.8007810460.799988027.08.55203.335872
846010510.23310354604.8007814300.799988026.510.012003.828789
877710510.28511657369.6015620537.599976027.55.53204.703076
903510960.00269227993.5996090134.3999940-5.5-30.0408.036874
905610960.12257230412.8007810121.59999804.5-6.04007.787962

64 rows × 10 columns

Model Initialization

Once the data has been prepared by the prepare_tabulardata method, it is ready to be passed to the ANN for training. First, the ANN, known as FullyConnectedNetwork, is imported from arcgis.learn and initialized as follows:

# importing the model from arcgis.learn
from arcgis.learn import FullyConnectedNetwork
# Initialize the model with the data where the weights are randomly allocated
fcn = FullyConnectedNetwork(data)
# searching for an optimal learning rate using the lr_find for passing it to the final model fitting 
fcn.lr_find()
<Figure size 432x288 with 1 Axes>
0.0005754399373371565

Here the suggested learning rate by the lr_find method is around 0.0005754. The automatic lr_finder will take a conservative estimate of the learning rate, but some experts can interpret the graph more appropriately and find a better learning rate to be used for final training of the model.

Model Training

Finally, the model is now ready for training. To train the model, the model.fit is called and provided with the number of epochs for training and the estimated learning rate suggested by lr_find in the previous step:

# the model is trained for 100 epochs 
fcn.fit(100,0.0005754399373371565)
epochtrain_lossvalid_losstime
00.0111530.00477600:01
10.0040830.00317500:01
20.0028930.00266500:01
30.0024980.00234000:01
40.0023570.00219400:01
50.0022790.00225800:01
60.0022660.00209700:01
70.0021510.00228900:01
80.0020760.00227500:01
90.0021140.00197300:01
100.0020950.00196900:01
110.0021280.00191800:01
120.0021310.00193600:01
130.0020780.00193200:01
140.0020490.00232600:01
150.0022050.00192700:01
160.0019950.00182600:01
170.0020920.00221000:01
180.0021260.00188900:01
190.0022250.00389900:01
200.0021480.00196600:01
210.0020380.00181400:01
220.0020350.00174500:01
230.0020270.00255400:01
240.0020280.00186600:01
250.0019940.00206400:01
260.0019600.00191300:01
270.0019610.00168700:01
280.0018640.00180900:01
290.0018460.00172000:01
300.0018380.00184600:01
310.0017730.00194700:01
320.0018410.00167300:01
330.0018570.00162000:01
340.0017980.00193100:01
350.0018980.00173500:01
360.0017650.00170200:01
370.0018200.00172600:01
380.0017860.00167200:01
390.0017120.00195800:01
400.0016870.00169500:01
410.0017940.00169500:01
420.0017910.00161300:01
430.0017090.00157900:01
440.0016760.00194400:01
450.0017000.00157300:01
460.0017500.00155700:01
470.0016830.00163700:01
480.0016210.00157900:01
490.0016630.00151600:01
500.0016540.00157800:01
510.0016420.00164000:01
520.0016270.00155600:01
530.0015530.00154000:01
540.0015540.00159200:01
550.0016040.00152700:01
560.0015750.00160400:01
570.0015920.00151800:01
580.0016480.00157300:01
590.0014960.00159900:01
600.0015460.00150400:01
610.0015550.00148900:01
620.0016020.00153300:01
630.0014960.00149600:01
640.0014760.00155200:01
650.0014550.00158300:01
660.0014820.00147400:01
670.0014580.00153300:01
680.0013980.00150400:01
690.0014450.00148000:01
700.0013880.00151400:01
710.0014590.00146300:01
720.0015380.00147200:01
730.0014240.00145200:01
740.0014340.00144100:01
750.0014480.00149200:01
760.0014450.00148900:01
770.0014410.00143400:01
780.0013610.00148500:01
790.0013710.00144200:01
800.0013920.00147400:01
810.0013530.00145200:01
820.0013850.00143900:01
830.0013550.00143300:01
840.0014270.00143600:01
850.0014050.00147300:01
860.0013680.00141900:01
870.0013270.00148700:01
880.0013810.00141400:01
890.0013490.00143800:01
900.0013410.00139900:01
910.0013610.00142800:01
920.0013480.00144200:01
930.0013810.00142200:01
940.0013700.00144700:01
950.0013410.00141000:01
960.0013970.00141900:01
970.0013510.00142700:01
980.0013560.00141800:01
990.0012890.00141200:01

The train_loss and valid loss are plotted to check whether the model is over-fitting. The resulting plot shows that the model has been trained well and that the losses are gradually decreasing, but not significantly.

# the train vs valid losses is plotted to check quality of the trained model
fcn.plot_losses()
<Figure size 432x288 with 1 Axes>

Finally, the training results are printed to assess the prediction on the test set by the trained model.

# the predicted values by the trained model is printed for the test set
fcn.show_results()
100.00% [6/6 00:00<00:00]
altitude_mcapacity_fdayl__s_prcp__mm_dsrad__W_m_swe__kg_m_tmax__degtmin__degvp__Pa_wind_speedprediction_results
010950.25931158752.0000000403.200012017.04.06806.6492750.261776
110950.29940758752.0000000425.600006019.55.57206.1357750.257786
210700.16372340435.1992190361.600006445.0-11.52006.3529460.109318
310950.01505227993.5996090118.40000245.0-7.53609.0147800.019792
410950.00000227648.0000001105.59999820-10.5-20.51207.204670-0.003998
510550.07407233868.8007810176.000000011.0-1.55606.2757430.077243

In the table above, the values predicted by the model when applied to the test set, prediction_results, are similar to the actual values of the test set, capacity_f.

As such, the model metrics of the trained model can be now estimated using the model.score function, which returns the r-squared metric of the fitted model.

# the model.score method from the tabular learner returns r-square
r_Square_fcn_test = fcn.score() 
print('r_Square_fcn_test: ', round(r_Square_fcn_test,5))
100.00% [912/912 00:07<00:00]
r_Square_fcn_test:  0.84533

The high r-square value indicates that the model has been trained well

Solar Energy Generation Forecast & Validation

The trained model(FullyConnectedNetwork) will now be used to predict the daily lifetime solar energy generation for the solar plant installed at the Southland Leisure Centre, since its installation in 2015. The aim is to validate the trained model and measure its performance of solar output estimation using only weather variables from the Southland Leisure Center.

Accordingly, the model.predict method from arcgis.learn is used with the daily weather variables as input for the mentioned site, ranging from September 2015 to December 2019, to predict daily solar energy output in KWh for that same time period. The predictors are automatically chosen from the input feature layer of southland_layer by the trained model without mentioning them explicitly, since their names are exactly same as those used to train the model.

# predicting using the predict function 
southland_solar_layer_predicted = fcn.predict(southland_solar_layer, output_layer_name='prediction_layer')
100.00% [1590/1590 00:13<00:00]
C:\Users\sup10432\.conda\envs\upsupervisedEnvAug11\lib\site-packages\arcgis\features\geo\_io\fileops.py:743: UserWarning: Discarding nonzero nanoseconds in conversion
  out_name=fc_name)
# print the predicted layer
southland_solar_layer_predicted
prediction_layer
Feature Layer Collection by arcgis_python
Last Modified: September 04, 2020
0 comments, 0 views
# Access & visualize the dataframe from the predicted layer 
test_pred_layer = southland_solar_layer_predicted.layers[0]
test_pred_layer_sdf = test_pred_layer.query().sdf
test_pred_layer_sdf.head()
FIDfid_1field1idsolar_planaltitude_mlatitudelongitudewind_speeddayl_s_...vp_pa_k_wh_fillecapacity_fglobal_idcreation_dcreatoredit_dateeditorpredictionSHAPE
014012016-07-05164440Southland Leisure Centre110050.962485-114.1084723.78757158060.800781...920593.2750.1615673cbc6ed1-6504-4f41-8e4b-b95c628160702020-04-27arcgis_python2020-04-27arcgis_python0.202823{"x": -12702497.020502415, "y": 6614660.374377...
124022016-07-06164440Southland Leisure Centre110050.962485-114.1084723.30231058060.800781...920575.3970.156699d19858dc-1caa-4893-9e98-1540753eec4a2020-04-27arcgis_python2020-04-27arcgis_python0.190378{"x": -12702497.020502415, "y": 6614660.374377...
234032016-07-07164440Southland Leisure Centre110050.962485-114.1084723.92360958060.800781...880886.4230.2414010ede54fa-3541-45be-9b64-5cdf259f69aa2020-04-27arcgis_python2020-04-27arcgis_python0.226812{"x": -12702497.020502415, "y": 6614660.374377...
344042016-07-08164440Southland Leisure Centre110050.962485-114.1084724.37531057715.199219...1000976.1360.265832c7259240-55a6-40fe-8700-844fafc12b8f2020-04-27arcgis_python2020-04-27arcgis_python0.218669{"x": -12702497.020502415, "y": 6614660.374377...
454052016-07-09164440Southland Leisure Centre110050.962485-114.1084722.81672557715.199219...1000490.2500.1335106e59869a-e4e3-473a-b98f-a57ec6a5b4802020-04-27arcgis_python2020-04-27arcgis_python0.179353{"x": -12702497.020502415, "y": 6614660.374377...

5 rows × 25 columns

test_pred_layer_sdf.shape
(1590, 25)

The table above returns the predicted values for the Southland photovoltaic power plant stored in the field called prediction_results, which holds the model estimated daily capacity factor of energy generation, whereas the actual capacity factor is in the field named capacity_f.

The capacity factor is a normalized value that will be rescaled back to the original unit of KWh by using the peak capacity of the Southland photovoltaic power plant of 153KWp.

# inverse scaling from capcacity factor to actual generation in KWh  - peak capcity of Southland Leisure Centre is 153KWp
test_pred_datetime = test_pred_layer_sdf[['field1','capacity_f','prediction']].copy()
test_pred_datetime = test_pred_datetime.rename(columns={'field1':'date'})
test_pred_datetime['date'] = pd.to_datetime(test_pred_datetime['date']) 
test_pred_datetime = test_pred_datetime.set_index(test_pred_datetime['date'])
test_pred_datetime['Actual_generation(KWh)'] = test_pred_datetime['capacity_f']*24*153
test_pred_datetime['predicted_generation(KWh)'] = test_pred_datetime['prediction']*24*153
test_pred_datetime = test_pred_datetime.drop(['date','capacity_f','prediction'], axis=1).sort_index() 
test_pred_datetime
Actual_generation(KWh)predicted_generation(KWh)
date
2015-09-01286.013672.612955
2015-09-02681.646566.551533
2015-09-03647.906553.634092
2015-09-04102.448229.400620
2015-09-0593.432195.826120
.........
2019-12-271.34937.905438
2019-12-281.96512.047412
2019-12-291.61669.387516
2019-12-307.440114.383683
2019-12-318.32369.632977

1590 rows × 2 columns

The table above shows the actual versus the model predicted daily solar energy generated for the Southland plant for the duration of late 2015 to the end of 2019. These values are now used to estimate the various model metrics to understand the prediction power of the model.

# estimate model metrics of r-square, rmse and mse for the actual and predicted values for daily energy generation
from sklearn.metrics import r2_score
r2_test = r2_score(test_pred_datetime['Actual_generation(KWh)'],test_pred_datetime['predicted_generation(KWh)'])
print('R-Square: ', round(r2_test, 2))
R-Square:  0.86

The comparison returns a high r-square of 0.86, showing a high similarity between the actual and predicted values.

# Comparison between the actual sum of the total energy generated to the total predicted values 
actual = (test_pred_datetime['Actual_generation(KWh)'].sum()/4/1000).round(2)  
predicted = (test_pred_datetime['predicted_generation(KWh)'].sum()/4/1000).round(2) 
print('Actual annual Solar Energy Generated by Southland Solar Station: {} MWh'.format(actual))
print('Predicted annual Solar Energy Generated by Southland Solar Stations: {} MWh'.format(predicted))
Actual annual Solar Energy Generated by Southland Solar Station: 170.03 MWh
Predicted annual Solar Energy Generated by Southland Solar Stations: 170.34 MWh

Summarizing the values, the actual average annual energy generated by the solar plant is 170.03 MWh which is close to the predicted annual average generated energy of 170.34 MWh, indicating a high level high precision.

Result Visualization

Finally, the actual and predicted values are plotted to visualize their distribution across the entire lifetime of the power plant.

plt.figure(figsize=(30,6))
plt.plot(test_pred_datetime['Actual_generation(KWh)'],  linewidth=1, label= 'Actual') 
plt.plot(test_pred_datetime['predicted_generation(KWh)'], linewidth=1, label= 'Predicted')  
plt.ylabel('Solar Energy in KWh', fontsize=14)
plt.legend(fontsize=14,loc='upper right')
plt.title('Actual Vs Predicted Solar Energy Generated by Southland Solar-FulyConnectedNetwork Model', fontsize=14)
plt.grid()
plt.show() 
<Figure size 2160x432 with 1 Axes>

In the plot above, the blue line represents the actual generation values, and the orange line represents the predicted generation values. The two show a high degree of overlap, indicating that the model has a high predictive capacity.

MLModel

In the second method, a machine learning model is applied to model the same data using the MLModel framework from arcgis.learn. This framework can be used to import and apply any machine learning model from the scikit-learn library on the data returned by the prepare_tabulardata function from arcgis.learn.

# importing the libraries from arcgis.learn for data preprocessing for the Machine Learning Model
from sklearn.preprocessing import MinMaxScaler

Data Preprocessing

Like the data preparation process for the neural network, first a list is made consisting of the feature data that will be used for predicting daily solar energy generation. By default, it will receive continuous variables, whereas for categorical variables, the True value should be passed inside a tuple along with the variables. These variables are then transformed by the RobustScaler function from scikit-learn by passing it, along with the variable list, into the column transformer function as follows:

# scaling the feature data using MinMaxScaler(), the default is Normalizer from scikit learn
X = ['altitude_m', 'wind_speed', 'dayl__s_', 'prcp__mm_d','srad__W_m_','swe__kg_m_','tmax__deg','tmin__deg','vp__Pa_']
preprocessors =  [('altitude_m', 'wind_speed', 'dayl__s_', 'prcp__mm_d','srad__W_m_','swe__kg_m_','tmax__deg',
                   'tmin__deg','vp__Pa_', MinMaxScaler())]

Once the explanatory variables list is defined and the preprocessors are computed, they are now used as input for the prepare_tabulardata method in arcgis.learn. The method takes a feature layer or a spatial dataframe containing the dataset and returns a TabularDataObject that can be fed into the model.

The input parameters required for the tool are similar to the ones mentioned previously:

# importing the library from arcgis.learn for prepare data
from arcgis.learn import prepare_tabulardata
# precrocessing data using prepare data method for MLModel
data = prepare_tabulardata(calgary_no_southland_solar_layer,
                           'capacity_f',
                           explanatory_variables=X,                           
                           preprocessors=preprocessors)
# check the data that is being trained
data.show_batch()
altitude_mcapacity_fdayl__s_prcp__mm_dsrad__W_m_swe__kg_m_tmax__degtmin__degvp__Pa_wind_speed
14610950.00805138707.1992190329.60000683.0-13.52004.242083
17010950.02592133177.601562370.40000203.5-2.55205.934033
39110950.27363757715.1992190515.200012026.54.54806.532143
52210950.01634332140.8007810147.1999970-11.5-20.01204.157886
82510950.01935429376.0000000102.400002410.01.56806.422836
.................................
839010510.28530756332.8007810460.799988027.08.55203.335872
846010510.23310354604.8007814300.799988026.510.012003.828789
877710510.28511657369.6015620537.599976027.55.53204.703076
903510960.00269227993.5996090134.3999940-5.5-30.0408.036874
905610960.12257230412.8007810121.59999804.5-6.04007.787962

64 rows × 10 columns

Model Initialization

Once the data has been prepared by prepare_tabulardatamethod, it is ready to be passed to the selected machine learning model for training. Here, the GradientBoostingRegressor model from scikit-learn is used, which is passed into the MLModelfunction, along with its parameters, as follows:

# importing the MLModel framework from arcgis.learn and the model from scikit learn 
from arcgis.learn import MLModel

# defining the model along with the parameters 
model = MLModel(data, 'sklearn.ensemble.GradientBoostingRegressor', n_estimators=100, random_state=43)

Model Training

Finally, the model is now ready for training, and the model.fit method is used for fitting the machine learning model with its defined parameters mentioned in the previous step.

model.fit() 

The training results are printed to compute some model metrics and assess the quality of the trained model.

model.show_results()
altitude_mcapacity_fdayl__s_prcp__mm_dsrad__W_m_swe__kg_m_tmax__degtmin__degvp__Pa_wind_speedcapacity_f_results
256411120.23120958752.0000000432.000000020.05.57205.4991340.222301
557610700.16084658406.3984383208.000000015.57.510403.9043770.117711
707810900.16747835596.8007810176.00000006.0-3.54807.2950550.068521
823810510.00000727648.0000000115.1999978-6.5-18.51607.2622570.006838
906610960.09784332140.8007810147.1999970-10.5-20.51204.1578860.023777

In the table above, the last column, capacity_f_results, returns the values predicted by the model, which are similar to the actual values in the target variable column, capacity_f.

Subsequently, the model metrics of the trained model are now estimated using the model.score() function, which currently returns the r-squared of the model fit as follows:

# r-square is estimated using the inbuilt model.score() from the tabular learner
print('r_square_test_rf: ', round(model.score(), 5))
r_square_test_rf:  0.81248

The high R-squared value indicates that the model has been trained well.

Explaining Predictor Importance of Solar Energy Generation

Once the model has been fitted, it would be interesting to understand the explanability of the model, or the factors that are responsbile for predicting solar energy generation from the several varaible used in the model. The feature_importances method is used for the same as follows:

import seaborn as sns
feature_imp_RF = model.feature_importances_
rel_feature_imp = 100 * (feature_imp_RF / max(feature_imp_RF)) 
rel_feature_imp = pd.DataFrame({'features':list(X), 'rel_importance':rel_feature_imp })

rel_feature_imp = rel_feature_imp.sort_values('rel_importance', ascending=False)

plt.figure(figsize=[15,4])
plt.yticks(fontsize=10)
ax = sns.barplot(x="rel_importance", y="features", data=rel_feature_imp, palette="BrBG")

plt.xlabel("Relative Importance", fontsize=10)
plt.ylabel("Features", fontsize=10)
plt.show()
<Figure size 1080x288 with 1 Axes>

The above graph shows the feature importances returned by the featureimportances method of the trained Gradient Boosting regressor algorithm implemented via the MLModel framework. Here the most important predictor for forecasting solar energy generation at the site is shown to be the amount of solar radiation received by the site in watts per square meter (srad__W_m). This is followed by the length of day in seconds, maximum temperature and minimum temperature in the same order, which also matches the findings obtained from the correlation plot.

Solar Energy Generation Forecast & Validation

The trained GradientBoostingRegressor model, implemented via the MLModel, will now be used to predict the daily lifetime solar energy generation for the solar plant installed at the Southland Leisure Centre, since its installation in 2015. The aim is to compare and validate its performance to the performance of the FullyConnectedNetwork model developed in earlier in this lesson.

To reiterate, the model.predict method from arcgis.learn is used with the daily weather variables as input for the mentioned site, ranging from September 2015 to December 2019, to predict daily solar energy output in KWh for the same time period. The predictors are automatically chosen from the input feature layer of southland_layer by the trained model, without mentioning them explicitly, as their names are the same as those used for training the model.

southland_solar_layer_predicted_rf = model.predict(southland_solar_layer, output_layer_name='prediction_layer_rf')
C:\Users\sup10432\.conda\envs\upsupervisedEnvAug11\lib\site-packages\arcgis\features\geo\_io\fileops.py:743: UserWarning: Discarding nonzero nanoseconds in conversion
  out_name=fc_name)
# print the predicted layer
southland_solar_layer_predicted_rf
prediction_layer_rf
Feature Layer Collection by arcgis_python
Last Modified: September 04, 2020
0 comments, 0 views
# Access & visualize the dataframe from the predicted layer 
valid_pred_layer = southland_solar_layer_predicted_rf.layers[0]
valid_pred_layer_sdf = valid_pred_layer.query().sdf
valid_pred_layer_sdf.head()
FIDfid_1field1idsolar_planaltitude_mlatitudelongitudewind_speeddayl_s_...vp_pa_k_wh_fillecapacity_fglobal_idcreation_dcreatoredit_dateeditorpredictionSHAPE
0112019-10-03164440Southland Leisure Centre110050.962485-114.1084725.33223940089.601562...480309.6440.084326e9b0f671-d6ba-4560-b912-d635a0a129f82020-04-27arcgis_python2020-04-27arcgis_python0.124855{"x": -12702497.020502415, "y": 6614660.374377...
1222019-10-04164440Southland Leisure Centre110050.962485-114.1084726.30482940089.601562...560679.7850.1851277bde5210-a8c2-4731-9c23-e5f77c1ebc562020-04-27arcgis_python2020-04-27arcgis_python0.148331{"x": -12702497.020502415, "y": 6614660.374377...
2332019-10-05164440Southland Leisure Centre110050.962485-114.1084727.63104039744.000000...720581.8590.158458acfd49e7-3973-49b6-9077-f12ab0c44af12020-04-27arcgis_python2020-04-27arcgis_python0.134697{"x": -12702497.020502415, "y": 6614660.374377...
3442019-10-06164440Southland Leisure Centre110050.962485-114.1084728.59059139398.398438...720718.4780.19566427d3b8bb-aabe-4e70-acc3-579882d43b012020-04-27arcgis_python2020-04-27arcgis_python0.143388{"x": -12702497.020502415, "y": 6614660.374377...
4552019-10-07164440Southland Leisure Centre110050.962485-114.10847210.63689939398.398438...640406.9560.1108279885c59a-d0b2-4956-ad97-4c8ec6483aab2020-04-27arcgis_python2020-04-27arcgis_python0.113309{"x": -12702497.020502415, "y": 6614660.374377...

5 rows × 25 columns

The table above returns the MLModel predicted values for the Southland plant stored in the field prediction, while the actual capacity factor is stored in the field named capacity_f.

The capacity factor is a normalized value that will be rescaled back to the original unit of KWh by using the peak capacity of the Southland photovoltaic power plant of 153KWp.

# inverse scaling from capcacity factor to actual generation in KWh  - peak capcity of Southland Leisure Centre is 153KWp
valid_pred_datetime = valid_pred_layer_sdf[['field1','capacity_f','prediction']].copy()
valid_pred_datetime = valid_pred_datetime.rename(columns={'field1':'date'})
valid_pred_datetime['date'] = pd.to_datetime(valid_pred_datetime['date']) 
valid_pred_datetime = valid_pred_datetime.set_index(valid_pred_datetime['date'])
valid_pred_datetime['Actual_generation(KWh)'] = valid_pred_datetime['capacity_f']*24*153
valid_pred_datetime['predicted_generation(KWh)'] = valid_pred_datetime['prediction']*24*153
valid_pred_datetime = valid_pred_datetime.drop(['date','capacity_f','prediction'], axis=1)
valid_pred_datetime = valid_pred_datetime.sort_index() 
valid_pred_datetime.head()
Actual_generation(KWh)predicted_generation(KWh)
date
2015-09-01286.013718.458356
2015-09-02681.646679.957975
2015-09-03647.906548.296796
2015-09-04102.448195.091296
2015-09-0593.432132.086403

The table above shows the actual versus the MLModel predicted daily solar energy generated for the Southland plant for the duration of late 2015 to the end of 2019. These values are now used to estimate the various model metrics to understand the prediction power of the MLModel.

# estimate model metrics of r-square, rmse and mse for the actual and predicted values for daily energy generation
from sklearn.metrics import r2_score
r2_test = r2_score(valid_pred_datetime['Actual_generation(KWh)'],valid_pred_datetime['predicted_generation(KWh)'])
print('R-Square: ', round(r2_test, 2))
R-Square:  0.84

The comparison returns a high R-squared of 0.84, indicating a high similarity between the actual and predicted values.

# Comparison between the actual sum of the total energy generated to the total predicted values by the MLModel 
actual = (valid_pred_datetime['Actual_generation(KWh)'].sum()/4/1000).round(2)  
predicted = (valid_pred_datetime['predicted_generation(KWh)'].sum()/4/1000).round(2) 
print('Actual annual Solar Energy Generated by Southland Solar Station: {} MWh'.format(actual))
print('Predicted annual Solar Energy Generated by Southland Solar Stations: {} MWh'.format(predicted))
Actual annual Solar Energy Generated by Southland Solar Station: 170.03 MWh
Predicted annual Solar Energy Generated by Southland Solar Stations: 171.48 MWh

Summarizing the values, the actual average annual energy generated by the solar plant is 170.03 MWh, which is close to the predicted annual average generated energy of 171.48 MWh. This indicates a high level of precision.

Result Visualization

Finally, the actual and predicted values are plotted to visualize their distribution across the entire lifetime of the power plant.

plt.figure(figsize=(30,6))
plt.plot(valid_pred_datetime['Actual_generation(KWh)'],  linewidth=1, label= 'Actual') 
plt.plot(valid_pred_datetime['predicted_generation(KWh)'], linewidth=1, label= 'Predicted')  
plt.ylabel('Solar Energy in KWh', fontsize=14)
plt.legend(fontsize=14,loc='upper right')
plt.title('Actual Vs Predicted Solar Energy Generated by Southland Solar-FulyConnectedNetwork Model', fontsize=14)
plt.grid()
plt.show() 
<Figure size 2160x432 with 1 Axes>

Conclusion

The goal of this project was to create a model that could predict the daily solar energy efficiency, thereby estimate the actual output of a photovoltaic solar energy plant at a location using the daily weather variables of the site as inputs. In the process it demonstrated the newly implemented artificial neural network, called FullyConnectedNetwork, and machine learning models, called MLModel, available in the arcgis.learn module in ArcGIS API for Python.

Accordingly, data from 10 solar energy installation sites in the City of Calgary in Canada were used to train two different models — the first being the FullyConnectedNetwork model and second being the MLModel framework from the arcgis.learn module. These were eventually used to predict the daily solar output of a different solar plant in Calgary, which was withheld from the training set. The steps for implementing these models are elaborated in the notebook, and include the steps of data preprocessing, model training, and final inferencing.

Comparison of the result shows that both models successfully predicted the solar energy output of the test solar plant with predicted values of 170.34 MWh and 171.48 MWh by the FullyConnectedNetwork and the MLModel algorithm respectively, compared to the actual value of average annual solar generation of 170.03 MWh for the station.

Finally, to expand on this model further in the furture, it would be interesting to apply this model to other solar generation plants located across different geographies and to record its performance to understand the generalizability of the model.

Summary of methods used

MethodDescriptionExamples
prepare_tabulardataprepare data including imputation, normalization and train-test splitprepare data ready for fitting a MLModel or FullyConnectedNetwork model
FullyConnectedNetwork()set a fully connected neural network to a datainitialize a FullyConnectedNetwork model with prepared data
model.lr_find()find an optimal learning ratefinalize a good learning rate for training the FullyConnectedNetwork model
MLModel()select the ML algorithm to be used for fittingany supervised or unsupervised regression and classification model from scikit learn can be used
model.fit()train a model with epochs & learning rate as inputtraining the FullyConnectedNetwork model with sutiable input
model.score()find the model metric of R-squared of the trained modelreturns R-squared value after training the MLModel and FullyConnectedNetwork model
model.predict()predict on a test setpredict values using the trained models on test input

Data resources

DatasetSourceLink
Calgary solar energyCalgary daily solar energy generationhttps://data.calgary.ca/Environment/Solar-Energy-Production/ytdn-2qsp
Calgary Photovoltaic SitesLocation of Calgary Solar sites in Lat & Lonhttps://data.calgary.ca/dataset/City-of-Calgary-Solar-Photovoltaic-Sites/vrdj-ycb5
Calgary Daily weather dataMODIS - Daily Surface Weather Data on a 1-km Grid for North America, Version 3https://daac.ornl.gov/DAYMET/guides/Daymet_V3_CFMosaics.html

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