|
|
import os
|
|
|
import concurrent
|
|
|
import multiprocessing
|
|
|
|
|
|
import netCDF4
|
|
|
import numpy as np
|
|
|
from scipy.integrate import trapezoid
|
|
|
|
|
|
from ..features.io import load_batch
|
|
|
|
|
|
|
|
|
def confusion_matrix(fc_frac, obs_frac, prob_threshold):
|
|
|
N = np.prod(fc_frac.shape)
|
|
|
fc_above = fc_frac > prob_threshold
|
|
|
obs_above = obs_frac > prob_threshold
|
|
|
tp = np.count_nonzero(fc_above & obs_above) / N
|
|
|
fp = np.count_nonzero(fc_above & ~obs_above) / N
|
|
|
fn = np.count_nonzero(~fc_above & obs_above) / N
|
|
|
tn = 1.0 - tp - fp - fn
|
|
|
return np.array(((tp, fn), (fp, tn)))
|
|
|
|
|
|
|
|
|
def confusion_matrix_thresholds(fc_frac, obs_frac, thresholds):
|
|
|
N_threads = multiprocessing.cpu_count()
|
|
|
with concurrent.futures.ThreadPoolExecutor(N_threads) as executor:
|
|
|
futures = [
|
|
|
executor.submit(confusion_matrix, fc_frac, obs_frac, t)
|
|
|
for t in thresholds
|
|
|
]
|
|
|
conf_matrix = [f.result() for f in futures]
|
|
|
return np.stack(conf_matrix, axis=-1)
|
|
|
|
|
|
|
|
|
def confusion_matrix_thresholds_leadtime(fc_frac, obs_frac, thresholds):
|
|
|
N_threads = multiprocessing.cpu_count()
|
|
|
conf_matrix = []
|
|
|
with concurrent.futures.ThreadPoolExecutor(N_threads) as executor:
|
|
|
for lt in range(fc_frac.shape[2]):
|
|
|
futures = [
|
|
|
executor.submit(confusion_matrix,
|
|
|
fc_frac[...,lt,:,:], obs_frac[...,lt,:,:], t)
|
|
|
for t in thresholds
|
|
|
]
|
|
|
conf_matrix_lt = [f.result() for f in futures]
|
|
|
conf_matrix_lt = np.stack(conf_matrix_lt, axis=-1)
|
|
|
conf_matrix.append(conf_matrix_lt)
|
|
|
|
|
|
return np.stack(conf_matrix, axis=-2)
|
|
|
|
|
|
|
|
|
|
|
|
def precision(conf_matrix):
|
|
|
((tp, fn), (fp, tn)) = conf_matrix
|
|
|
precision = tp / (tp + fp)
|
|
|
precision[np.isnan(precision)] = 1.0
|
|
|
return precision
|
|
|
|
|
|
|
|
|
def recall(conf_matrix):
|
|
|
((tp, fn), (fp, tn)) = conf_matrix
|
|
|
return tp / (tp + fn)
|
|
|
|
|
|
|
|
|
def false_alarm_ratio(conf_matrix):
|
|
|
return 1.0 - precision(conf_matrix)
|
|
|
|
|
|
|
|
|
def intersection_over_union(conf_matrix):
|
|
|
((tp, fn), (fp, tn)) = conf_matrix
|
|
|
return tp / (tp+fp+fn)
|
|
|
|
|
|
|
|
|
def equitable_threat_score(conf_matrix):
|
|
|
((tp, fn), (fp, tn)) = conf_matrix
|
|
|
tp_rnd = (tp+fn) * (tp+fp) / (tp+fp+tn+fn)
|
|
|
return (tp-tp_rnd) / (tp+fp+fn-tp_rnd)
|
|
|
|
|
|
|
|
|
def peirce_skill_score(conf_matrix):
|
|
|
((tp, fn), (fp, tn)) = conf_matrix
|
|
|
return (tp*tn - fn*fp) / ((tp+fn) * (fp+tn))
|
|
|
|
|
|
|
|
|
def heidke_skill_score(conf_matrix):
|
|
|
((tp, fn), (fp, tn)) = conf_matrix
|
|
|
return 2 * (tp*tn - fn*fp) / ((tp+fn)*(fn+tn) + (tp+fp)*(fp+tn))
|
|
|
|
|
|
|
|
|
def roc_area_under_curve(conf_matrix):
|
|
|
((tp, fn), (fp, tn)) = conf_matrix
|
|
|
tpr = tp / (tp + fn)
|
|
|
fpr = fp / (fp + tn)
|
|
|
|
|
|
auc = trapezoid(tpr[::-1], x=fpr[::-1])
|
|
|
return auc
|
|
|
|
|
|
|
|
|
def pr_area_under_curve(conf_matrix):
|
|
|
prec = precision(conf_matrix)
|
|
|
rec = recall(conf_matrix)
|
|
|
|
|
|
if (rec[-1] != 0) or (prec[-1] != 1):
|
|
|
rec = np.hstack((rec, 0.0))
|
|
|
prec = np.hstack((prec, 1.0))
|
|
|
|
|
|
auc = trapezoid(prec[::-1], x=rec[::-1])
|
|
|
return auc
|
|
|
|
|
|
|
|
|
def cost_loss_value(conf_matrix, cost, loss, p_clim):
|
|
|
((tp, fn), (fp, tn)) = conf_matrix
|
|
|
|
|
|
E_c = min(cost, p_clim*loss)
|
|
|
E_p = p_clim * cost
|
|
|
E_f = (tp+fp)*cost + fn*loss
|
|
|
|
|
|
return (E_f - E_c) / (E_p - E_c)
|
|
|
|