In [1]:
import random
import numpy as np
import matplotlib.pyplot as plt

import sys
sys.path.append("..")
from solarbench import FormatMLDataset, load_datasets, get_dataloader, DatasetsInfo
import solarbench.evaluation as evaluation
from demo_helpers import display_sample, display_tensor_img

Dataset meta information retrieval

The DatasetsInfo class allows you to retrieve the meta information for the datasets that you are interested in. You can query the information either by dataset names (DatasetsInfo.by_name) or by dataset features (GetDatasetsInfo.by_features).

Obtaining metadata for all datasets

DatasetsInfo.datasets allows you to retrieve a pandas DataFrame containing metadata for every dataset in SolarBench.

In [2]:
DatasetsInfo.datasets
Out[2]:
name region country location years_collected data_types img_res time_res (minutes) num_train num_test
0 UCSD-Folsom NA United States [38.642, -121.148] 2014-2016 [sky_img, irrad, weather] [64, 64] 1 722320 42820
1 SIPPMIF OC Australia [-34.400309, 150.899807] 2019 [sky_img, pv] [64, 64] 1 1000 500
2 SKIPPD NA United States [37.427, -122.174] 2017-2019 [sky_img, pv] [64, 64] 1 349372 14003
3 SRRL-BMS NA United States [39.742, -105.18] 2021-2024 [sky_img, irrad, weather] [64, 64] 1 347121 20757
4 COPDD EU France [45.7742447, 2.96240064] 2015-2023 [sky_img, irrad, weather] [64, 64] [1, 2] 973555 75247
5 SIRTA EU France [48.713, 2.208] 2015-2022 [sky_img, irrad, weather] [64, 64] [1, 2] 1390161 78283
6 EnerSHelF-StAugustin EU Germany [50.779376, 7.182599] 2020-2022 [sky_img, irrad] [64, 64] 1 445598 24187
7 EnerSHelF-Akwatia AF Ghana [6.047167, -0.797028] 2022 [sky_img, irrad] [64, 64] 1 84717 5824
8 EnerSHelF-Kologo AF Ghana [10.737222, -1.077556] 2022 [sky_img, irrad] [64, 64] 1 87099 6053
9 HK-PolyU AS Hong Kong [22.335, 114.264] 2023 [sky_img, irrad, weather] [64, 64] 1 412027 20317
10 UNottingham EU United Kingdom [52.9529047, -1.1825815] 2023 [sky_img, irrad, weather] [64, 64] 1 59561 8676
11 UQueensland OC Australia [-27.55960330542919, 152.34060760751836] 2020 [sky_img, pv] [64, 64] 1 73619 3721

DatasetsInfo.benchmarks allows you to retrieve a pandas Dataframe containing information about the available benchmark datasets composed of multiple SolarBench datasets.

