dl4ds.metrics

View Source
  0import tensorflow as tf
  1import numpy as np
  2import xarray as xr
  3from joblib import Parallel, delayed
  4from matplotlib import pyplot as plt
  5from sklearn.metrics import mean_squared_error
  6from scipy.stats import spearmanr, pearsonr
  7import os
  8import seaborn as sns
  9import ecubevis as ecv
 10
 11from .utils import checkarray_ndim, Timing
 12
 13
 14def compute_rmse(y, y_hat, over='time', squared=False, n_jobs=40):
 15    """
 16
 17    Parameters
 18    ----------
 19    squared : bool
 20        If True returns MSE value, if False returns RMSE value.
 21
 22    https://scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html
 23    """
 24    def rmse_per_px(tupyx):
 25        y_coord, x_coord = tupyx
 26        return y_coord, x_coord, mean_squared_error(y[:,y_coord,x_coord,0], y_hat[:,y_coord,x_coord,0])
 27
 28    def rmse_gridpair(index):
 29        return mean_squared_error(y[index].flatten(), y_hat[index].flatten(), squared=squared)
 30
 31    #---------------------------------------------------------------------------
 32    if over == 'time':
 33        rmse_map = np.zeros_like(y[0,:,:,0]) 
 34        rmse_map *= np.nan
 35        yy, xx = np.where(y[0,:,:,0])
 36        coords = zip(yy, xx)
 37        out = Parallel(n_jobs=n_jobs, verbose=False)(delayed(rmse_per_px)(i) for i in coords) 
 38
 39        for i in range(len(out)):
 40            y_coord, x_coord, val = out[i]
 41            rmse_map[y_coord, x_coord] = val
 42        return rmse_map
 43
 44    elif over == 'space':
 45        n_timesteps = np.arange(y.shape[0])
 46        out = Parallel(n_jobs=n_jobs, verbose=False)(delayed(rmse_gridpair)(i) for i in n_timesteps)
 47        return out
 48    
 49
 50def compute_correlation(y, y_hat, over='time', mode='spearman', n_jobs=40):
 51    """
 52    https://scipy.github.io/devdocs/generated/scipy.stats.spearmanr.html
 53
 54    https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.pearsonr.html
 55    """
 56    def corr_per_px(tupyx):
 57        if mode == 'spearman':
 58            f = spearmanr
 59        elif mode == 'pearson':
 60            f = pearsonr
 61        y_coord, x_coord = tupyx
 62        return y_coord, x_coord, f(y[:,y_coord,x_coord,0], y_hat[:,y_coord,x_coord,0])[0]
 63
 64    def corr_per_gridpair(index):
 65        if mode == 'spearman':
 66            f = spearmanr
 67        elif mode == 'pearson':
 68            f = pearsonr
 69        return index, f(y[index].ravel(), y_hat[index].ravel())[0]
 70
 71    #---------------------------------------------------------------------------
 72    if over == 'time':
 73        corrmap = np.zeros_like(y[0,:,:,0]) 
 74        corrmap *= np.nan
 75        yy, xx = np.where(y[0,:,:,0])
 76        coords = zip(yy, xx)
 77        out = Parallel(n_jobs=n_jobs, verbose=False)(delayed(corr_per_px)(i) for i in coords) 
 78
 79        for i in range(len(out)):
 80            y_coord, x_coord, val = out[i]
 81            corrmap[y_coord, x_coord] = val
 82
 83        return corrmap
 84
 85    elif over == 'space':
 86        n_timesteps = np.arange(y.shape[0])
 87        out = Parallel(n_jobs=n_jobs, verbose=False)(delayed(corr_per_gridpair)(i) for i in n_timesteps)
 88
 89        list_corrs = []
 90        list_inds = []
 91        for i in range(len(out)):
 92            index, value = out[i]
 93            list_inds.append(index)
 94            list_corrs.append(value)
 95
 96        return list_corrs
 97
 98
 99def compute_metrics(
100    y_test, 
101    y_test_hat, 
102    dpi=150, 
103    plot_size_px=1000,
104    n_jobs=-1, 
105    scaler=None, 
106    mask=None,
107    save_path=None):
108    """ Compute temporal and spatial-wise metrics, e.g., RMSE and CORRELATION, 
109    based on the groundtruth and prediction ndarrays.
110
111    Parameters
112    ----------
113    y_test : np.ndarray
114        Groundtruth.
115    y_test_hat : np.ndarray
116        Prediction.
117    dpi : int, optional
118        DPI of the plots.  
119    n_jobs : int, optional
120        Number of cores for the computation of metrics (parallelizing over
121        grid points). Passed to joblib.Parallel. If -1 all CPUs are used. If 1 
122        or None is given, no parallel computing code is used at all, which is 
123        useful for debugging.
124    scaler : scaler object
125        Scaler object from preprocessing module. 
126    mask : np.ndarray or None
127        Binary mask with valid (ones) and non-valid (zeroes) grid points.
128    save_path : str or None, optional
129        Path to save results to disk. 
130        
131    """
132    timing = Timing()
133
134    if y_test.ndim == 5:
135        y_test = np.squeeze(y_test, -1)
136        y_test_hat = np.squeeze(y_test_hat, -1)
137
138    y_test = checkarray_ndim(y_test, 4, -1)
139    y_test_hat = checkarray_ndim(y_test_hat, 4, -1)
140
141    # backward transformation with the provided scaler
142    if scaler is not None:
143        if hasattr(scaler, 'inverse_transform'):
144            y_test = scaler.inverse_transform(y_test)
145            y_test_hat = scaler.inverse_transform(y_test_hat)        
146
147    # applying valid grid points mask
148    if mask is not None:
149        if isinstance(mask, xr.DataArray):
150            mask = mask.values.copy()
151        elif isinstance(mask, np.ndarray):
152            mask = mask.copy()
153
154        if mask.ndim == 2:
155            mask = np.expand_dims(mask, -1)
156        y_test = y_test.copy()
157        y_test_hat = y_test_hat.copy()
158        for i in range(y_test.shape[0]):
159            y_test[i] *= mask
160        for i in range(y_test_hat.shape[0]):
161            y_test_hat[i] *= mask
162        mask_nan = mask.astype('float').copy()
163        mask_nan[mask == 0] = np.nan
164        mask = np.squeeze(mask)
165
166    ### Computing metrics
167    drange = max(y_test.max(), y_test_hat.max()) - min(y_test.min(), y_test_hat.min())
168    with tf.device("cpu:0"):
169        psnr = tf.image.psnr(y_test, y_test_hat, drange)
170    mean_psnr = np.mean(psnr)
171    std_psnr = np.std(psnr)
172
173    with tf.device("cpu:0"):
174        ssim = tf.image.ssim(tf.convert_to_tensor(y_test, dtype=tf.float32), 
175                             tf.convert_to_tensor(y_test_hat, dtype=tf.float32), 
176                             drange)
177    mean_ssim = np.mean(ssim)
178    std_ssim = np.std(ssim)
179
180    with tf.device("cpu:0"):
181        maes = tf.keras.metrics.mean_absolute_error(y_test, y_test_hat)
182    maes_pairs = np.mean(maes, axis=(1,2))
183    mean_mae = np.mean(maes_pairs)
184    std_mae = np.std(maes_pairs)
185
186    ### RMSE 
187    temp_rmse_map = compute_rmse(y_test, y_test_hat, n_jobs=n_jobs, over='time')
188    spatial_rmse = compute_rmse(y_test, y_test_hat, n_jobs=n_jobs, over='space')
189    if save_path is not None:
190        np.save(os.path.join(save_path, 'metrics_mse_pergridpair.npy'), spatial_rmse)
191    mean_spatial_rmse = np.mean(spatial_rmse)
192    std_spatial_rmse = np.std(spatial_rmse)
193    mean_temp_rmse = np.nanmean(temp_rmse_map)
194    std_temp_rmse = np.nanstd(temp_rmse_map)
195    if mask is not None:
196        temp_rmse_map[np.where(mask == 0)] = 0
197    subpti = f'RMSE map ($\mu$ = {mean_temp_rmse:.6f})'
198    if save_path is not None:
199        savepath = os.path.join(save_path, 'metrics_pergridpoint_rmse_map.png')
200        np.save(os.path.join(save_path, 'metrics_pergridpoint_rmse_map.npy'), temp_rmse_map)
201    else:
202        savepath = None
203    ecv.plot_ndarray(temp_rmse_map, dpi=dpi, subplot_titles=(subpti), cmap='viridis', 
204                     plot_size_px=plot_size_px, interactive=False, save=savepath)
205
206    ### Normalized per grid point RMSE 
207    norm_temp_rmse_map = temp_rmse_map / (np.mean(y_test) * 100)
208    norm_mean_temp_rmse = np.nanmean(norm_temp_rmse_map)
209    norm_std_temp_rmse = np.nanstd(norm_temp_rmse_map)
210    if mask is not None:
211        norm_temp_rmse_map[np.where(mask == 0)] = 0
212    subpti = f'nRMSE map ($\mu$ = {norm_mean_temp_rmse:.6f})'
213    if save_path is not None:
214        savepath = os.path.join(save_path, 'metrics_pergridpoint_nrmse_map.png')
215        np.save(os.path.join(save_path, 'metrics_pergridpoint_nrmse_map.npy'), norm_temp_rmse_map)
216    else:
217        savepath = None
218    ecv.plot_ndarray(norm_temp_rmse_map, dpi=dpi, subplot_titles=(subpti), cmap='viridis', 
219                     plot_size_px=plot_size_px, interactive=False, save=savepath)
220
221    # Normalized mean bias
222    nmeanbias = np.mean(y_test_hat - y_test, axis=0)
223    nmeanbias /= np.mean(y_test) * 100
224    if mask is not None:
225        nmeanbias *= mask_nan
226    mean_nmeanbias = np.nanmean(nmeanbias)
227    nmeanbias[np.where(mask == 0)] = 0
228    subpti = f'NMBias map ($\mu$ = {mean_nmeanbias:.6f})'
229    if save_path is not None:
230        savepath = os.path.join(save_path, 'metrics_nmeanbias_map.png')
231        np.save(os.path.join(save_path, 'metrics_nmeanbias_map.npy'), nmeanbias)
232    else:
233        savepath = None
234    ecv.plot_ndarray(nmeanbias, dpi=dpi, subplot_titles=(subpti), cmap='viridis', 
235                     plot_size_px=plot_size_px, interactive=False, save=savepath)
236
237    ### Spearman correlation coefficient
238    spatial_spearman_corr = compute_correlation(y_test, y_test_hat, n_jobs=n_jobs, over='space')
239    mean_spatial_spearman_corr = np.mean(spatial_spearman_corr)
240    std_spatial_spearman_corr = np.std(spatial_spearman_corr)
241    if save_path is not None:
242        np.save(os.path.join(save_path, 'metrics_spearcorr_pergridpair.npy'), spatial_spearman_corr)
243
244    ### Pearson correlation coefficient
245    spatial_pearson_corr = compute_correlation(y_test, y_test_hat, mode='pearson', n_jobs=n_jobs, over='space')
246    mean_spatial_pearson_corr = np.mean(spatial_pearson_corr)
247    std_spatial_pearson_corr = np.std(spatial_pearson_corr)
248    if save_path is not None:
249        np.save(os.path.join(save_path, 'metrics_pearcorr_pergridpair.npy'), spatial_pearson_corr)
250    temp_pearson_corrmap = compute_correlation(y_test, y_test_hat, mode='pearson', n_jobs=n_jobs)
251    mean_temp_pearson_corr = np.nanmean(temp_pearson_corrmap)
252    std_temp_pearson_corr = np.nanstd(temp_pearson_corrmap)
253    temp_pearson_corrmap[np.where(mask == 0)] = 0
254    subpti = f'Pearson correlation map ($\mu$ = {mean_temp_pearson_corr:.6f})'
255    if save_path is not None:
256        savepath = os.path.join(save_path, 'metrics_pergridpoint_corrpears_map.png')
257        np.save(os.path.join(save_path, 'metrics_pergridpoint_corrpears_map.npy'), temp_pearson_corrmap)
258    else:
259        savepath = None
260    ecv.plot_ndarray(temp_pearson_corrmap, dpi=dpi, subplot_titles=(subpti), cmap='magma', 
261                     plot_size_px=plot_size_px, interactive=False, save=savepath)
262    
263    ### Plotting violin plots: http://seaborn.pydata.org/tutorial/aesthetics.html
264    sns.set_style("whitegrid") #{"axes.facecolor": ".9"}
265    sns.despine(left=True)
266    sns.set_context("notebook")
267    f, ax = plt.subplots(1, 6, figsize=(15, 5), dpi=dpi)
268    for axis in f.axes:
269        axis.tick_params(labelrotation=40)
270
271    ax_ = sns.violinplot(x=np.array(psnr), ax=ax[0], orient='h', color="skyblue", saturation=1, linewidth=0.8)
272    ax_.set_title('PSNR')
273    ax_.set_xlabel(f'$\mu$ = {mean_psnr:.4f} \n$\sigma$ = {std_psnr:.4f}')
274
275    ax_ = sns.violinplot(x=np.array(ssim), ax=ax[1], orient='h', color="skyblue", saturation=1, linewidth=0.8)
276    ax_.set_title('SSIM')
277    ax_.set_xlabel(f'$\mu$ = {mean_ssim:.4f} \n$\sigma$ = {std_ssim:.4f}')
278
279    ax_ = sns.violinplot(x=maes_pairs, ax=ax[2], orient='h', color="skyblue", saturation=1, linewidth=0.8)
280    ax_.set_title('MAE')
281    ax_.set_xlabel(f'$\mu$ = {mean_mae:.4f} \n$\sigma$ = {std_mae:.4f}')
282
283    ax_ = sns.violinplot(x=spatial_rmse, ax=ax[3], orient='h', color="skyblue", saturation=1, linewidth=0.8)
284    ax_.set_title('RMSE')
285    ax_.set_xlabel(f'$\mu$ = {mean_spatial_rmse:.4f} \n$\sigma$ = {std_spatial_rmse:.4f}')
286
287    ax_ = sns.violinplot(x=spatial_pearson_corr, ax=ax[4], orient='h', color="skyblue", saturation=1, linewidth=0.8)
288    ax_.set_title('Pearson correlation')
289    ax_.set_xlabel(f'$\mu$ = {mean_spatial_pearson_corr:.4f} \n$\sigma$ = {std_spatial_pearson_corr:.4f}')
290
291    ax_ = sns.violinplot(x=spatial_spearman_corr, ax=ax[5], orient='h', color="skyblue", saturation=1, linewidth=0.8)
292    ax_.set_title('Spearman correlation')
293    ax_.set_xlabel(f'$\mu$ = {mean_spatial_spearman_corr:.4f} \n$\sigma$ = {std_spatial_spearman_corr:.4f}')
294
295    f.tight_layout()
296    if save_path is not None: 
297        plt.savefig(os.path.join(save_path, 'metrics_violin_plots.png'))
298        plt.close()
299    else:
300        plt.show()
301    
302    sns.set_style("white")
303
304    if save_path is not None: 
305        f = open(os.path.join(save_path, 'metrics_summary.txt'), "a")
306    else:
307        f = None
308
309    print('Metrics on y_test and y_test_hat:\n', file=f)
310    print(f'PSNR \tmu = {mean_psnr} \tsigma = {std_psnr}', file=f)
311    print(f'SSIM \tmu = {mean_ssim} \tsigma = {std_ssim}', file=f)
312    print(f'MAE \tmu = {mean_mae} \tsigma = {std_mae}', file=f)
313    print(f'Per-grid-point RMSE \tmu = {mean_temp_rmse} \tsigma = {std_temp_rmse}', file=f)
314    print(f'Per-grid-point nRMSE \tmu = {norm_mean_temp_rmse} \tsigma = {norm_std_temp_rmse}', file=f)
315    print(f'Per-grid-point Spearman correlation \tmu = {mean_spatial_spearman_corr} \tsigma = {std_spatial_spearman_corr}', file=f)
316    print(f'Per-grid-point Pearson correlation \tmu = {mean_temp_pearson_corr} \tsigma = {std_temp_pearson_corr}', file=f)
317    print(file=f)
318    print(f'Spatial MSE \tmu = {mean_spatial_rmse} \tsigma = {std_spatial_rmse}', file=f)
319    print(f'Spatial Spearman correlation \tmu = {mean_spatial_spearman_corr} \tsigma = {std_spatial_spearman_corr}', file=f)
320    print(f'Spatial Pearson correlation \tmu = {mean_spatial_pearson_corr} \tsigma = {std_spatial_pearson_corr}', file=f)
321
322    if save_path is not None:
323        f.close()
324
325    timing.runtime()
326    return temp_rmse_map, temp_pearson_corrmap, nmeanbias
#   def compute_rmse(y, y_hat, over='time', squared=False, n_jobs=40):
View Source
15def compute_rmse(y, y_hat, over='time', squared=False, n_jobs=40):
16    """
17
18    Parameters
19    ----------
20    squared : bool
21        If True returns MSE value, if False returns RMSE value.
22
23    https://scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html
24    """
25    def rmse_per_px(tupyx):
26        y_coord, x_coord = tupyx
27        return y_coord, x_coord, mean_squared_error(y[:,y_coord,x_coord,0], y_hat[:,y_coord,x_coord,0])
28
29    def rmse_gridpair(index):
30        return mean_squared_error(y[index].flatten(), y_hat[index].flatten(), squared=squared)
31
32    #---------------------------------------------------------------------------
33    if over == 'time':
34        rmse_map = np.zeros_like(y[0,:,:,0]) 
35        rmse_map *= np.nan
36        yy, xx = np.where(y[0,:,:,0])
37        coords = zip(yy, xx)
38        out = Parallel(n_jobs=n_jobs, verbose=False)(delayed(rmse_per_px)(i) for i in coords) 
39
40        for i in range(len(out)):
41            y_coord, x_coord, val = out[i]
42            rmse_map[y_coord, x_coord] = val
43        return rmse_map
44
45    elif over == 'space':
46        n_timesteps = np.arange(y.shape[0])
47        out = Parallel(n_jobs=n_jobs, verbose=False)(delayed(rmse_gridpair)(i) for i in n_timesteps)
48        return out
Parameters
  • squared (bool): If True returns MSE value, if False returns RMSE value.
  • https (//scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html):
#   def compute_correlation(y, y_hat, over='time', mode='spearman', n_jobs=40):
View Source
51def compute_correlation(y, y_hat, over='time', mode='spearman', n_jobs=40):
52    """
53    https://scipy.github.io/devdocs/generated/scipy.stats.spearmanr.html
54
55    https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.pearsonr.html
56    """
57    def corr_per_px(tupyx):
58        if mode == 'spearman':
59            f = spearmanr
60        elif mode == 'pearson':
61            f = pearsonr
62        y_coord, x_coord = tupyx
63        return y_coord, x_coord, f(y[:,y_coord,x_coord,0], y_hat[:,y_coord,x_coord,0])[0]
64
65    def corr_per_gridpair(index):
66        if mode == 'spearman':
67            f = spearmanr
68        elif mode == 'pearson':
69            f = pearsonr
70        return index, f(y[index].ravel(), y_hat[index].ravel())[0]
71
72    #---------------------------------------------------------------------------
73    if over == 'time':
74        corrmap = np.zeros_like(y[0,:,:,0]) 
75        corrmap *= np.nan
76        yy, xx = np.where(y[0,:,:,0])
77        coords = zip(yy, xx)
78        out = Parallel(n_jobs=n_jobs, verbose=False)(delayed(corr_per_px)(i) for i in coords) 
79
80        for i in range(len(out)):
81            y_coord, x_coord, val = out[i]
82            corrmap[y_coord, x_coord] = val
83
84        return corrmap
85
86    elif over == 'space':
87        n_timesteps = np.arange(y.shape[0])
88        out = Parallel(n_jobs=n_jobs, verbose=False)(delayed(corr_per_gridpair)(i) for i in n_timesteps)
89
90        list_corrs = []
91        list_inds = []
92        for i in range(len(out)):
93            index, value = out[i]
94            list_inds.append(index)
95            list_corrs.append(value)
96
97        return list_corrs

https://scipy.github.io/devdocs/generated/scipy.stats.spearmanr.html

https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.pearsonr.html

#   def compute_metrics( y_test, y_test_hat, dpi=150, plot_size_px=1000, n_jobs=-1, scaler=None, mask=None, save_path=None ):
View Source
100def compute_metrics(
101    y_test, 
102    y_test_hat, 
103    dpi=150, 
104    plot_size_px=1000,
105    n_jobs=-1, 
106    scaler=None, 
107    mask=None,
108    save_path=None):
109    """ Compute temporal and spatial-wise metrics, e.g., RMSE and CORRELATION, 
110    based on the groundtruth and prediction ndarrays.
111
112    Parameters
113    ----------
114    y_test : np.ndarray
115        Groundtruth.
116    y_test_hat : np.ndarray
117        Prediction.
118    dpi : int, optional
119        DPI of the plots.  
120    n_jobs : int, optional
121        Number of cores for the computation of metrics (parallelizing over
122        grid points). Passed to joblib.Parallel. If -1 all CPUs are used. If 1 
123        or None is given, no parallel computing code is used at all, which is 
124        useful for debugging.
125    scaler : scaler object
126        Scaler object from preprocessing module. 
127    mask : np.ndarray or None
128        Binary mask with valid (ones) and non-valid (zeroes) grid points.
129    save_path : str or None, optional
130        Path to save results to disk. 
131        
132    """
133    timing = Timing()
134
135    if y_test.ndim == 5:
136        y_test = np.squeeze(y_test, -1)
137        y_test_hat = np.squeeze(y_test_hat, -1)
138
139    y_test = checkarray_ndim(y_test, 4, -1)
140    y_test_hat = checkarray_ndim(y_test_hat, 4, -1)
141
142    # backward transformation with the provided scaler
143    if scaler is not None:
144        if hasattr(scaler, 'inverse_transform'):
145            y_test = scaler.inverse_transform(y_test)
146            y_test_hat = scaler.inverse_transform(y_test_hat)        
147
148    # applying valid grid points mask
149    if mask is not None:
150        if isinstance(mask, xr.DataArray):
151            mask = mask.values.copy()
152        elif isinstance(mask, np.ndarray):
153            mask = mask.copy()
154
155        if mask.ndim == 2:
156            mask = np.expand_dims(mask, -1)
157        y_test = y_test.copy()
158        y_test_hat = y_test_hat.copy()
159        for i in range(y_test.shape[0]):
160            y_test[i] *= mask
161        for i in range(y_test_hat.shape[0]):
162            y_test_hat[i] *= mask
163        mask_nan = mask.astype('float').copy()
164        mask_nan[mask == 0] = np.nan
165        mask = np.squeeze(mask)
166
167    ### Computing metrics
168    drange = max(y_test.max(), y_test_hat.max()) - min(y_test.min(), y_test_hat.min())
169    with tf.device("cpu:0"):
170        psnr = tf.image.psnr(y_test, y_test_hat, drange)
171    mean_psnr = np.mean(psnr)
172    std_psnr = np.std(psnr)
173
174    with tf.device("cpu:0"):
175        ssim = tf.image.ssim(tf.convert_to_tensor(y_test, dtype=tf.float32), 
176                             tf.convert_to_tensor(y_test_hat, dtype=tf.float32), 
177                             drange)
178    mean_ssim = np.mean(ssim)
179    std_ssim = np.std(ssim)
180
181    with tf.device("cpu:0"):
182        maes = tf.keras.metrics.mean_absolute_error(y_test, y_test_hat)
183    maes_pairs = np.mean(maes, axis=(1,2))
184    mean_mae = np.mean(maes_pairs)
185    std_mae = np.std(maes_pairs)
186
187    ### RMSE 
188    temp_rmse_map = compute_rmse(y_test, y_test_hat, n_jobs=n_jobs, over='time')
189    spatial_rmse = compute_rmse(y_test, y_test_hat, n_jobs=n_jobs, over='space')
190    if save_path is not None:
191        np.save(os.path.join(save_path, 'metrics_mse_pergridpair.npy'), spatial_rmse)
192    mean_spatial_rmse = np.mean(spatial_rmse)
193    std_spatial_rmse = np.std(spatial_rmse)
194    mean_temp_rmse = np.nanmean(temp_rmse_map)
195    std_temp_rmse = np.nanstd(temp_rmse_map)
196    if mask is not None:
197        temp_rmse_map[np.where(mask == 0)] = 0
198    subpti = f'RMSE map ($\mu$ = {mean_temp_rmse:.6f})'
199    if save_path is not None:
200        savepath = os.path.join(save_path, 'metrics_pergridpoint_rmse_map.png')
201        np.save(os.path.join(save_path, 'metrics_pergridpoint_rmse_map.npy'), temp_rmse_map)
202    else:
203        savepath = None
204    ecv.plot_ndarray(temp_rmse_map, dpi=dpi, subplot_titles=(subpti), cmap='viridis', 
205                     plot_size_px=plot_size_px, interactive=False, save=savepath)
206
207    ### Normalized per grid point RMSE 
208    norm_temp_rmse_map = temp_rmse_map / (np.mean(y_test) * 100)
209    norm_mean_temp_rmse = np.nanmean(norm_temp_rmse_map)
210    norm_std_temp_rmse = np.nanstd(norm_temp_rmse_map)
211    if mask is not None:
212        norm_temp_rmse_map[np.where(mask == 0)] = 0
213    subpti = f'nRMSE map ($\mu$ = {norm_mean_temp_rmse:.6f})'
214    if save_path is not None:
215        savepath = os.path.join(save_path, 'metrics_pergridpoint_nrmse_map.png')
216        np.save(os.path.join(save_path, 'metrics_pergridpoint_nrmse_map.npy'), norm_temp_rmse_map)
217    else:
218        savepath = None
219    ecv.plot_ndarray(norm_temp_rmse_map, dpi=dpi, subplot_titles=(subpti), cmap='viridis', 
220                     plot_size_px=plot_size_px, interactive=False, save=savepath)
221
222    # Normalized mean bias
223    nmeanbias = np.mean(y_test_hat - y_test, axis=0)
224    nmeanbias /= np.mean(y_test) * 100
225    if mask is not None:
226        nmeanbias *= mask_nan
227    mean_nmeanbias = np.nanmean(nmeanbias)
228    nmeanbias[np.where(mask == 0)] = 0
229    subpti = f'NMBias map ($\mu$ = {mean_nmeanbias:.6f})'
230    if save_path is not None:
231        savepath = os.path.join(save_path, 'metrics_nmeanbias_map.png')
232        np.save(os.path.join(save_path, 'metrics_nmeanbias_map.npy'), nmeanbias)
233    else:
234        savepath = None
235    ecv.plot_ndarray(nmeanbias, dpi=dpi, subplot_titles=(subpti), cmap='viridis', 
236                     plot_size_px=plot_size_px, interactive=False, save=savepath)
237
238    ### Spearman correlation coefficient
239    spatial_spearman_corr = compute_correlation(y_test, y_test_hat, n_jobs=n_jobs, over='space')
240    mean_spatial_spearman_corr = np.mean(spatial_spearman_corr)
241    std_spatial_spearman_corr = np.std(spatial_spearman_corr)
242    if save_path is not None:
243        np.save(os.path.join(save_path, 'metrics_spearcorr_pergridpair.npy'), spatial_spearman_corr)
244
245    ### Pearson correlation coefficient
246    spatial_pearson_corr = compute_correlation(y_test, y_test_hat, mode='pearson', n_jobs=n_jobs, over='space')
247    mean_spatial_pearson_corr = np.mean(spatial_pearson_corr)
248    std_spatial_pearson_corr = np.std(spatial_pearson_corr)
249    if save_path is not None:
250        np.save(os.path.join(save_path, 'metrics_pearcorr_pergridpair.npy'), spatial_pearson_corr)
251    temp_pearson_corrmap = compute_correlation(y_test, y_test_hat, mode='pearson', n_jobs=n_jobs)
252    mean_temp_pearson_corr = np.nanmean(temp_pearson_corrmap)
253    std_temp_pearson_corr = np.nanstd(temp_pearson_corrmap)
254    temp_pearson_corrmap[np.where(mask == 0)] = 0
255    subpti = f'Pearson correlation map ($\mu$ = {mean_temp_pearson_corr:.6f})'
256    if save_path is not None:
257        savepath = os.path.join(save_path, 'metrics_pergridpoint_corrpears_map.png')
258        np.save(os.path.join(save_path, 'metrics_pergridpoint_corrpears_map.npy'), temp_pearson_corrmap)
259    else:
260        savepath = None
261    ecv.plot_ndarray(temp_pearson_corrmap, dpi=dpi, subplot_titles=(subpti), cmap='magma', 
262                     plot_size_px=plot_size_px, interactive=False, save=savepath)
263    
264    ### Plotting violin plots: http://seaborn.pydata.org/tutorial/aesthetics.html
265    sns.set_style("whitegrid") #{"axes.facecolor": ".9"}
266    sns.despine(left=True)
267    sns.set_context("notebook")
268    f, ax = plt.subplots(1, 6, figsize=(15, 5), dpi=dpi)
269    for axis in f.axes:
270        axis.tick_params(labelrotation=40)
271
272    ax_ = sns.violinplot(x=np.array(psnr), ax=ax[0], orient='h', color="skyblue", saturation=1, linewidth=0.8)
273    ax_.set_title('PSNR')
274    ax_.set_xlabel(f'$\mu$ = {mean_psnr:.4f} \n$\sigma$ = {std_psnr:.4f}')
275
276    ax_ = sns.violinplot(x=np.array(ssim), ax=ax[1], orient='h', color="skyblue", saturation=1, linewidth=0.8)
277    ax_.set_title('SSIM')
278    ax_.set_xlabel(f'$\mu$ = {mean_ssim:.4f} \n$\sigma$ = {std_ssim:.4f}')
279
280    ax_ = sns.violinplot(x=maes_pairs, ax=ax[2], orient='h', color="skyblue", saturation=1, linewidth=0.8)
281    ax_.set_title('MAE')
282    ax_.set_xlabel(f'$\mu$ = {mean_mae:.4f} \n$\sigma$ = {std_mae:.4f}')
283
284    ax_ = sns.violinplot(x=spatial_rmse, ax=ax[3], orient='h', color="skyblue", saturation=1, linewidth=0.8)
285    ax_.set_title('RMSE')
286    ax_.set_xlabel(f'$\mu$ = {mean_spatial_rmse:.4f} \n$\sigma$ = {std_spatial_rmse:.4f}')
287
288    ax_ = sns.violinplot(x=spatial_pearson_corr, ax=ax[4], orient='h', color="skyblue", saturation=1, linewidth=0.8)
289    ax_.set_title('Pearson correlation')
290    ax_.set_xlabel(f'$\mu$ = {mean_spatial_pearson_corr:.4f} \n$\sigma$ = {std_spatial_pearson_corr:.4f}')
291
292    ax_ = sns.violinplot(x=spatial_spearman_corr, ax=ax[5], orient='h', color="skyblue", saturation=1, linewidth=0.8)
293    ax_.set_title('Spearman correlation')
294    ax_.set_xlabel(f'$\mu$ = {mean_spatial_spearman_corr:.4f} \n$\sigma$ = {std_spatial_spearman_corr:.4f}')
295
296    f.tight_layout()
297    if save_path is not None: 
298        plt.savefig(os.path.join(save_path, 'metrics_violin_plots.png'))
299        plt.close()
300    else:
301        plt.show()
302    
303    sns.set_style("white")
304
305    if save_path is not None: 
306        f = open(os.path.join(save_path, 'metrics_summary.txt'), "a")
307    else:
308        f = None
309
310    print('Metrics on y_test and y_test_hat:\n', file=f)
311    print(f'PSNR \tmu = {mean_psnr} \tsigma = {std_psnr}', file=f)
312    print(f'SSIM \tmu = {mean_ssim} \tsigma = {std_ssim}', file=f)
313    print(f'MAE \tmu = {mean_mae} \tsigma = {std_mae}', file=f)
314    print(f'Per-grid-point RMSE \tmu = {mean_temp_rmse} \tsigma = {std_temp_rmse}', file=f)
315    print(f'Per-grid-point nRMSE \tmu = {norm_mean_temp_rmse} \tsigma = {norm_std_temp_rmse}', file=f)
316    print(f'Per-grid-point Spearman correlation \tmu = {mean_spatial_spearman_corr} \tsigma = {std_spatial_spearman_corr}', file=f)
317    print(f'Per-grid-point Pearson correlation \tmu = {mean_temp_pearson_corr} \tsigma = {std_temp_pearson_corr}', file=f)
318    print(file=f)
319    print(f'Spatial MSE \tmu = {mean_spatial_rmse} \tsigma = {std_spatial_rmse}', file=f)
320    print(f'Spatial Spearman correlation \tmu = {mean_spatial_spearman_corr} \tsigma = {std_spatial_spearman_corr}', file=f)
321    print(f'Spatial Pearson correlation \tmu = {mean_spatial_pearson_corr} \tsigma = {std_spatial_pearson_corr}', file=f)
322
323    if save_path is not None:
324        f.close()
325
326    timing.runtime()
327    return temp_rmse_map, temp_pearson_corrmap, nmeanbias

Compute temporal and spatial-wise metrics, e.g., RMSE and CORRELATION, based on the groundtruth and prediction ndarrays.

Parameters
  • y_test (np.ndarray): Groundtruth.
  • y_test_hat (np.ndarray): Prediction.
  • dpi (int, optional): DPI of the plots.
  • n_jobs (int, optional): Number of cores for the computation of metrics (parallelizing over grid points). Passed to joblib.Parallel. If -1 all CPUs are used. If 1 or None is given, no parallel computing code is used at all, which is useful for debugging.
  • scaler (scaler object): Scaler object from preprocessing module.
  • mask (np.ndarray or None): Binary mask with valid (ones) and non-valid (zeroes) grid points.
  • save_path (str or None, optional): Path to save results to disk.