BrierScore.R 10.3 KB
Newer Older
aho's avatar
aho committed
#'Compute Brier score and its decomposition and Brier skill score
#'
#'Compute the Brier score (BS) and the components of its standard decompostion
#'as well with the two within-bin components described in Stephenson et al., 
#'(2008). It also returns the bias-corrected decomposition of the BS (Ferro and
#'Fricker, 2012). BSS has the climatology as the reference forecast. 

#'.BrierScore provides the same functionality, but taking a matrix of ensemble 
#'members (exp) as input.
#'
#'@param obs Vector of binary observations (1 or 0).
#'@param pred Vector of probablistic predictions with values in the range [0,1].
#'@param thresholds Values used to bin the forecasts. By default the bins are 
#'  {[0,0.1), [0.1, 0.2), ... [0.9, 1]}.
#'@param exp Matrix of predictions with values in the range [0,1] for the 
#'  .BrierScore function
#'
#'@return Both BrierScore and .Brier score provide the same outputs:
#'\itemize{ 
#'  \item{$rel}{standard reliability}
#'  \item{$res}{standard resolution}
#'  \item{$unc}{standard uncertainty}  
#'  \item{$bs}{Brier score}
#'  \item{$bs_check_res}{rel-res+unc}
#'  \item{$bss_res}{res-rel/unc}
#'  \item{$gres}{generalized resolution}
#'  \item{$bs_check_gres}{rel-gres+unc}
#'  \item{$bss_gres}{gres-rel/unc}
#'  \item{$rel_bias_corrected}{bias-corrected rel}
#'  \item{$gres_bias_corrected}{bias-corrected gres}
#'  \item{$unc_bias_corrected}{bias-corrected unc}
#'  \item{$bss_bias_corrected}{gres_bias_corrected-rel_bias_corrected/unc_bias_corrected}
#'  \item{$nk}{number of forecast in each bin}
#'  \item{$fkbar}{average probability of each bin}
#'  \item{$okbar}{relative frequency that the observed event occurred}
#'  \item{$bins}{bins used}
#'  \item{$pred}{values with which the forecasts are verified}
#'  \item{$obs}{probability forecasts of the event}
#'}
#'
#'@references
#'Wilks (2006) Statistical Methods in the Atmospheric Sciences.\cr
#'Stephenson et al. (2008). Two extra components in the Brier score decomposition. 
#'  Weather and Forecasting, 23: 752-757.\cr
#'Ferro and Fricker (2012). A bias-corrected decomposition of the BS. 
#'  Quarterly Journal of the Royal Meteorological Society, DOI: 10.1002/qj.1924.
#'
#'@examples
#'# Minimalist examples with BrierScore
#'a <- runif(10)
#'b <- round(a)
#'x <- BrierScore(b, a)
#'x$bs - x$bs_check_res
#'x$bs - x$bs_check_gres
#'x$rel_bias_corrected - x$gres_bias_corrected + x$unc_bias_corrected
#'  \dontrun{
#'a <- runif(10)
#'b <- cbind(round(a),round(a)) # matrix containing 2 identical ensemble members...
#'x2 <- BrierScore(a, b)
#'  }
#'
#'# Example of BrierScore using UltimateBrier
#'# See ?UltimateBrier for more information
#'example(Load)
#'clim <- Clim(sampleData$mod, sampleData$obs)
#'ano_exp <- Ano(sampleData$mod, clim$clim_exp)
#'ano_obs <- Ano(sampleData$obs, clim$clim_obs)
#'bs <- UltimateBrier(ano_exp, ano_obs, thr = c(1/3, 2/3))
#'
#'  \dontrun{
#'# Example of .BrierScore with veriApply
#'require(easyVerification)
#'BrierScore2 <- s2dverification:::.BrierScore
#'bins_ano_exp <- ProbBins(ano_exp, thr = c(1/3, 2/3), posdates = 3, posdim = 2)
#'bins_ano_obs <- ProbBins(ano_obs, thr = c(1/3, 2/3), posdates = 3, posdim = 2)
#'bs2 <- veriApply("BrierScore2", bins_ano_exp, Mean1Dim(bins_ano_ob,s 3), 
#'                 tdim = 2, ensdim = 3)
#'  }
#'@import multiApply
#'@export
BrierScore <- function(obs, pred, thresholds = seq(0, 1, 0.1)) {
  if (max(pred) > 1 | min(pred) < 0) {
    stop("Predictions outside [0,1] range. Are you certain this is a probability forecast? \n")
  } else if (max(obs) != 1 & min(obs) != 0) {
    .message("Binary events must be either 0 or 1. Are you certain this is a binary event? ")
  } else {
    nbins <- length(thresholds) - 1  # Number of bins
    n <- length(pred)
    bins <- as.list(paste("bin", 1:nbins,sep = ""))
    for (i in 1:nbins) {
      if (i == nbins) {
        bins[[i]] <- list(which(pred >= thresholds[i] & pred <= thresholds[i + 1]))
      } else {
        bins[[i]] <- list(which(pred >= thresholds[i] & pred < thresholds[i + 1]))
      }
    }
    
    fkbar <- okbar <- nk <- array(0, dim = nbins)
    for (i in 1:nbins) {
      nk[i] <- length(bins[[i]][[1]])
      fkbar[i] <- sum(pred[bins[[i]][[1]]]) / nk[i]
      okbar[i] <- sum(obs[bins[[i]][[1]]]) / nk[i]
    }
    
    obar <- sum(obs) / length(obs)
    relsum <- ressum <- term1 <- term2 <- 0
    for (i in 1:nbins) {
      if (nk[i] > 0) {
        relsum <- relsum + nk[i] * (fkbar[i] - okbar[i])^2
        ressum <- ressum + nk[i] * (okbar[i] - obar)^2
        for (j in 1:nk[i]) {
          term1 <- term1 + (pred[bins[[i]][[1]][j]] - fkbar[i])^2
          term2 <- term2 + (pred[bins[[i]][[1]][j]] - fkbar[i]) * (obs[bins[[i]][[1]][j]] - okbar[i])
        }
      }
    }
    rel <- relsum / n
    res <- ressum / n
    unc <- obar * (1 - obar)
    bs <- sum((pred - obs)^2) / n
    bs_check_res <- rel - res + unc
    bss_res <- (res - rel) / unc
    gres <- res - term1 * (1 / n) + term2 * (2 / n)   # Generalized resolution
    bs_check_gres <- rel - gres + unc                 # BS using GRES
    bss_gres <- (gres - rel) / unc                    # BSS using GRES
    
    #
    # Estimating the bias-corrected components of the BS 
    #
    term3 <- array(0, nbins)
    for (i in 1:nbins) {
      term3[i] <- (nk[i] / (nk[i] - 1)) * okbar[i] * (1 - okbar[i])
    }
    term_a <- sum(term3,  na.rm = T) / n
    term_b <- (obar * (1 - obar)) / (n - 1)
    rel_bias_corrected <- rel - term_a
    gres_bias_corrected <- gres - term_a + term_b
    if (rel_bias_corrected < 0 || gres_bias_corrected < 0) {
      rel_bias_corrected2 <- max(rel_bias_corrected, rel_bias_corrected - gres_bias_corrected, 0)
      gres_bias_corrected2 <- max(gres_bias_corrected, gres_bias_corrected - rel_bias_corrected, 0)
      rel_bias_corrected <- rel_bias_corrected2
      gres_bias_corrected <- gres_bias_corrected2
    }
    unc_bias_corrected <- unc + term_b
    bss_bias_corrected <- (gres_bias_corrected - rel_bias_corrected) / unc_bias_corrected
    
    #if (round(bs, 8) == round(bs_check_gres, 8) & round(bs_check_gres, 8) == round((rel_bias_corrected - gres_bias_corrected + unc_bias_corrected), 8)) {
    #  cat("No error found  \ n")
    #  cat("BS = REL - GRES + UNC = REL_lessbias - GRES_lessbias + UNC_lessbias  \ n")
    #}
    
    invisible(list(rel = rel, res = res, unc = unc, bs = bs, bs_check_res = bs_check_res, bss_res = bss_res, gres = gres, bs_check_gres = bs_check_gres, bss_gres = bss_gres, rel_bias_corrected = rel_bias_corrected, gres_bias_corrected = gres_bias_corrected, unc_bias_corrected = unc_bias_corrected, bss_bias_corrected = bss_bias_corrected, nk = nk, fkbar = fkbar, okbar = okbar, bins = bins, pred = pred, obs = obs))
  }
}