In [3]:
DatasetsInfo.benchmarks
Out[3]:
name datasets
0 forecast-irradiance [SRRL-BMS, UCSD-Folsom, SKIPPD, COPDD, SIRTA, ...
1 forecast-pv [SKIPPD, SIPPMIF]

Query by dataset names

DatasetsInfo.by_name allows you to retrieve the information for a list of dataset names.

In [4]:
# Retrieve the meta information of specific datasets
dataset_list = ['SRRL-BMS', 'SKIPPD']
DatasetsInfo.by_name(dataset_list)
Out[4]:
name region country location years_collected data_types img_res time_res (minutes) num_train num_test
0 SKIPPD NA United States [37.427, -122.174] 2017-2019 [sky_img, pv] [64, 64] 1 349372 14003
1 SRRL-BMS NA United States [39.742, -105.18] 2021-2024 [sky_img, irrad, weather] [64, 64] 1 347121 20757

Query by dataset features

DatasetsInfo.by_features allows you to retrieve the information by dataset features, e.g., the location of the data, the data types contained in the dataset, etc. A complete list of available query parameters is available in our full documentation.

In [5]:
# Retrieve information for datasets that contain PV generation data 
dataset_features = {'data_types':['pv']}
DatasetsInfo.by_features(dataset_features)
Out[5]:
name region country location years_collected data_types img_res time_res (minutes) num_train num_test
1 SIPPMIF OC Australia [-34.400309, 150.899807] 2019 [sky_img, pv] [64, 64] 1 1000 500
2 SKIPPD NA United States [37.427, -122.174] 2017-2019 [sky_img, pv] [64, 64] 1 349372 14003
11 UQueensland OC Australia [-27.55960330542919, 152.34060760751836] 2020 [sky_img, pv] [64, 64] 1 73619 3721
In [6]:
# Retrieve information for datasets that are collectioned in certain regions or countries,e.g., {'Country':['France']}
dataset_features = {'Region':['NA'], 'Country': ['United States']} # you are able to input multiple regions in the list
DatasetsInfo.by_features(dataset_features)
Out[6]:
name region country location years_collected data_types img_res time_res (minutes) num_train num_test
0 UCSD-Folsom NA United States [38.642, -121.148] 2014-2016 [sky_img, irrad, weather] [64, 64] 1 722320 42820
1 SKIPPD NA United States [37.427, -122.174] 2017-2019 [sky_img, pv] [64, 64] 1 349372 14003
2 SRRL-BMS NA United States [39.742, -105.18] 2021-2024 [sky_img, irrad, weather] [64, 64] 1 347121 20757
In [7]:
# Retrieve information for datasets that contain PV generation data and are collectioned in certain regions
dataset_features = {'Data_types':['pv'],'Region':['NA', 'OC']}
DatasetsInfo.by_features(dataset_features)
Out[7]:
name region country location years_collected data_types img_res time_res (minutes) num_train num_test
0 SIPPMIF OC Australia [-34.400309, 150.899807] 2019 [sky_img, pv] [64, 64] 1 1000 500
1 SKIPPD NA United States [37.427, -122.174] 2017-2019 [sky_img, pv] [64, 64] 1 349372 14003
2 UQueensland OC Australia [-27.55960330542919, 152.34060760751836] 2020 [sky_img, pv] [64, 64] 1 73619 3721

Fetching available/benchmark dataset names

For convenience, you can use DatasetInfo.datasets_list to obtain a Python list containing the names of the currently available datasets in SolarBench, and DatasetInfo.benchmarks_list for benchmark dataset names.

In [8]:
print("Datasets:", DatasetsInfo.datasets_list)
print("Benchmarks:", DatasetsInfo.benchmarks_list)
Datasets: ['UCSD-Folsom', 'SIPPMIF', 'SKIPPD', 'SRRL-BMS', 'COPDD', 'SIRTA', 'EnerSHelF-StAugustin', 'EnerSHelF-Akwatia', 'EnerSHelF-Kologo', 'HK-PolyU', 'UNottingham', 'UQueensland']
Benchmarks: ['forecast-irradiance', 'forecast-pv']

Dataset downloading

You can download or retrieve one or more datasets using load_datasets. If you would like to potentially speed up your download, you can set the num_proc keyword argument. Once datasets are downloaded once, they are cached locally in your system.

In [9]:
dataset_list = ['SRRL-BMS', 'UCSD-Folsom', 'SIRTA']
datasets = load_datasets(dataset_list)
print(datasets)
Downloading dataset SRRL-BMS
Downloading dataset UCSD-Folsom
Downloading dataset SIRTA
<class 'solarbench.loading.LoaderContainer'>({
    'SRRL-BMS': DatasetDict({
        train: Dataset({
            features: ['image', 'time', 'ghi', 'dni', 'dhi', 'air_temp', 'relhum', 'press', 'windsp', 'winddir', 'max_windsp', 'precipitation'],
            num_rows: 347121
        })
        test: Dataset({
            features: ['image', 'time', 'ghi', 'dni', 'dhi', 'air_temp', 'relhum', 'press', 'windsp', 'winddir', 'max_windsp', 'precipitation'],
            num_rows: 20757
        })
    }),
    'UCSD-Folsom': DatasetDict({
        train: Dataset({
            features: ['image', 'time', 'ghi', 'dni', 'dhi', 'air_temp', 'relhum', 'press', 'windsp', 'winddir', 'max_windsp', 'precipitation'],
            num_rows: 722320
        })
        test: Dataset({
            features: ['image', 'time', 'ghi', 'dni', 'dhi', 'air_temp', 'relhum', 'press', 'windsp', 'winddir', 'max_windsp', 'precipitation'],
            num_rows: 42820
        })
    }),
    'SIRTA': DatasetDict({
        train: Dataset({
            features: ['image', 'time', 'ghi', 'ghi_std', 'ghi_min', 'ghi_max', 'dni', 'dni_std', 'dni_min', 'dni_max', 'dhi', 'dhi_std', 'dhi_min', 'dhi_max', 'lwd_max', 'air_temp', 'relhum', 'press'],
            num_rows: 1390161
        })
        test: Dataset({
            features: ['image', 'time', 'ghi', 'ghi_std', 'ghi_min', 'ghi_max', 'dni', 'dni_std', 'dni_min', 'dni_max', 'dhi', 'dhi_std', 'dhi_min', 'dhi_max', 'lwd_max', 'air_temp', 'relhum', 'press'],
            num_rows: 78283
        })
    }),
})
In [10]:
# you can check individual datasets
# e.g., show the structure of the dataset
print(datasets['SIRTA'])
DatasetDict({
    train: Dataset({
        features: ['image', 'time', 'ghi', 'ghi_std', 'ghi_min', 'ghi_max', 'dni', 'dni_std', 'dni_min', 'dni_max', 'dhi', 'dhi_std', 'dhi_min', 'dhi_max', 'lwd_max', 'air_temp', 'relhum', 'press'],
        num_rows: 1390161
    })
    test: Dataset({
        features: ['image', 'time', 'ghi', 'ghi_std', 'ghi_min', 'ghi_max', 'dni', 'dni_std', 'dni_min', 'dni_max', 'dhi', 'dhi_std', 'dhi_min', 'dhi_max', 'lwd_max', 'air_temp', 'relhum', 'press'],
        num_rows: 78283
    })
})
In [11]:
# e.g., query specifc samples
display(datasets['SRRL-BMS']['train'][0]['image'])

Dataset Formatting

Sky images can be used for different tasks, such as solar forecasting, cloud segmentation, cloud classification, and cloud motion prediction. Below is an example utilizing the solar_forecast formatter.

Forecast parameters

In [12]:
formatted_datasets = FormatMLDataset.solar_forecast(datasets, forecast_span=2,       # time span from the first to the last forecasts
                                                    forecast_resolution=1,  # temporal resolution of the forecasts
                                                    lead_time=15,           # the time between the forecast submission deadline and the first forecast timestamp
                                                    history_length=15,      # time span from the first to the last input history
                                                    history_resolution=3,   # temporal resolution of the input history
                                                    sample_interval=2,      # the time interval between two consecutive samples
                                                    transform=None)
Formatting SRRL-BMS: split='train'.
100%|██████████| 347121/347121 [00:06<00:00, 56200.93it/s]
Formatting SRRL-BMS: split='test'.
100%|██████████| 20757/20757 [00:00<00:00, 54294.77it/s]
Formatting UCSD-Folsom: split='train'.
100%|██████████| 722320/722320 [00:18<00:00, 39732.98it/s]
Formatting UCSD-Folsom: split='test'.
100%|██████████| 42820/42820 [00:01<00:00, 41097.45it/s]
Formatting SIRTA: split='train'.
100%|██████████| 1390161/1390161 [00:26<00:00, 52099.56it/s]
Formatting SIRTA: split='test'.
100%|██████████| 78283/78283 [00:01<00:00, 54887.25it/s]
In [13]:
# Create validation splits for the formatted datasets
formatted_datasets.split("random", 0.8)

# Combine the formatted datasets into one through concatenation
combined_dataset = formatted_datasets.combine("concat")
In [14]:
print(f"Number of training samples: {len(combined_dataset['train'])}")
print(f"Number of validation samples: {len(combined_dataset['validation'])}")
print(f"Number of test samples: {len(combined_dataset['test'])}")
Number of training samples: 646206
Number of validation samples: 161549
Number of test samples: 46684
In [15]:
for _ in range(5):
    idx = random.randint(0, len(combined_dataset['train']))
    display_sample(combined_dataset['train'][idx])
--------------------------------------------------
Sample from 2016-05-08 08:48:00+02:00 to 2016-05-08 09:03:00+02:00
Sample's keys and shapes:
                                      0
image_log    torch.Size([6, 3, 64, 64])
time_log                            [6]
target_log              torch.Size([6])
image_pred   torch.Size([3, 3, 64, 64])
time_pred                           [3]
target_pred             torch.Size([3])
Images (historical):
Images (future):
--------------------------------------------------
Sample from 2016-04-03 11:43:00+02:00 to 2016-04-03 11:58:00+02:00
Sample's keys and shapes:
                                      0
image_log    torch.Size([6, 3, 64, 64])
time_log                            [6]
target_log              torch.Size([6])
image_pred   torch.Size([3, 3, 64, 64])
time_pred                           [3]
target_pred             torch.Size([3])
Images (historical):
Images (future):
--------------------------------------------------
Sample from 2015-08-29 15:02:00-07:00 to 2015-08-29 15:17:00-07:00
Sample's keys and shapes:
                                      0
image_log    torch.Size([6, 3, 64, 64])
time_log                            [6]
target_log              torch.Size([6])
image_pred   torch.Size([3, 3, 64, 64])
time_pred                           [3]
target_pred             torch.Size([3])
Images (historical):
Images (future):
--------------------------------------------------
Sample from 2022-04-06 16:18:30-07:00 to 2022-04-06 16:33:30-07:00
Sample's keys and shapes:
                                      0
image_log    torch.Size([6, 3, 64, 64])
time_log                            [6]
target_log              torch.Size([6])
image_pred   torch.Size([3, 3, 64, 64])
time_pred                           [3]
target_pred             torch.Size([3])
Images (historical):
Images (future):
--------------------------------------------------
Sample from 2016-09-14 09:19:00+02:00 to 2016-09-14 09:34:00+02:00
Sample's keys and shapes:
                                      0
image_log    torch.Size([6, 3, 64, 64])
time_log                            [6]
target_log              torch.Size([6])
image_pred   torch.Size([3, 3, 64, 64])
time_pred                           [3]
target_pred             torch.Size([3])
Images (historical):
Images (future):

Using a DataLoader

With a formatted dataset

In [16]:
# Can set a batch_size parameter to use batched elements for training
formatted_dataloader = get_dataloader(combined_dataset['train'], batch_size=2)
In [17]:
idx = 0
for example in formatted_dataloader:
    display_sample(example, False, False)
    idx += 1
    if idx > 5:
        break
DataLoader: Passing through field 'image_log'.
DataLoader: Passing through field 'target_log'.
DataLoader: Passing through field 'image_pred'.
DataLoader: Passing through field 'target_pred'.
--------------------------------------------------
Sample's keys and shapes:
                                         0
image_log    torch.Size([2, 6, 3, 64, 64])
time_log             torch.Size([2, 6, 7])
target_log              torch.Size([2, 6])
image_pred   torch.Size([2, 3, 3, 64, 64])
time_pred            torch.Size([2, 3, 7])
target_pred             torch.Size([2, 3])
--------------------------------------------------
Sample's keys and shapes:
                                         0
image_log    torch.Size([2, 6, 3, 64, 64])
time_log             torch.Size([2, 6, 7])
target_log              torch.Size([2, 6])
image_pred   torch.Size([2, 3, 3, 64, 64])
time_pred            torch.Size([2, 3, 7])
target_pred             torch.Size([2, 3])
--------------------------------------------------
Sample's keys and shapes:
                                         0
image_log    torch.Size([2, 6, 3, 64, 64])
time_log             torch.Size([2, 6, 7])
target_log              torch.Size([2, 6])
image_pred   torch.Size([2, 3, 3, 64, 64])
time_pred            torch.Size([2, 3, 7])
target_pred             torch.Size([2, 3])
--------------------------------------------------
Sample's keys and shapes:
                                         0
image_log    torch.Size([2, 6, 3, 64, 64])
time_log             torch.Size([2, 6, 7])
target_log              torch.Size([2, 6])
image_pred   torch.Size([2, 3, 3, 64, 64])
time_pred            torch.Size([2, 3, 7])
target_pred             torch.Size([2, 3])
--------------------------------------------------
Sample's keys and shapes:
                                         0
image_log    torch.Size([2, 6, 3, 64, 64])
time_log             torch.Size([2, 6, 7])
target_log              torch.Size([2, 6])
image_pred   torch.Size([2, 3, 3, 64, 64])
time_pred            torch.Size([2, 3, 7])
target_pred             torch.Size([2, 3])
--------------------------------------------------
Sample's keys and shapes:
                                         0
image_log    torch.Size([2, 6, 3, 64, 64])
time_log             torch.Size([2, 6, 7])
target_log              torch.Size([2, 6])
image_pred   torch.Size([2, 3, 3, 64, 64])
time_pred            torch.Size([2, 3, 7])
target_pred             torch.Size([2, 3])

Evaluating your Model

Provided are various functions for evaluation of a model's performance.

These include:

  • Mean Absolute Error (MAE)
  • Root Mean Squared Error (RMSE)
  • Mean Bias Error (MBE)
  • Relative RMSE (RRMSE)
  • Temporal Distortion Index (TDI)
  • Temporal Distortion Mix (TDM)

See Barhmi et. al. (2024) (https://www.mdpi.com/2673-9941/4/1/5) for more details.

In [18]:
# Generate some sample data
pred_ghi = np.array([random.randint(-5, 5) + pred for pred in [500, 500, 500, 500, 600, 700, 600, 500, 400, 400]])
target_ghi = np.array([500, 500, 600, 700, 600, 500, 400, 400, 400, 400])
plt.plot(pred_ghi, label='pred')
plt.plot(target_ghi, label='target')
plt.legend()
plt.show()
In [19]:
# Evaluate with the various metrics

print(f"MAE: {evaluation.mae(target_ghi, pred_ghi):.2f}")

print(f"RMSE: {evaluation.rmse(target_ghi, pred_ghi):.2f}")

print(f"MBE: {evaluation.mbe(target_ghi, pred_ghi):.2f}")

print(f"RRMSE: {evaluation.rrmse(target_ghi, pred_ghi):.2f}")

print(f"TDI: {evaluation.tdi(target_ghi, pred_ghi):.2f}")

print(f"TDI (Advance): {evaluation.tdi_adv(target_ghi, pred_ghi):.2f}")

print(f"TDI (Late): {evaluation.tdi_late(target_ghi, pred_ghi):.2f}")

print(f"TDM: {evaluation.tdm(target_ghi, pred_ghi):.2f}")
MAE: 80.30
RMSE: 116.69
MBE: -18.10
RRMSE: 0.22
TDI: 0.28
TDI (Advance): 1.00
TDI (Late): 0.00
TDM: -1.00

Loading a benchmark

In [20]:
benchmark = load_datasets('forecast-pv', num_proc=10)
Loading benchmark 'forecast-pv' containing datasets: ['SKIPPD', 'SIPPMIF']
Downloading dataset SKIPPD
Downloading dataset SIPPMIF
In [21]:
print(benchmark)
<class 'solarbench.loading.LoaderContainer'>({
    'SKIPPD': DatasetDict({
        train: Dataset({
            features: ['image', 'time', 'pv'],
            num_rows: 349372
        })
        test: Dataset({
            features: ['image', 'time', 'pv'],
            num_rows: 14003
        })
    }),
    'SIPPMIF': DatasetDict({
        train: Dataset({
            features: ['image', 'time', 'pv'],
            num_rows: 1000
        })
        test: Dataset({
            features: ['image', 'time', 'pv'],
            num_rows: 500
        })
    }),
})

Putting it all together

The following is an example combining the above steps into one cell.

In [22]:
datasets = ["SIPPMIF", "SKIPPD"]
datasets_container = load_datasets(datasets, num_proc=10)
formatted_datasets = FormatMLDataset.solar_estimation(datasets_container, target_label="pv", normalization="max")
formatted_datasets.split("random", 0.8)
combined_dataset = formatted_datasets.combine("concat")

train_dataloader = get_dataloader(combined_dataset['train'])
validation_dataloader = get_dataloader(combined_dataset['validation'])
test_dataloader = get_dataloader(combined_dataset['test'])
Downloading dataset SIPPMIF
Downloading dataset SKIPPD
Formatting SIPPMIF: split='train'.
Formatting SIPPMIF: split='test'.
Formatting SKIPPD: split='train'.
Formatting SKIPPD: split='test'.
In [23]:
# Print out a couple samples from each loader
def print_non_image(sample):
    labels = {key: value for key, value in sample.items() if key != 'image'}
    print(f"Labels: {labels}")

itrain = iter(train_dataloader)
ivalid = iter(validation_dataloader)
itest = iter(test_dataloader)
for i in range(605):
    train_sample = next(itrain)
    validation_sample = next(ivalid)
    test_sample = next(itest)

    if i > 603: # Get samples from both datasets
        print("Train Sample")
        print("Prediction target:", train_sample['target'])
        print_non_image(train_sample)
        display_tensor_img(train_sample['image'][0])

        print("Validation Sample")
        print("Prediction target:", validation_sample['target'])
        print_non_image(validation_sample)
        display_tensor_img(validation_sample['image'][0])

        print("Test Sample")
        print("Prediction target:", test_sample['target'])
        print_non_image(test_sample)
        display_tensor_img(test_sample['image'][0])
DataLoader: Passing through field 'target'.
DataLoader: Converting field 'image' to tensor.
DataLoader: Passing through field 'target'.
DataLoader: Converting field 'image' to tensor.
DataLoader: Passing through field 'target'.
DataLoader: Converting field 'image' to tensor.
Train Sample
Prediction target: tensor([0.6140], dtype=torch.float64)
Labels: {'target': tensor([0.6140], dtype=torch.float64), 'time': [tensor([2019]), tensor([9]), tensor([10]), tensor([14]), tensor([26]), tensor([0]), tensor([36000.], dtype=torch.float64)]}
Validation Sample
Prediction target: tensor([0.5222], dtype=torch.float64)
Labels: {'target': tensor([0.5222], dtype=torch.float64), 'time': [tensor([2019]), tensor([8]), tensor([15]), tensor([17]), tensor([25]), tensor([40]), tensor([-25200.], dtype=torch.float64)]}
Test Sample
Prediction target: tensor([0.1084], dtype=torch.float64)
Labels: {'target': tensor([0.1084], dtype=torch.float64), 'time': [tensor([2017]), tensor([6]), tensor([24]), tensor([9]), tensor([27]), tensor([40]), tensor([-25200.], dtype=torch.float64)]}