#'@rdname BrierScore
#'@export
.BrierScore <- function(exp, obs, thresholds = seq(0, 1, 0.1)) {
  if (max(exp) > 1 || min(exp) < 0) {
    stop("Parameter 'exp' contains predictions outside [0,1] range. Are you certain this is a probability forecast?")
  } else if (max(obs) != 1 && min(obs) != 0) {
    .message("Binary events in 'obs' must be either 0 or 1. Are you certain this is a binary event?")
  } else {
    nbins <- length(thresholds) - 1  # Number of bins
    n <- dim(exp)[1]                 # Number of observations
    ens_mean <- rowMeans(exp, na.rm = TRUE)
    n.ens <- seq(1, dim(exp)[2], 1)  # Number of ensemble members
    bins <- as.list(paste("bin", 1:nbins, sep = ""))
    for (i in 1:nbins) {
      if (i == nbins) {
        bins[[i]] <- list(which(ens_mean >= thresholds[i] & ens_mean <= thresholds[i + 1])) 
      } else {
        bins[[i]] <- list(which(ens_mean >= thresholds[i] & ens_mean < thresholds[i + 1])) 
      }
    }

    fkbar <- okbar <- nk <- array(0, dim = nbins)
    for (i in 1:nbins) {
      nk[i] <- length(bins[[i]][[1]])
      fkbar[i] <- sum(ens_mean[bins[[i]][[1]]]) / nk[i]
      okbar[i] <- sum(obs[bins[[i]][[1]]]) / nk[i]
    }

    fkbar[fkbar == Inf] <- 0 
    okbar[is.nan(okbar)] <- 0
    obar <- sum(obs) / length(obs)
    relsum <- ressum <- relsum1 <- ressum1 <- term1 <- term1a <- term2 <- term2a <- 0

    for (i in 1:nbins) {
      if (nk[i] > 0) {
        relsum <- relsum + nk[i] * (fkbar[i] - okbar[i]) ^ 2
        ressum <- ressum + nk[i] * (okbar[i] - obar) ^ 2
        for (j in 1:nk[i]) {
          term1 <- term1 + (ens_mean[bins[[i]][[1]][j]] - fkbar[i]) ^ 2
          term2 <- term2 + (ens_mean[bins[[i]][[1]][j]] - fkbar[i]) * (obs[bins[[i]][[1]][j]] - okbar[i])
        }
      }
    }
  }
  rel <- relsum / n
  res <- ressum / n
  unc <- obar * (1 - obar)
  #bs <- apply(ens, MARGIN = 2, FUN = function(x) sum((x - obs)^2) / n)
  bs <- sum((rowMeans(exp, na.rm = T) - obs) ^ 2) / n
  bs_check_res <- rel - res + unc
  bss_res <- (res - rel) / unc
  gres <- res - term1 * (1 / n) + term2 * (2 / n)   # Generalized resolution
  bs_check_gres <- rel - gres + unc                 # BS using GRES
  bss_gres <- (gres - rel) / unc                    # BSS using GRES
  
  #
  # Estimating the bias-corrected components of the BS 
  #
  term3 <- array(0, nbins)
  for (i in 1:nbins) {
    term3[i] <- (nk[i] / (nk[i] - 1)) * okbar[i] * (1 - okbar[i])
  }
  term_a <- sum(term3,  na.rm = T) / n
  term_b <- (obar * (1 - obar)) / (n - 1)
  rel_bias_corrected <- rel - term_a
  gres_bias_corrected <- gres - term_a + term_b
  if (rel_bias_corrected < 0 || gres_bias_corrected < 0) {
    rel_bias_corrected2 <- max(rel_bias_corrected, rel_bias_corrected - gres_bias_corrected, 0)
    gres_bias_corrected2 <- max(gres_bias_corrected, gres_bias_corrected - rel_bias_corrected, 0)
    rel_bias_corrected <- rel_bias_corrected2
    gres_bias_corrected <- gres_bias_corrected2
  }
  unc_bias_corrected <- unc + term_b
  bss_bias_corrected <- (gres_bias_corrected - rel_bias_corrected) / unc_bias_corrected
    
  #if (round(bs, 8) == round(bs_check_gres, 8) & round(bs_check_gres, 8) == round((rel_bias_corrected - gres_bias_corrected + unc_bias_corrected), 8)) {
  #  cat("No error found  \ n")
  #  cat("BS = REL - GRES + UNC = REL_lessbias - GRES_lessbias + UNC_lessbias  \ n")
  #}
    
  invisible(list(rel = rel, res = res, unc = unc, bs = bs, 
                 bs_check_res = bs_check_res, bss_res = bss_res, gres = gres, 
                 bs_check_gres = bs_check_gres, bss_gres = bss_gres, 
                 rel_bias_corrected = rel_bias_corrected, 
                 gres_bias_corrected = gres_bias_corrected, 
                 unc_bias_corrected = unc_bias_corrected, 
                 bss_bias_corrected = bss_bias_corrected))
}