From 574ce5898c6cb529fe959e40059370888448813f Mon Sep 17 00:00:00 2001 From: jcos Date: Wed, 22 Jun 2022 15:33:33 +0200 Subject: [PATCH 001/140] split weights into weights_ref and weights_exp --- R/RPSS.R | 56 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/R/RPSS.R b/R/RPSS.R index 3b24777..50d4c3f 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -34,11 +34,13 @@ #'@param Fair A logical indicating whether to compute the FairRPSS (the #' potential RPSS that the forecast would have with an infinite ensemble size). #' The default value is FALSE. -#'@param weights A named two-dimensional numerical array of the weights for each -#' member and time. The dimension names should include 'memb_dim' and -#' 'time_dim'. The default value is NULL. The ensemble should have at least 70 -#' members or span at least 10 time steps and have more than 45 members if -#' consistency between the weighted and unweighted methodologies is desired. +#'@param weights_exp A named two-dimensional numerical array of the forecast +#' ensemble weights for each member and time. The dimension names should +#' include 'memb_dim' and 'time_dim'. The default value is NULL. The ensemble +#' should have at least 70 members or span at least 10 time steps and have +#' more than 45 members if consistency between the weighted and unweighted +#' methodologies is desired. +#'@param weights_ref Same as 'weights_exp' but for the reference ensemble. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -67,7 +69,7 @@ #'@export RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, - weights = NULL, ncores = NULL) { + weights_exp = NULL, weights_ref = NULL, ncores = NULL) { # Check inputs ## exp, obs, and ref (1) @@ -140,17 +142,29 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!is.logical(Fair) | length(Fair) > 1) { stop("Parameter 'Fair' must be either TRUE or FALSE.") } - ## weights - if (!is.null(weights)) { - if (!is.array(weights) | !is.numeric(weights)) - stop('Parameter "weights" must be a two-dimensional numeric array.') - if (length(dim(weights)) != 2 | any(!names(dim(weights)) %in% c(memb_dim, time_dim))) - stop("Parameter 'weights' must have two dimensions with the names of memb_dim and time_dim.") - if (dim(weights)[memb_dim] != dim(exp)[memb_dim] | - dim(weights)[time_dim] != dim(exp)[time_dim]) { - stop("Parameter 'weights' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") + ## weights_exp + if (!is.null(weights_exp)) { + if (!is.array(weights_exp) | !is.numeric(weights_exp)) + stop('Parameter "weights_exp" must be a two-dimensional numeric array.') + if (length(dim(weights_exp)) != 2 | any(!names(dim(weights_exp)) %in% c(memb_dim, time_dim))) + stop("Parameter 'weights_exp' must have two dimensions with the names of memb_dim and time_dim.") + if (dim(weights_exp)[memb_dim] != dim(exp)[memb_dim] | + dim(weights_exp)[time_dim] != dim(exp)[time_dim]) { + stop("Parameter 'weights_exp' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") } - weights <- Reorder(weights, c(time_dim, memb_dim)) + weights_exp <- Reorder(weights_exp, c(time_dim, memb_dim)) + } + ## weights_ref + if (!is.null(weights_ref)) { + if (!is.array(weights_ref) | !is.numeric(weights_ref)) + stop('Parameter "weights_ref" must be a two-dimensional numeric array.') + if (length(dim(weights_ref)) != 2 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim))) + stop("Parameter 'weights_ref' must have two dimensions with the names of memb_dim and time_dim.") + if (dim(weights_ref)[memb_dim] != dim(exp)[memb_dim] | + dim(weights_ref)[time_dim] != dim(exp)[time_dim]) { + stop("Parameter 'weights_ref' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") + } + weights_ref <- Reorder(weights_ref, c(time_dim, memb_dim)) } ## ncores if (!is.null(ncores)) { @@ -184,21 +198,23 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', fun = .RPSS, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, - weights = weights, + weights_exp = weights_exp, + weights_ref = weights_ref, ncores = ncores) return(output) } .RPSS <- function(exp, obs, ref = NULL, prob_thresholds = c(1/3, 2/3), - indices_for_clim = NULL, Fair = FALSE, weights = NULL) { + indices_for_clim = NULL, Fair = FALSE, + weights_exp = NULL, weights_ref = NULL) { # exp: [sdate, memb] # obs: [sdate, (memb)] # ref: [sdate, memb] or NULL # RPS of the forecast rps_exp <- .RPS(exp = exp, obs = obs, prob_thresholds = prob_thresholds, - indices_for_clim = indices_for_clim, Fair = Fair, weights = weights) + indices_for_clim = indices_for_clim, Fair = Fair, weights = weights_exp) # RPS of the reference forecast if (is.null(ref)) { ## using climatology as reference forecast @@ -227,7 +243,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } else { # use "ref" as reference forecast rps_ref <- .RPS(exp = ref, obs = obs, prob_thresholds = prob_thresholds, - indices_for_clim = indices_for_clim, Fair = Fair, weights = weights) + indices_for_clim = indices_for_clim, Fair = Fair, weights = weights_ref) } # RPSS -- GitLab From fdd671e373e80fa76c3970945eee1bd5813eb3a6 Mon Sep 17 00:00:00 2001 From: jcos Date: Wed, 22 Jun 2022 15:46:10 +0200 Subject: [PATCH 002/140] fix test --- tests/testthat/test-RPSS.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index c63add0..0e98320 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -168,7 +168,7 @@ c(0.5259259, 0.4771242), tolerance = 0.0001 ) expect_equal( -as.vector(RPSS(exp1, obs1, ref1, weights = weights1)$rpss), +as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1)$rpss), c(0.6596424, 0.4063579), tolerance = 0.0001 ) -- GitLab From 88114fd001130a1ae0d148df10fb6439c93c89ae Mon Sep 17 00:00:00 2001 From: jcos Date: Tue, 28 Jun 2022 13:02:07 +0200 Subject: [PATCH 003/140] fix test differences --- tests/testthat/test-RPSS.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index 0e98320..081cb56 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -168,7 +168,7 @@ c(0.5259259, 0.4771242), tolerance = 0.0001 ) expect_equal( -as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1)$rpss), +as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1, weights_ref = weights)$rpss), c(0.6596424, 0.4063579), tolerance = 0.0001 ) -- GitLab From 4c8a83b255897055f1cd5b4f245041a35b1e2b7b Mon Sep 17 00:00:00 2001 From: jcos Date: Tue, 28 Jun 2022 13:26:02 +0200 Subject: [PATCH 004/140] fix wrong weights array name in test --- tests/testthat/test-RPSS.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index 081cb56..2b80be3 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -168,7 +168,7 @@ c(0.5259259, 0.4771242), tolerance = 0.0001 ) expect_equal( -as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1, weights_ref = weights)$rpss), +as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1, weights_ref = weights1)$rpss), c(0.6596424, 0.4063579), tolerance = 0.0001 ) -- GitLab From 208ed9743b1a5ceb4501ee6081d7654c25e46b52 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 29 Jun 2022 16:12:54 +0200 Subject: [PATCH 005/140] Allow dat_dim = NULL in Corr() function --- R/Corr.R | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index 0382a39..3cf640d 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -23,7 +23,8 @@ #'@param time_dim A character string indicating the name of dimension along #' which the correlations are computed. The default value is 'sdate'. #'@param dat_dim A character string indicating the name of dataset (nobs/nexp) -#' dimension. The default value is 'dataset'. +#' dimension. The default value is 'dataset'. If there is no dataset +#' dimension, set NULL. #'@param comp_dim A character string indicating the name of dimension along which #' obs is taken into account only if it is complete. The default value #' is NULL. @@ -51,7 +52,8 @@ #' c(nexp, nobs, exp_memb, obs_memb, all other dimensions of exp except #' time_dim and memb_dim).\cr #'nexp is the number of experiment (i.e., 'dat_dim' in exp), and nobs is the -#'number of observation (i.e., 'dat_dim' in obs). exp_memb is the number of +#'number of observation (i.e., 'dat_dim' in obs). If +#' dat_dim is NULL, nexp and nobs are omitted. exp_memb is the number of #'member in experiment (i.e., 'memb_dim' in exp) and obs_memb is the number of #'member in observation (i.e., 'memb_dim' in obs).\cr\cr #'\item{$corr}{ @@ -129,11 +131,14 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string or NULL.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + "Set it as NULL if there is no dataset dimension.") + } } ## comp_dim if (!is.null(comp_dim)) { @@ -194,8 +199,10 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) - name_exp <- name_exp[-which(name_exp == dat_dim)] - name_obs <- name_obs[-which(name_obs == dat_dim)] + if (!is.null(dat_dim)) { + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] + } if (!is.null(memb_dim)) { name_exp <- name_exp[-which(name_exp == memb_dim)] name_obs <- name_obs[-which(name_obs == memb_dim)] @@ -327,15 +334,19 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', return(res) } -.Corr <- function(exp, obs, time_dim = 'sdate', method = 'pearson', +.Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', method = 'pearson', conf = TRUE, pval = TRUE, conf.lev = 0.95, ncores_input = NULL) { if (length(dim(exp)) == 2) { # exp: [sdate, dat_exp] # obs: [sdate, dat_obs] + if (is.null(dat_dim)) { + nexp <- 1 + nobs <- 1 + } else { nexp <- as.numeric(dim(exp)[2]) nobs <- as.numeric(dim(obs)[2]) - + } # NOTE: Use sapply to replace the for loop CORR <- sapply(1:nobs, function(i) { sapply(1:nexp, function (x) { @@ -356,11 +367,15 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', # exp: [sdate, dat_exp, memb_exp] # obs: [sdate, dat_obs, memb_obs] + if (is.null(dat_dim)) { + nexp <- 1 + nobs <- 1 + } else { nexp <- as.numeric(dim(exp)[2]) nobs <- as.numeric(dim(obs)[2]) exp_memb <- as.numeric(dim(exp)[3]) obs_memb <- as.numeric(dim(obs)[3]) - + } CORR <- array(dim = c(nexp = nexp, nobs = nobs, exp_memb = exp_memb, obs_memb = obs_memb)) for (j in 1:obs_memb) { -- GitLab From 5743f5818bb9aa785472830ab1b54c97a64aff01 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 29 Jun 2022 17:18:10 +0200 Subject: [PATCH 006/140] Allow dat_dim = NULL in Consist_Trend() function --- R/Consist_Trend.R | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/R/Consist_Trend.R b/R/Consist_Trend.R index b02aa5f..5ac797e 100644 --- a/R/Consist_Trend.R +++ b/R/Consist_Trend.R @@ -14,7 +14,8 @@ #'@param dat_dim A character string indicating the name of the dataset #' dimensions. If data at some point of 'time_dim' are not complete along #' 'dat_dim' in both 'exp' and 'obs', this point in all 'dat_dim' will be -#' discarded. The default value is 'dataset'. +#' discarded. The default value is 'dataset'. If there is no dataset +#' dimension, set NULL. #'@param time_dim A character string indicating the name of dimension along #' which the trend is computed. The default value is 'sdate'. #'@param interval A positive numeric indicating the unit length between two @@ -29,7 +30,8 @@ #' with dimensions c(stats = 2, nexp + nobs, the rest dimensions of 'exp' and #' 'obs' except time_dim), where 'nexp' is the length of 'dat_dim' in 'exp' #' and 'nobs' is the length of 'dat_dim' in 'obs. The 'stats' dimension -#' contains the intercept and the slope. +#' contains the intercept and the slope. If dat_dim is NULL, nexp and nobs are +#' omitted. #'} #'\item{$conf.lower}{ #' A numeric array of the lower limit of 95\% confidence interval with @@ -109,19 +111,24 @@ Consist_Trend <- function(exp, obs, dat_dim = 'dataset', time_dim = 'sdate', int stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.character(dat_dim)) { - stop("Parameter 'dat_dim' must be a character string.") - } - if (!all(dat_dim %in% names(dim(exp))) | !all(dat_dim %in% names(dim(obs)))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string or NULL.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + "Set it as NULL if there is no dataset dimension.") + } } ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) + if (!is.null(dat_dim)) { for (i in 1:length(dat_dim)) { name_exp <- name_exp[-which(name_exp == dat_dim[i])] name_obs <- name_obs[-which(name_obs == dat_dim[i])] } + } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", "all dimension expect 'dat_dim'.")) -- GitLab From c9b969fd231bb1bfc61023d31e51d550685ba9e8 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 29 Jun 2022 17:26:18 +0200 Subject: [PATCH 007/140] Allow dat_dim = NULL in RMS() function --- R/RMS.R | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/R/RMS.R b/R/RMS.R index b3c8ad4..194a3f6 100644 --- a/R/RMS.R +++ b/R/RMS.R @@ -21,7 +21,8 @@ #'@param time_dim A character string indicating the name of dimension along #' which the correlations are computed. The default value is 'sdate'. #'@param dat_dim A character string indicating the name of member (nobs/nexp) -#' dimension. The default value is 'dataset'. +#' dimension. The default value is 'dataset'. If there is no dataset +#' dimension, set NULL. #'@param comp_dim A character string indicating the name of dimension along which #' obs is taken into account only if it is complete. The default value #' is NULL. @@ -107,11 +108,14 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string or NULL.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + "Set it as NULL if there is no dataset dimension.") + } } ## comp_dim if (!is.null(comp_dim)) { @@ -151,8 +155,10 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) + if (!is.null(dat_dim)) { name_exp <- name_exp[-which(name_exp == dat_dim)] name_obs <- name_obs[-which(name_obs == dat_dim)] + } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", "all dimension expect 'dat_dim'.")) -- GitLab From 29f39928b9e03057faefa51446834c3cffe5ae2f Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 29 Jun 2022 17:29:21 +0200 Subject: [PATCH 008/140] Allow dat_dim = NULL in RMSSS() function --- R/RMSSS.R | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/R/RMSSS.R b/R/RMSSS.R index 5fa9659..3c3e006 100644 --- a/R/RMSSS.R +++ b/R/RMSSS.R @@ -23,7 +23,8 @@ #' 'exp', then the vector will automatically be 'time_dim' and 'dat_dim' will #' be 1. #'@param dat_dim A character string indicating the name of dataset (nobs/nexp) -#' dimension. The default value is 'dataset'. +#' dimension. The default value is 'dataset'. If there is no dataset +#' dimension, set NULL. #'@param time_dim A character string indicating the name of dimension along #' which the RMSSS are computed. The default value is 'sdate'. #'@param pval A logical value indicating whether to compute or not the p-value @@ -96,11 +97,14 @@ RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string or NULL.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + "Set it as NULL if there is no dataset dimension.") + } } ## pval if (!is.logical(pval) | length(pval) > 1) { @@ -116,8 +120,10 @@ RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) + if (!is.null(dat_dim)) { name_exp <- name_exp[-which(name_exp == dat_dim)] name_obs <- name_obs[-which(name_obs == dat_dim)] + } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", "all dimension expect 'dat_dim'.")) -- GitLab From 846c8d6cf55381d0c318e411d2fb22c12a61185b Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 29 Jun 2022 17:33:38 +0200 Subject: [PATCH 009/140] Allow dat_dim = NULL in UlitmateBrier() function --- R/UltimateBrier.R | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/R/UltimateBrier.R b/R/UltimateBrier.R index aeaddcd..9e7698f 100644 --- a/R/UltimateBrier.R +++ b/R/UltimateBrier.R @@ -102,11 +102,14 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti stop("Parameter 'exp' and 'obs' must have dimension names.") } ## dat_dim - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string or NULL.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + "Set it as NULL if there is no dataset dimension.") + } } ## memb_dim if (!is.character(memb_dim) | length(memb_dim) > 1) { @@ -133,8 +136,10 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti name_obs <- sort(names(dim(obs))) name_exp <- name_exp[-which(name_exp == memb_dim)] name_obs <- name_obs[-which(name_obs == memb_dim)] + if (!is.null(dat_dim)) { name_exp <- name_exp[-which(name_exp == dat_dim)] name_obs <- name_obs[-which(name_obs == dat_dim)] + } if (any(name_exp != name_obs)) { stop(paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", "of all the dimensions expect 'dat_dim' and 'memb_dim'.")) -- GitLab From 37a85620434cd3fa7f8ae97df952d5290a41a5bd Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 30 Jun 2022 13:19:17 +0200 Subject: [PATCH 010/140] Corrected typo of output text - expect for except - in some functions --- R/ACC.R | 2 +- R/BrierScore.R | 10 +++++----- R/Clim.R | 2 +- R/Consist_Trend.R | 2 +- R/Corr.R | 2 +- R/NAO.R | 2 +- R/RMS.R | 2 +- R/RMSSS.R | 2 +- R/RPS.R | 2 +- R/RPSS.R | 4 ++-- R/RatioRMS.R | 2 +- R/RatioSDRMS.R | 2 +- R/ResidualCorr.R | 2 +- R/UltimateBrier.R | 4 ++-- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/R/ACC.R b/R/ACC.R index 44c724c..ceb1e94 100644 --- a/R/ACC.R +++ b/R/ACC.R @@ -276,7 +276,7 @@ ACC <- function(exp, obs, dat_dim = 'dataset', lat_dim = 'lat', lon_dim = 'lon', } if (!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all the dimensions expect 'dat_dim' and 'memb_dim'.")) + "all the dimensions except 'dat_dim' and 'memb_dim'.")) } #----------------------------------------------------------------- diff --git a/R/BrierScore.R b/R/BrierScore.R index c3673ad..22f497d 100644 --- a/R/BrierScore.R +++ b/R/BrierScore.R @@ -57,13 +57,13 @@ #''bs_check_gres', 'bss_gres', 'rel_bias_corrected', 'gres_bias_corrected', #''unc_bias_corrected', and 'bss_bias_corrected' are (a) a number (b) an array #'with dimensions c(nexp, nobs, all the rest dimensions in 'exp' and 'obs' -#'expect 'time_dim' and 'memb_dim') (c) an array with dimensions of +#'except 'time_dim' and 'memb_dim') (c) an array with dimensions of #''exp' and 'obs' except 'time_dim' and 'memb_dim'\cr #'Items 'nk', 'fkbar', and 'okbar' are (a) a vector of length of bin number #'determined by 'threshold' (b) an array with dimensions c(nexp, nobs, -#'no. of bins, all the rest dimensions in 'exp' and 'obs' expect 'time_dim' and +#'no. of bins, all the rest dimensions in 'exp' and 'obs' except 'time_dim' and #''memb_dim') (c) an array with dimensions c(no. of bin, all the rest dimensions -#'in 'exp' and 'obs' expect 'time_dim' and 'memb_dim') +#'in 'exp' and 'obs' except 'time_dim' and 'memb_dim') #' #'@references #'Wilks (2006) Statistical Methods in the Atmospheric Sciences.\cr @@ -176,11 +176,11 @@ BrierScore <- function(exp, obs, thresholds = seq(0.1, 0.9, 0.1), time_dim = 'sd } if (any(!name_exp %in% name_obs) | any(!name_obs %in% name_exp)) { stop(paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.")) + "of all the dimensions except 'dat_dim' and 'memb_dim'.")) } if (!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.")) + "of all the dimensions except 'dat_dim' and 'memb_dim'.")) } ## ncores if (!is.null(ncores)) { diff --git a/R/Clim.R b/R/Clim.R index ce9ba2e..c144025 100644 --- a/R/Clim.R +++ b/R/Clim.R @@ -173,7 +173,7 @@ Clim <- function(exp, obs, time_dim = 'sdate', dat_dim = c('dataset', 'member'), } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have the same dimensions ", - "expect 'dat_dim'.")) + "except 'dat_dim'.")) } ############################### diff --git a/R/Consist_Trend.R b/R/Consist_Trend.R index 5ac797e..84b7699 100644 --- a/R/Consist_Trend.R +++ b/R/Consist_Trend.R @@ -131,7 +131,7 @@ Consist_Trend <- function(exp, obs, dat_dim = 'dataset', time_dim = 'sdate', int } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension expect 'dat_dim'.")) + "all dimension except 'dat_dim'.")) } ## interval if (!is.numeric(interval) | interval <= 0 | length(interval) > 1) { diff --git a/R/Corr.R b/R/Corr.R index 3cf640d..628511d 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -209,7 +209,7 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension expect 'dat_dim' and 'memb_dim'.")) + "all dimension except 'dat_dim' and 'memb_dim'.")) } if (dim(exp)[time_dim] < 3) { stop("The length of time_dim must be at least 3 to compute correlation.") diff --git a/R/NAO.R b/R/NAO.R index af4893a..6d48dba 100644 --- a/R/NAO.R +++ b/R/NAO.R @@ -194,7 +194,7 @@ NAO <- function(exp = NULL, obs = NULL, lat, lon, time_dim = 'sdate', } if (throw_error) { stop(paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'memb_dim'.")) + "of all the dimensions except 'memb_dim'.")) } } ## ftime_avg diff --git a/R/RMS.R b/R/RMS.R index 194a3f6..20da981 100644 --- a/R/RMS.R +++ b/R/RMS.R @@ -161,7 +161,7 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension expect 'dat_dim'.")) + "all dimension except 'dat_dim'.")) } if (dim(exp)[time_dim] < 2) { stop("The length of time_dim must be at least 2 to compute RMS.") diff --git a/R/RMSSS.R b/R/RMSSS.R index 3c3e006..9526517 100644 --- a/R/RMSSS.R +++ b/R/RMSSS.R @@ -126,7 +126,7 @@ RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension expect 'dat_dim'.")) + "all dimension except 'dat_dim'.")) } if (dim(exp)[time_dim] <= 2) { stop("The length of time_dim must be more than 2 to compute RMSSS.") diff --git a/R/RPS.R b/R/RPS.R index 8ded53e..200c59c 100644 --- a/R/RPS.R +++ b/R/RPS.R @@ -88,7 +88,7 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions expect 'memb_dim'.")) + "all dimensions except 'memb_dim'.")) } ## prob_thresholds if (!is.numeric(prob_thresholds) | !is.vector(prob_thresholds) | diff --git a/R/RPSS.R b/R/RPSS.R index 3b24777..cbd87bd 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -108,7 +108,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions expect 'memb_dim'.")) + "all dimensions except 'memb_dim'.")) } if (!is.null(ref)) { name_ref <- sort(names(dim(ref))) @@ -116,7 +116,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!identical(length(name_exp), length(name_ref)) | !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions expect 'memb_dim'.")) + "all dimensions except 'memb_dim'.")) } } ## prob_thresholds diff --git a/R/RatioRMS.R b/R/RatioRMS.R index f7e34b4..51f3984 100644 --- a/R/RatioRMS.R +++ b/R/RatioRMS.R @@ -21,7 +21,7 @@ #' computation. The default value is NULL. #' #'@return A list containing the numeric arrays with dimensions identical with -#' 'exp1', 'exp2', and 'obs', expect 'time_dim': +#' 'exp1', 'exp2', and 'obs', except 'time_dim': #'\item{$ratiorms}{ #' The ratio between the RMSE (i.e., RMSE1/RMSE2). #'} diff --git a/R/RatioSDRMS.R b/R/RatioSDRMS.R index 544ca6f..d527625 100644 --- a/R/RatioSDRMS.R +++ b/R/RatioSDRMS.R @@ -109,7 +109,7 @@ RatioSDRMS <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', name_obs <- name_obs[-which(name_obs == memb_dim)] if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all the dimensions expect 'dat_dim' and 'memb_dim'.")) + "all the dimensions except 'dat_dim' and 'memb_dim'.")) } ## pval if (!is.logical(pval) | length(pval) > 1) { diff --git a/R/ResidualCorr.R b/R/ResidualCorr.R index 7428d1b..8d9f040 100644 --- a/R/ResidualCorr.R +++ b/R/ResidualCorr.R @@ -125,7 +125,7 @@ ResidualCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', if (length(name_exp) != length(name_obs) | length(name_exp) != length(name_ref) | any(dim(exp)[name_exp] != dim(obs)[name_obs]) | any(dim(exp)[name_exp] != dim(ref)[name_ref])) { stop(paste0("Parameter 'exp', 'obs', and 'ref' must have same length of ", - "all dimensions expect 'memb_dim'.")) + "all dimensions except 'memb_dim'.")) } ## method if (!method %in% c("pearson", "kendall", "spearman")) { diff --git a/R/UltimateBrier.R b/R/UltimateBrier.R index 9e7698f..a2c594e 100644 --- a/R/UltimateBrier.R +++ b/R/UltimateBrier.R @@ -142,11 +142,11 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti } if (any(name_exp != name_obs)) { stop(paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.")) + "of all the dimensions except 'dat_dim' and 'memb_dim'.")) } if (!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.")) + "of all the dimensions except 'dat_dim' and 'memb_dim'.")) } ## quantile if (!is.logical(quantile) | length(quantile) > 1) { -- GitLab From cad3511398b10702b1d9867c4881631d1c0f7bc8 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 30 Jun 2022 16:59:15 +0200 Subject: [PATCH 011/140] Correct typo - except - also in tests --- tests/testthat/test-ACC.R | 2 +- tests/testthat/test-BrierScore.R | 2 +- tests/testthat/test-Clim.R | 2 +- tests/testthat/test-Consist_Trend.R | 2 +- tests/testthat/test-Corr.R | 2 +- tests/testthat/test-NAO.R | 4 ++-- tests/testthat/test-RMS.R | 2 +- tests/testthat/test-RMSSS.R | 2 +- tests/testthat/test-RPS.R | 2 +- tests/testthat/test-RPSS.R | 4 ++-- tests/testthat/test-RatioSDRMS.R | 2 +- tests/testthat/test-ResidualCorr.R | 2 +- tests/testthat/test-UltimateBrier.R | 4 ++-- 13 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/testthat/test-ACC.R b/tests/testthat/test-ACC.R index 5c36725..04e60ad 100644 --- a/tests/testthat/test-ACC.R +++ b/tests/testthat/test-ACC.R @@ -152,7 +152,7 @@ test_that("1. Input checks", { obs = array(1:4, dim = c(dataset = 2, member = 2, lat = 1, lon = 1)), lat = c(1, 2), lon = c(1), avg_dim = NULL), - "Parameter 'exp' and 'obs' must have same length of all the dimensions expect 'dat_dim' and 'memb_dim'." + "Parameter 'exp' and 'obs' must have same length of all the dimensions except 'dat_dim' and 'memb_dim'." ) }) diff --git a/tests/testthat/test-BrierScore.R b/tests/testthat/test-BrierScore.R index 5668a08..e2c34f9 100644 --- a/tests/testthat/test-BrierScore.R +++ b/tests/testthat/test-BrierScore.R @@ -51,7 +51,7 @@ test_that("1. Input checks", { expect_error( BrierScore(exp3, obs3), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.") + "of all the dimensions except 'dat_dim' and 'memb_dim'.") ) # thresholds expect_error( diff --git a/tests/testthat/test-Clim.R b/tests/testthat/test-Clim.R index 7751afb..f5e288e 100644 --- a/tests/testthat/test-Clim.R +++ b/tests/testthat/test-Clim.R @@ -134,7 +134,7 @@ test_that("1. Input checks", { expect_error( Clim(array(1:10, dim = c(dataset = 2, member = 5, sdate = 4, ftime = 3)), array(1:4, dim = c(dataset = 2, member = 2, sdate = 5, ftime = 3))), - "Parameter 'exp' and 'obs' must have the same dimensions expect 'dat_dim'." + "Parameter 'exp' and 'obs' must have the same dimensions except 'dat_dim'." ) }) diff --git a/tests/testthat/test-Consist_Trend.R b/tests/testthat/test-Consist_Trend.R index aa66f45..91dacf7 100644 --- a/tests/testthat/test-Consist_Trend.R +++ b/tests/testthat/test-Consist_Trend.R @@ -62,7 +62,7 @@ test_that("1. Input checks", { Consist_Trend(array(1:10, dim = c(dataset = 2, member = 5, sdate = 4, ftime = 3)), array(1:4, dim = c(dataset = 2, member = 2, sdate = 5, ftime = 3))), paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension expect 'dat_dim'.") + "all dimension except 'dat_dim'.") ) # interval expect_error( diff --git a/tests/testthat/test-Corr.R b/tests/testthat/test-Corr.R index 9d5d4a3..164e61e 100644 --- a/tests/testthat/test-Corr.R +++ b/tests/testthat/test-Corr.R @@ -132,7 +132,7 @@ test_that("1. Input checks", { expect_error( Corr(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), obs = array(1:4, dim = c(a = 1, sdate = 2, dataset = 2))), - "Parameter 'exp' and 'obs' must have same length of all dimension expect 'dat_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimension except 'dat_dim'." ) expect_error( Corr(exp = array(1:10, dim = c(sdate = 2, dataset = 5, a = 1)), diff --git a/tests/testthat/test-NAO.R b/tests/testthat/test-NAO.R index f3c6d21..2da0d40 100644 --- a/tests/testthat/test-NAO.R +++ b/tests/testthat/test-NAO.R @@ -95,12 +95,12 @@ test_that("1. Input checks", { expect_error( NAO(exp1, array(rnorm(10), dim = c(member = 1, sdate = 3, ftime = 4, lat = 2, lon = 2))), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'memb_dim'.") + "of all the dimensions except 'memb_dim'.") ) expect_error( NAO(exp1, array(rnorm(10), dim = c(dataset = 1, member = 1, sdate = 3, ftime = 4, lat = 1, lon = 2))), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'memb_dim'.") + "of all the dimensions except 'memb_dim'.") ) # ftime_avg expect_error( diff --git a/tests/testthat/test-RMS.R b/tests/testthat/test-RMS.R index 6e18bee..f1083dd 100644 --- a/tests/testthat/test-RMS.R +++ b/tests/testthat/test-RMS.R @@ -89,7 +89,7 @@ test_that("1. Input checks", { expect_error( RMS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), obs = array(1:4, dim = c(a = 1, sdate = 2, dataset = 2))), - "Parameter 'exp' and 'obs' must have same length of all dimension expect 'dat_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimension except 'dat_dim'." ) expect_error( RMS(exp = array(1:5, dim = c(sdate = 1, dataset = 5, a = 1)), diff --git a/tests/testthat/test-RMSSS.R b/tests/testthat/test-RMSSS.R index 7d2a16d..91ef930 100644 --- a/tests/testthat/test-RMSSS.R +++ b/tests/testthat/test-RMSSS.R @@ -77,7 +77,7 @@ test_that("1. Input checks", { expect_error( RMSSS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), obs = array(1:4, dim = c(a = 1, sdate = 2, dataset = 2))), - "Parameter 'exp' and 'obs' must have same length of all dimension expect 'dat_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimension except 'dat_dim'." ) expect_error( RMSSS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), diff --git a/tests/testthat/test-RPS.R b/tests/testthat/test-RPS.R index 04df1bb..2029fd9 100644 --- a/tests/testthat/test-RPS.R +++ b/tests/testthat/test-RPS.R @@ -56,7 +56,7 @@ test_that("1. Input checks", { # exp, ref, and obs (2) expect_error( RPS(exp1, array(1:9, dim = c(sdate = 9))), - "Parameter 'exp' and 'obs' must have same length of all dimensions expect 'memb_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim'." ) # prob_thresholds expect_error( diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index c63add0..b8623b6 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -63,11 +63,11 @@ test_that("1. Input checks", { # exp, ref, and obs (2) expect_error( RPSS(exp1, array(1:9, dim = c(sdate = 9))), - "Parameter 'exp' and 'obs' must have same length of all dimensions expect 'memb_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim'." ) expect_error( RPSS(exp1, obs1, ref2), - "Parameter 'exp' and 'obs' must have same length of all dimensions expect 'memb_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim'." ) # prob_thresholds expect_error( diff --git a/tests/testthat/test-RatioSDRMS.R b/tests/testthat/test-RatioSDRMS.R index 4b93faf..5dbc171 100644 --- a/tests/testthat/test-RatioSDRMS.R +++ b/tests/testthat/test-RatioSDRMS.R @@ -77,7 +77,7 @@ test_that("1. Input checks", { expect_error( RatioSDRMS(exp = exp3, obs = array(1:2, dim = c(member = 1, sdate = 2)), dat_dim = NULL), - "Parameter 'exp' and 'obs' must have same length of all the dimensions expect 'dat_dim' and 'memb_dim'." + "Parameter 'exp' and 'obs' must have same length of all the dimensions except 'dat_dim' and 'memb_dim'." ) }) diff --git a/tests/testthat/test-ResidualCorr.R b/tests/testthat/test-ResidualCorr.R index c15276c..be71b47 100644 --- a/tests/testthat/test-ResidualCorr.R +++ b/tests/testthat/test-ResidualCorr.R @@ -66,7 +66,7 @@ test_that("1. Input checks", { # exp, ref, and obs (2) expect_error( ResidualCorr(exp1, obs1, array(1:10, dim = c(sdate = 10, memb = 1)), memb_dim = 'memb'), - "Parameter 'exp', 'obs', and 'ref' must have same length of all dimensions expect 'memb_dim'." + "Parameter 'exp', 'obs', and 'ref' must have same length of all dimensions except 'memb_dim'." ) # method expect_error( diff --git a/tests/testthat/test-UltimateBrier.R b/tests/testthat/test-UltimateBrier.R index 654aad0..0eb0118 100644 --- a/tests/testthat/test-UltimateBrier.R +++ b/tests/testthat/test-UltimateBrier.R @@ -58,12 +58,12 @@ test_that("1. Input checks", { expect_error( UltimateBrier(exp1, array(1:10, dim = c(dataset = 1, sdate = 6, ftime = 2))), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.") + "of all the dimensions except 'dat_dim' and 'memb_dim'.") ) expect_error( UltimateBrier(exp1, array(1:10, dim = c(dataset = 1, sdate = 5, time = 2))), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.") + "of all the dimensions except 'dat_dim' and 'memb_dim'.") ) # quantile expect_error( -- GitLab From 175cea42f6928e33783278f34937fdf20dc9c52e Mon Sep 17 00:00:00 2001 From: aho Date: Fri, 1 Jul 2022 16:21:02 +0200 Subject: [PATCH 012/140] Change CI/CD R version to 4.1.2 --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3eb90d5..8ffc555 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ stages: build: stage: build script: - - module load R/3.6.1-foss-2015a-bare + - module load R/4.1.2-foss-2015a-bare - module load CDO/1.9.8-foss-2015a - R CMD build --resave-data . - R CMD check --as-cran --no-manual --run-donttest s2dv_*.tar.gz -- GitLab From 2f9f2206e154714918a05fcef69a2b672f64094f Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Fri, 1 Jul 2022 16:43:25 +0200 Subject: [PATCH 013/140] Upload files in roxygen format --- man/BrierScore.Rd | 6 +++--- man/Consist_Trend.Rd | 6 ++++-- man/Corr.Rd | 6 ++++-- man/RMS.Rd | 3 ++- man/RMSSS.Rd | 3 ++- man/RatioRMS.Rd | 2 +- man/s2dv-package.Rd | 10 +++++++++- man/sampleDepthData.Rd | 6 ++---- man/sampleMap.Rd | 6 ++---- man/sampleTimeSeries.Rd | 6 ++---- 10 files changed, 31 insertions(+), 23 deletions(-) diff --git a/man/BrierScore.Rd b/man/BrierScore.Rd index 9271a2a..76fb27e 100644 --- a/man/BrierScore.Rd +++ b/man/BrierScore.Rd @@ -73,13 +73,13 @@ Items 'rel', 'res', 'unc', 'bs', 'bs_check_res', 'bss_res', 'gres', 'bs_check_gres', 'bss_gres', 'rel_bias_corrected', 'gres_bias_corrected', 'unc_bias_corrected', and 'bss_bias_corrected' are (a) a number (b) an array with dimensions c(nexp, nobs, all the rest dimensions in 'exp' and 'obs' -expect 'time_dim' and 'memb_dim') (c) an array with dimensions of +except 'time_dim' and 'memb_dim') (c) an array with dimensions of 'exp' and 'obs' except 'time_dim' and 'memb_dim'\cr Items 'nk', 'fkbar', and 'okbar' are (a) a vector of length of bin number determined by 'threshold' (b) an array with dimensions c(nexp, nobs, -no. of bins, all the rest dimensions in 'exp' and 'obs' expect 'time_dim' and +no. of bins, all the rest dimensions in 'exp' and 'obs' except 'time_dim' and 'memb_dim') (c) an array with dimensions c(no. of bin, all the rest dimensions -in 'exp' and 'obs' expect 'time_dim' and 'memb_dim') +in 'exp' and 'obs' except 'time_dim' and 'memb_dim') } \description{ Compute the Brier score (BS) and the components of its standard decompostion diff --git a/man/Consist_Trend.Rd b/man/Consist_Trend.Rd index 2ac7d42..b93dad5 100644 --- a/man/Consist_Trend.Rd +++ b/man/Consist_Trend.Rd @@ -23,7 +23,8 @@ parameter 'exp' except along 'dat_dim'.} \item{dat_dim}{A character string indicating the name of the dataset dimensions. If data at some point of 'time_dim' are not complete along 'dat_dim' in both 'exp' and 'obs', this point in all 'dat_dim' will be -discarded. The default value is 'dataset'.} +discarded. The default value is 'dataset'. If there is no dataset +dimension, set NULL.} \item{time_dim}{A character string indicating the name of dimension along which the trend is computed. The default value is 'sdate'.} @@ -41,7 +42,8 @@ A list containing: with dimensions c(stats = 2, nexp + nobs, the rest dimensions of 'exp' and 'obs' except time_dim), where 'nexp' is the length of 'dat_dim' in 'exp' and 'nobs' is the length of 'dat_dim' in 'obs. The 'stats' dimension - contains the intercept and the slope. + contains the intercept and the slope. If dat_dim is NULL, nexp and nobs are + omitted. } \item{$conf.lower}{ A numeric array of the lower limit of 95\% confidence interval with diff --git a/man/Corr.Rd b/man/Corr.Rd index 077791f..54f101b 100644 --- a/man/Corr.Rd +++ b/man/Corr.Rd @@ -31,7 +31,8 @@ parameter 'exp' except along 'dat_dim' and 'memb_dim'.} which the correlations are computed. The default value is 'sdate'.} \item{dat_dim}{A character string indicating the name of dataset (nobs/nexp) -dimension. The default value is 'dataset'.} +dimension. The default value is 'dataset'. If there is no dataset +dimension, set NULL.} \item{comp_dim}{A character string indicating the name of dimension along which obs is taken into account only if it is complete. The default value @@ -68,7 +69,8 @@ A list containing the numeric arrays with dimension:\cr c(nexp, nobs, exp_memb, obs_memb, all other dimensions of exp except time_dim and memb_dim).\cr nexp is the number of experiment (i.e., 'dat_dim' in exp), and nobs is the -number of observation (i.e., 'dat_dim' in obs). exp_memb is the number of +number of observation (i.e., 'dat_dim' in obs). If + dat_dim is NULL, nexp and nobs are omitted. exp_memb is the number of member in experiment (i.e., 'memb_dim' in exp) and obs_memb is the number of member in observation (i.e., 'memb_dim' in obs).\cr\cr \item{$corr}{ diff --git a/man/RMS.Rd b/man/RMS.Rd index 4391df4..a84f965 100644 --- a/man/RMS.Rd +++ b/man/RMS.Rd @@ -31,7 +31,8 @@ length as 'exp', then the vector will automatically be 'time_dim' and which the correlations are computed. The default value is 'sdate'.} \item{dat_dim}{A character string indicating the name of member (nobs/nexp) -dimension. The default value is 'dataset'.} +dimension. The default value is 'dataset'. If there is no dataset +dimension, set NULL.} \item{comp_dim}{A character string indicating the name of dimension along which obs is taken into account only if it is complete. The default value diff --git a/man/RMSSS.Rd b/man/RMSSS.Rd index 9ebcf65..2c23ee4 100644 --- a/man/RMSSS.Rd +++ b/man/RMSSS.Rd @@ -30,7 +30,8 @@ be 1.} which the RMSSS are computed. The default value is 'sdate'.} \item{dat_dim}{A character string indicating the name of dataset (nobs/nexp) -dimension. The default value is 'dataset'.} +dimension. The default value is 'dataset'. If there is no dataset +dimension, set NULL.} \item{pval}{A logical value indicating whether to compute or not the p-value of the test Ho: RMSSS = 0. If pval = TRUE, the insignificant RMSSS will diff --git a/man/RatioRMS.Rd b/man/RatioRMS.Rd index 194c6b9..3330eb5 100644 --- a/man/RatioRMS.Rd +++ b/man/RatioRMS.Rd @@ -30,7 +30,7 @@ computation. The default value is NULL.} } \value{ A list containing the numeric arrays with dimensions identical with - 'exp1', 'exp2', and 'obs', expect 'time_dim': + 'exp1', 'exp2', and 'obs', except 'time_dim': \item{$ratiorms}{ The ratio between the RMSE (i.e., RMSE1/RMSE2). } diff --git a/man/s2dv-package.Rd b/man/s2dv-package.Rd index 3c98a95..ffb6783 100644 --- a/man/s2dv-package.Rd +++ b/man/s2dv-package.Rd @@ -6,7 +6,15 @@ \alias{s2dv-package} \title{s2dv: A Set of Common Tools for Seasonal to Decadal Verification} \description{ -The advanced version of package 's2dverification'. It is intended for 'seasonal to decadal' (s2d) climate forecast verification, but it can also be used in other kinds of forecasts or general climate analysis. This package is specially designed for the comparison between the experimental and observational datasets. The functionality of the included functions covers from data retrieval, data post-processing, skill scores against observation, to visualization. Compared to 's2dverification', 's2dv' is more compatible with the package 'startR', able to use multiple cores for computation and handle multi-dimensional arrays with a higher flexibility. +The advanced version of package 's2dverification'. It is + intended for 'seasonal to decadal' (s2d) climate forecast verification, but + it can also be used in other kinds of forecasts or general climate analysis. + This package is specially designed for the comparison between the experimental + and observational datasets. The functionality of the included functions covers + from data retrieval, data post-processing, skill scores against observation, + to visualization. Compared to 's2dverification', 's2dv' is more compatible + with the package 'startR', able to use multiple cores for computation and + handle multi-dimensional arrays with a higher flexibility. } \references{ \url{https://earth.bsc.es/gitlab/es/s2dv/} diff --git a/man/sampleDepthData.Rd b/man/sampleDepthData.Rd index 47e2f1b..77e4a7a 100644 --- a/man/sampleDepthData.Rd +++ b/man/sampleDepthData.Rd @@ -5,8 +5,7 @@ \alias{sampleDepthData} \title{Sample of Experimental Data for Forecast Verification In Function Of Latitudes And Depths} -\format{ -The data set provides with a variable named 'sampleDepthData'.\cr\cr +\format{The data set provides with a variable named 'sampleDepthData'.\cr\cr sampleDepthData$exp is an array that contains the experimental data and the dimension meanings and values are:\cr @@ -19,8 +18,7 @@ but in this sample is not defined (NULL).\cr\cr sampleDepthData$depths is an array with the 7 longitudes covered by the data.\cr\cr -sampleDepthData$lat is an array with the 21 latitudes covered by the data.\cr\cr -} +sampleDepthData$lat is an array with the 21 latitudes covered by the data.\cr\cr} \usage{ data(sampleDepthData) } diff --git a/man/sampleMap.Rd b/man/sampleMap.Rd index e4ec5a5..eaf8aa5 100644 --- a/man/sampleMap.Rd +++ b/man/sampleMap.Rd @@ -4,8 +4,7 @@ \name{sampleMap} \alias{sampleMap} \title{Sample Of Observational And Experimental Data For Forecast Verification In Function Of Longitudes And Latitudes} -\format{ -The data set provides with a variable named 'sampleMap'.\cr\cr +\format{The data set provides with a variable named 'sampleMap'.\cr\cr sampleMap$mod is an array that contains the experimental data and the dimension meanings and values are:\cr c(# of experimental datasets, # of members, # of starting dates, # of lead-times, # of latitudes, # of longitudes)\cr @@ -17,8 +16,7 @@ sampleMap$obs is an array that contains the observational data and the dimension sampleMap$lat is an array with the 2 latitudes covered by the data (see examples on Load() for details on why such low resolution).\cr\cr - sampleMap$lon is an array with the 3 longitudes covered by the data (see examples on Load() for details on why such low resolution). -} + sampleMap$lon is an array with the 3 longitudes covered by the data (see examples on Load() for details on why such low resolution).} \usage{ data(sampleMap) } diff --git a/man/sampleTimeSeries.Rd b/man/sampleTimeSeries.Rd index 7f058e2..05a8e79 100644 --- a/man/sampleTimeSeries.Rd +++ b/man/sampleTimeSeries.Rd @@ -4,8 +4,7 @@ \name{sampleTimeSeries} \alias{sampleTimeSeries} \title{Sample Of Observational And Experimental Data For Forecast Verification As Area Averages} -\format{ -The data set provides with a variable named 'sampleTimeSeries'.\cr\cr +\format{The data set provides with a variable named 'sampleTimeSeries'.\cr\cr sampleTimeSeries$mod is an array that contains the experimental data and the dimension meanings and values are:\cr c(# of experimental datasets, # of members, # of starting dates, # of lead-times)\cr @@ -17,8 +16,7 @@ sampleTimeSeries$obs is an array that contains the observational data and the di sampleTimeSeries$lat is an array with the 2 latitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution).\cr\cr -sampleTimeSeries$lon is an array with the 3 longitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution). -} +sampleTimeSeries$lon is an array with the 3 longitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution).} \usage{ data(sampleTimeSeries) } -- GitLab From 5e69a01ac18b067ca4d59feaae2592dfb93de5bb Mon Sep 17 00:00:00 2001 From: aho Date: Fri, 1 Jul 2022 17:15:30 +0200 Subject: [PATCH 014/140] Add 'weights' back to remain compatibility. Update document. --- R/RPSS.R | 12 +++++++++++- man/RPSS.Rd | 18 +++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/R/RPSS.R b/R/RPSS.R index 50d4c3f..f97080c 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -34,6 +34,8 @@ #'@param Fair A logical indicating whether to compute the FairRPSS (the #' potential RPSS that the forecast would have with an infinite ensemble size). #' The default value is FALSE. +#'@param weights Deprecated and will be removed in the next release. Please use +#' 'weights_exp' and 'weights_ref' instead. #'@param weights_exp A named two-dimensional numerical array of the forecast #' ensemble weights for each member and time. The dimension names should #' include 'memb_dim' and 'time_dim'. The default value is NULL. The ensemble @@ -68,7 +70,7 @@ #'@import multiApply #'@export RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', - prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, + prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights = NULL, weights_exp = NULL, weights_ref = NULL, ncores = NULL) { # Check inputs @@ -142,6 +144,14 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!is.logical(Fair) | length(Fair) > 1) { stop("Parameter 'Fair' must be either TRUE or FALSE.") } + ## weights + if (!is.null(weights)) { + warning(paste0("Parameter 'weights' is deprecated and will be removed in the next release. ", + "Use 'weights_exp' and 'weights_ref' instead. The value will be assigned ", + "to these two parameters now if they are NULL.")) + if (is.null(weights_exp)) weights_exp <- weights + if (is.null(weights_ref)) weights_ref <- weights + } ## weights_exp if (!is.null(weights_exp)) { if (!is.array(weights_exp) | !is.numeric(weights_exp)) diff --git a/man/RPSS.Rd b/man/RPSS.Rd index 5b8bd7e..893169d 100644 --- a/man/RPSS.Rd +++ b/man/RPSS.Rd @@ -14,6 +14,8 @@ RPSS( indices_for_clim = NULL, Fair = FALSE, weights = NULL, + weights_exp = NULL, + weights_ref = NULL, ncores = NULL ) } @@ -48,11 +50,17 @@ the whole period is used. The default value is NULL.} potential RPSS that the forecast would have with an infinite ensemble size). The default value is FALSE.} -\item{weights}{A named two-dimensional numerical array of the weights for each -member and time. The dimension names should include 'memb_dim' and -'time_dim'. The default value is NULL. The ensemble should have at least 70 -members or span at least 10 time steps and have more than 45 members if -consistency between the weighted and unweighted methodologies is desired.} +\item{weights}{Deprecated and will be removed in the next release. Please use +'weights_exp' and 'weights_ref' instead.} + +\item{weights_exp}{A named two-dimensional numerical array of the forecast +ensemble weights for each member and time. The dimension names should +include 'memb_dim' and 'time_dim'. The default value is NULL. The ensemble +should have at least 70 members or span at least 10 time steps and have +more than 45 members if consistency between the weighted and unweighted +methodologies is desired.} + +\item{weights_ref}{Same as 'weights_exp' but for the reference ensemble.} \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} -- GitLab From 6f555454ee541b9b383dec85a88263146b84876c Mon Sep 17 00:00:00 2001 From: aho Date: Fri, 1 Jul 2022 17:17:18 +0200 Subject: [PATCH 015/140] Correct typo and update unit test --- tests/testthat/test-RPSS.R | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index 2b80be3..6473b24 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -88,18 +88,18 @@ test_that("1. Input checks", { RPSS(exp1, obs1, Fair = 1), "Parameter 'Fair' must be either TRUE or FALSE." ) - # weights + # weights_exp and weights_ref expect_error( - RPS(exp1, obs1, weights = c(0, 1)), - 'Parameter "weights" must be a two-dimensional numeric array.' + RPSS(exp1, obs1, weights_exp = c(0, 1)), + 'Parameter "weights_exp" must be a two-dimensional numeric array.' ) expect_error( - RPS(exp1, obs1, weights = array(1, dim = c(member = 3, time = 10))), - "Parameter 'weights' must have two dimensions with the names of memb_dim and time_dim." + RPSS(exp1, obs1, weights_exp = array(1, dim = c(member = 3, time = 10))), + "Parameter 'weights_exp' must have two dimensions with the names of memb_dim and time_dim." ) expect_error( - RPS(exp1, obs1, weights = array(1, dim = c(member = 3, sdate = 1))), - "Parameter 'weights' must have the same dimension lengths as memb_dim and time_dim in 'exp'." + RPSS(exp1, obs1, weights_ref = array(1, dim = c(member = 3, sdate = 1))), + "Parameter 'weights_ref' must have the same dimension lengths as memb_dim and time_dim in 'exp'." ) # ncores expect_error( -- GitLab From ea68861e43836af438f429a6c6ce5b6db367b66d Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Tue, 5 Jul 2022 12:50:31 +0200 Subject: [PATCH 016/140] Corrected development of dat_dim = NULL --- R/Corr.R | 160 +++++++++++++++++++++++++++---------- tests/testthat/test-Corr.R | 59 +++++++++++++- 2 files changed, 177 insertions(+), 42 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index 628511d..2e3ea17 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -242,6 +242,16 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } if (is.null(memb_dim)) { + if (is.null(dat_dim)) { + res <- Apply(list(exp, obs), + target_dims = list(c(time_dim), + c(time_dim)), + fun = .Corr, + dat_dim = dat_dim, memb_dim = memb_dim, + time_dim = time_dim, method = method, + pval = pval, conf = conf, conf.lev = conf.lev, + ncores = ncores) + } else { # Define output_dims if (conf & pval) { output_dims <- list(corr = c('nexp', 'nobs'), @@ -264,11 +274,12 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', c(time_dim, dat_dim)), output_dims = output_dims, fun = .Corr, + dat_dim = dat_dim, memb_dim = memb_dim, time_dim = time_dim, method = method, pval = pval, conf = conf, conf.lev = conf.lev, ncores = ncores) - } else { + }} else { if (!memb) { #ensemble mean name_exp <- names(dim(exp)) margin_dims_ind <- c(1:length(name_exp))[-which(name_exp == memb_dim)] @@ -297,11 +308,40 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', c(time_dim, dat_dim)), output_dims = output_dims, fun = .Corr, + dat_dim = dat_dim, memb_dim = NULL, time_dim = time_dim, method = method, pval = pval, conf = conf, conf.lev = conf.lev, ncores_input = ncores, ncores = ncores) } else { # correlation for each member + if (is.null(dat_dim)) { + # Define output_dims + if (conf & pval) { + output_dims <- list(corr = c('exp_memb', 'obs_memb'), + p.val = c('exp_memb', 'obs_memb'), + conf.lower = c('exp_memb', 'obs_memb'), + conf.upper = c('exp_memb', 'obs_memb')) + } else if (conf & !pval) { + output_dims <- list(corr = c('exp_memb', 'obs_memb'), + conf.lower = c('exp_memb', 'obs_memb'), + conf.upper = c('exp_memb', 'obs_memb')) + } else if (!conf & pval) { + output_dims <- list(corr = c('exp_memb', 'obs_memb'), + p.val = c('exp_memb', 'obs_memb')) + } else { + output_dims <- list(corr = c('exp_memb', 'obs_memb')) + } + + res <- Apply(list(exp, obs), + target_dims = list(c(time_dim, memb_dim), + c(time_dim, memb_dim)), + output_dims = output_dims, + fun = .Corr, + dat_dim = dat_dim, memb_dim = memb_dim, + time_dim = time_dim, method = method, + pval = pval, conf = conf, conf.lev = conf.lev, ncores_input = ncores, + ncores = ncores) + } else { # Define output_dims if (conf & pval) { @@ -325,28 +365,33 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', c(time_dim, dat_dim, memb_dim)), output_dims = output_dims, fun = .Corr, + dat_dim = dat_dim, memb_dim = memb_dim, time_dim = time_dim, method = method, pval = pval, conf = conf, conf.lev = conf.lev, ncores_input = ncores, ncores = ncores) } + } } return(res) } -.Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', method = 'pearson', +.Corr <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', time_dim = 'sdate', method = 'pearson', conf = TRUE, pval = TRUE, conf.lev = 0.95, ncores_input = NULL) { - if (length(dim(exp)) == 2) { + if (is.null(memb_dim)) { + if (is.null(dat_dim)) { + # exp: [sdate] + # obs: [sdate] + nexp <- 1 + nobs <- 1 + CORR <- cor(exp, obs,use = "pairwise.complete.obs",method = method) + } else { # exp: [sdate, dat_exp] # obs: [sdate, dat_obs] - if (is.null(dat_dim)) { - nexp <- 1 - nobs <- 1 - } else { - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) - } + nexp <- as.numeric(dim(exp)[dat_dim]) + nobs <- as.numeric(dim(obs)[dat_dim]) + # NOTE: Use sapply to replace the for loop CORR <- sapply(1:nobs, function(i) { sapply(1:nexp, function (x) { @@ -361,40 +406,58 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', }) if (is.null(dim(CORR))) { CORR <- array(CORR, dim = c(1, 1)) - } + }} } else { # member - - # exp: [sdate, dat_exp, memb_exp] - # obs: [sdate, dat_obs, memb_obs] if (is.null(dat_dim)) { - nexp <- 1 - nobs <- 1 - } else { - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) - exp_memb <- as.numeric(dim(exp)[3]) - obs_memb <- as.numeric(dim(obs)[3]) - } - CORR <- array(dim = c(nexp = nexp, nobs = nobs, exp_memb = exp_memb, obs_memb = obs_memb)) - - for (j in 1:obs_memb) { - for (y in 1:exp_memb) { - CORR[, , y, j] <- sapply(1:nobs, function(i) { - sapply(1:nexp, function (x) { - if (any(!is.na(exp[, x, y])) && sum(!is.na(obs[, i, j])) > 2) { - cor(exp[, x, y], obs[, i, j], - use = "pairwise.complete.obs", - method = method) - } else { - NA #CORR[, i] <- NA - } - }) - }) + # exp: [sdate, memb_exp] + # obs: [sdate, memb_obs] + nexp <- 1 + nobs <- 1 + exp_memb <- as.numeric(dim(exp)[memb_dim]) # memb_dim + obs_memb <- as.numeric(dim(obs)[memb_dim]) + CORR <- array(dim = c(nexp = nexp, nobs = nobs, exp_memb = exp_memb, obs_memb = obs_memb)) + + for (j in 1:obs_memb) { + for (y in 1:exp_memb) { + + if (any(!is.na(exp[,y])) && sum(!is.na(obs[, j])) > 2) { + CORR[, , y, j] <- cor(exp[, y], obs[, j], + use = "pairwise.complete.obs", + method = method) + } else { + NA #CORR[, i] <- NA + } + + } + } + } else { + # exp: [sdate, dat_exp, memb_exp] + # obs: [sdate, dat_obs, memb_obs] + nexp <- as.numeric(dim(exp)[2]) + nobs <- as.numeric(dim(obs)[2]) + exp_memb <- as.numeric(dim(exp)[3]) + obs_memb <- as.numeric(dim(obs)[3]) + + CORR <- array(dim = c(nexp = nexp, nobs = nobs, exp_memb = exp_memb, obs_memb = obs_memb)) + + for (j in 1:obs_memb) { + for (y in 1:exp_memb) { + CORR[, , y, j] <- sapply(1:nobs, function(i) { + sapply(1:nexp, function (x) { + if (any(!is.na(exp[, x, y])) && sum(!is.na(obs[, i, j])) > 2) { + cor(exp[, x, y], obs[, i, j], + use = "pairwise.complete.obs", + method = method) + } else { + NA #CORR[, i] <- NA + } + }) + }) + } + } } - } - } @@ -420,7 +483,7 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', eno <- Eno(obs, time_dim, ncores = ncores_input) } - if (length(dim(exp)) == 2) { + if (is.null(memb_dim)) { eno_expand <- array(dim = c(nexp = nexp, nobs = nobs)) for (i in 1:nexp) { eno_expand[i, ] <- eno @@ -450,6 +513,21 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', confhigh <- tanh(atanh(CORR) + qnorm(conf.upper) / sqrt(eno_expand - 3)) } +################################### NEW +# Remove nexp and nobs if dat_dim = NULL + if (is.null(dat_dim) & !is.null(memb_dim)) { + dim(CORR) <- dim(CORR)[3:length(dim(CORR))] + if (pval) { + dim(p.val) <- dim(p.val)[3:length(dim(p.val))] + } + if (conf) { + dim(conflow) <- dim(conflow)[3:length(dim(conflow))] + dim(confhigh) <- dim(confhigh)[3:length(dim(confhigh))] + } + } + +################################### + if (pval & conf) { res <- list(corr = CORR, p.val = p.val, conf.lower = conflow, conf.upper = confhigh) @@ -462,6 +540,6 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', res <- list(corr = CORR) } - return(res) + return(res) } diff --git a/tests/testthat/test-Corr.R b/tests/testthat/test-Corr.R index 164e61e..65407d1 100644 --- a/tests/testthat/test-Corr.R +++ b/tests/testthat/test-Corr.R @@ -40,6 +40,22 @@ context("s2dv::Corr tests") obs4 <- array(rnorm(10), dim = c(member = 1, dataset = 1, sdate = 5, lat = 2)) + # dat5: exp and obs have memb_dim and dataset = NULL + set.seed(1) + exp5 <- array(rnorm(90), dim = c(member = 3, sdate = 5, + lat = 2, lon = 3)) + + set.seed(2) + obs5 <- array(rnorm(30), dim = c(member = 1, sdate = 5, + lat = 2, lon = 3)) + + # dat6: exp and obs have memb_dim = NULL and dataset = NULL + set.seed(1) + exp6 <- array(rnorm(90), dim = c(sdate = 5, lat = 2, lon = 3)) + + set.seed(2) + obs6 <- array(rnorm(30), dim = c(sdate = 5, lat = 2, lon = 3)) + ############################################## test_that("1. Input checks", { @@ -132,7 +148,7 @@ test_that("1. Input checks", { expect_error( Corr(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), obs = array(1:4, dim = c(a = 1, sdate = 2, dataset = 2))), - "Parameter 'exp' and 'obs' must have same length of all dimension except 'dat_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimension except 'dat_dim' and 'memb_dim'." ) expect_error( Corr(exp = array(1:10, dim = c(sdate = 2, dataset = 5, a = 1)), @@ -392,4 +408,45 @@ test_that("5. Output checks: dat4", { ) }) + +############################################## +test_that("6. Output checks: dat5", { + expect_equal( + dim(Corr(exp5, obs5, dat_dim = NULL, memb_dim = 'member')$corr), + c(exp_memb = 3, obs_memb = 1, lat = 2, lon = 3) + ) + expect_equal( + names(Corr(exp5, obs5, dat_dim = NULL, memb_dim = 'member')), + c("corr", "p.val", "conf.lower", "conf.upper") + ) + expect_equal( + names(Corr(exp5, obs5, dat_dim = NULL, memb_dim = 'member', pval = FALSE, conf = FALSE)), + c("corr") + ) + expect_equal( + mean(Corr(exp5, obs5, dat_dim = NULL, memb_dim = 'member', pval = FALSE, conf = FALSE)$corr), + 0.1880204, + tolerance = 0.0001 + ) +}) ############################################## +test_that("7. Output checks: dat6", { + expect_equal( + dim(Corr(exp6, obs6, dat_dim = NULL, memb_dim = NULL)$corr), + c(lat = 2, lon = 3) + ) + expect_equal( + names(Corr(exp6, obs6, dat_dim = NULL, memb_dim = NULL)), + c("corr", "p.val", "conf.lower", "conf.upper") + ) + expect_equal( + names(Corr(exp6, obs6, dat_dim = NULL, memb_dim = NULL, pval = FALSE, conf = FALSE)), + c("corr") + ) + expect_equal( + mean(Corr(exp6, obs6, dat_dim = NULL, memb_dim = NULL, pval = FALSE, conf = FALSE)$corr), + 0.1084488, + tolerance = 0.0001 + ) +}) +############################################## \ No newline at end of file -- GitLab From 2a693808e7e5309abf888d3e3a450eb4aa67b105 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Tue, 5 Jul 2022 16:25:46 +0200 Subject: [PATCH 017/140] Added test for new development --- tests/testthat/test-Corr.R | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/testthat/test-Corr.R b/tests/testthat/test-Corr.R index 65407d1..7d477ad 100644 --- a/tests/testthat/test-Corr.R +++ b/tests/testthat/test-Corr.R @@ -56,6 +56,13 @@ context("s2dv::Corr tests") set.seed(2) obs6 <- array(rnorm(30), dim = c(sdate = 5, lat = 2, lon = 3)) + # dat7: exp and obs have memb_dim = NULL and dataset = 1 + set.seed(1) + exp7 <- array(rnorm(90), dim = c(dataset = 1, sdate = 5, lat = 2, lon = 3)) + + set.seed(2) + obs7 <- array(rnorm(30), dim = c(dataset = 1, sdate = 5, lat = 2, lon = 3)) + ############################################## test_that("1. Input checks", { @@ -449,4 +456,12 @@ test_that("7. Output checks: dat6", { tolerance = 0.0001 ) }) +############################################## +test_that("8. Output checks: dat6 and dat7", { + expect_equal( + mean(Corr(exp6, obs6, dat_dim = NULL, memb_dim = NULL, pval = FALSE, conf = FALSE)$corr), + mean(Corr(exp7, obs7, memb_dim = NULL, pval = FALSE, conf = FALSE)$corr), + tolerance = 0.0001 + ) +}) ############################################## \ No newline at end of file -- GitLab From f4e53ae2f7cd5cb3ce5dccb00e41bc7af70e43c6 Mon Sep 17 00:00:00 2001 From: aho Date: Tue, 5 Jul 2022 18:24:42 +0200 Subject: [PATCH 018/140] Refine documentation --- R/Corr.R | 32 ++++++++++++++++---------------- man/Corr.Rd | 32 ++++++++++++++++---------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index 2e3ea17..d9e8144 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -2,22 +2,22 @@ #' #'Calculate the correlation coefficient (Pearson, Kendall or Spearman) for #'an array of forecast and an array of observation. The correlations are -#'computed along time_dim, the startdate dimension. If comp_dim is given, -#'the correlations are computed only if obs along the comp_dim dimension are -#'complete between limits[1] and limits[2], i.e., there is no NA between -#'limits[1] and limits[2]. This option can be activated if the user wants to -#'account only for the forecasts which the corresponding observations are -#'available at all leadtimes.\cr +#'computed along 'time_dim' that usually refers to the start date dimension. If +#''comp_dim' is given, the correlations are computed only if obs along comp_dim +#'dimension are complete between limits[1] and limits[2], i.e., there is no NA +#'between limits[1] and limits[2]. This option can be activated if the user +#'wants to account only for the forecasts which the corresponding observations +#'are available at all leadtimes.\cr #'The confidence interval is computed by the Fisher transformation and the #'significance level relies on an one-sided student-T distribution.\cr -#'If the dataset has more than one member, ensemble mean is necessary necessary -#'before using this function since it only allows one dimension 'dat_dim' to -#'have inconsistent length between 'exp' and 'obs'. If all the dimensions of -#''exp' and 'obs' are identical, you can simply use apply() and cor() to +#'The function can calculate ensemble mean before correlation by 'memb_dim' +#'specified and 'memb = F'. If ensemble mean is not calculated, correlation will +#'be calculated for each member. +#'If there is only one dataset for exp and obs, you can simply use cor() to #'compute the correlation. #' -#'@param exp A named numeric array of experimental data, with at least two -#' dimensions 'time_dim' and 'dat_dim'. +#'@param exp A named numeric array of experimental data, with at least dimension +#' 'time_dim'. #'@param obs A named numeric array of observational data, same dimensions as #' parameter 'exp' except along 'dat_dim' and 'memb_dim'. #'@param time_dim A character string indicating the name of dimension along @@ -52,10 +52,10 @@ #' c(nexp, nobs, exp_memb, obs_memb, all other dimensions of exp except #' time_dim and memb_dim).\cr #'nexp is the number of experiment (i.e., 'dat_dim' in exp), and nobs is the -#'number of observation (i.e., 'dat_dim' in obs). If -#' dat_dim is NULL, nexp and nobs are omitted. exp_memb is the number of -#'member in experiment (i.e., 'memb_dim' in exp) and obs_memb is the number of -#'member in observation (i.e., 'memb_dim' in obs).\cr\cr +#'number of observation (i.e., 'dat_dim' in obs). If dat_dim is NULL, nexp and +#'nobs are omitted. exp_memb is the number of member in experiment (i.e., +#''memb_dim' in exp) and obs_memb is the number of member in observation (i.e., +#''memb_dim' in obs). If memb = F, exp_memb and obs_memb are omitted.\cr\cr #'\item{$corr}{ #' The correlation coefficient. #'} diff --git a/man/Corr.Rd b/man/Corr.Rd index 54f101b..4cbd098 100644 --- a/man/Corr.Rd +++ b/man/Corr.Rd @@ -21,8 +21,8 @@ Corr( ) } \arguments{ -\item{exp}{A named numeric array of experimental data, with at least two -dimensions 'time_dim' and 'dat_dim'.} +\item{exp}{A named numeric array of experimental data, with at least dimension +'time_dim'.} \item{obs}{A named numeric array of observational data, same dimensions as parameter 'exp' except along 'dat_dim' and 'memb_dim'.} @@ -69,10 +69,10 @@ A list containing the numeric arrays with dimension:\cr c(nexp, nobs, exp_memb, obs_memb, all other dimensions of exp except time_dim and memb_dim).\cr nexp is the number of experiment (i.e., 'dat_dim' in exp), and nobs is the -number of observation (i.e., 'dat_dim' in obs). If - dat_dim is NULL, nexp and nobs are omitted. exp_memb is the number of -member in experiment (i.e., 'memb_dim' in exp) and obs_memb is the number of -member in observation (i.e., 'memb_dim' in obs).\cr\cr +number of observation (i.e., 'dat_dim' in obs). If dat_dim is NULL, nexp and +nobs are omitted. exp_memb is the number of member in experiment (i.e., +'memb_dim' in exp) and obs_memb is the number of member in observation (i.e., +'memb_dim' in obs). If memb = F, exp_memb and obs_memb are omitted.\cr\cr \item{$corr}{ The correlation coefficient. } @@ -89,18 +89,18 @@ member in observation (i.e., 'memb_dim' in obs).\cr\cr \description{ Calculate the correlation coefficient (Pearson, Kendall or Spearman) for an array of forecast and an array of observation. The correlations are -computed along time_dim, the startdate dimension. If comp_dim is given, -the correlations are computed only if obs along the comp_dim dimension are -complete between limits[1] and limits[2], i.e., there is no NA between -limits[1] and limits[2]. This option can be activated if the user wants to -account only for the forecasts which the corresponding observations are -available at all leadtimes.\cr +computed along 'time_dim' that usually refers to the start date dimension. If +'comp_dim' is given, the correlations are computed only if obs along comp_dim +dimension are complete between limits[1] and limits[2], i.e., there is no NA +between limits[1] and limits[2]. This option can be activated if the user +wants to account only for the forecasts which the corresponding observations +are available at all leadtimes.\cr The confidence interval is computed by the Fisher transformation and the significance level relies on an one-sided student-T distribution.\cr -If the dataset has more than one member, ensemble mean is necessary necessary -before using this function since it only allows one dimension 'dat_dim' to -have inconsistent length between 'exp' and 'obs'. If all the dimensions of -'exp' and 'obs' are identical, you can simply use apply() and cor() to +The function can calculate ensemble mean before correlation by 'memb_dim' +specified and 'memb = F'. If ensemble mean is not calculated, correlation will +be calculated for each member. +If there is only one dataset for exp and obs, you can simply use cor() to compute the correlation. } \examples{ -- GitLab From 5bf47ac9a85c39f090101d0241bc98273e9659b5 Mon Sep 17 00:00:00 2001 From: aho Date: Tue, 5 Jul 2022 18:24:52 +0200 Subject: [PATCH 019/140] Formatting --- man/s2dv-package.Rd | 10 +--------- man/sampleDepthData.Rd | 6 ++++-- man/sampleMap.Rd | 6 ++++-- man/sampleTimeSeries.Rd | 6 ++++-- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/man/s2dv-package.Rd b/man/s2dv-package.Rd index ffb6783..3c98a95 100644 --- a/man/s2dv-package.Rd +++ b/man/s2dv-package.Rd @@ -6,15 +6,7 @@ \alias{s2dv-package} \title{s2dv: A Set of Common Tools for Seasonal to Decadal Verification} \description{ -The advanced version of package 's2dverification'. It is - intended for 'seasonal to decadal' (s2d) climate forecast verification, but - it can also be used in other kinds of forecasts or general climate analysis. - This package is specially designed for the comparison between the experimental - and observational datasets. The functionality of the included functions covers - from data retrieval, data post-processing, skill scores against observation, - to visualization. Compared to 's2dverification', 's2dv' is more compatible - with the package 'startR', able to use multiple cores for computation and - handle multi-dimensional arrays with a higher flexibility. +The advanced version of package 's2dverification'. It is intended for 'seasonal to decadal' (s2d) climate forecast verification, but it can also be used in other kinds of forecasts or general climate analysis. This package is specially designed for the comparison between the experimental and observational datasets. The functionality of the included functions covers from data retrieval, data post-processing, skill scores against observation, to visualization. Compared to 's2dverification', 's2dv' is more compatible with the package 'startR', able to use multiple cores for computation and handle multi-dimensional arrays with a higher flexibility. } \references{ \url{https://earth.bsc.es/gitlab/es/s2dv/} diff --git a/man/sampleDepthData.Rd b/man/sampleDepthData.Rd index 77e4a7a..47e2f1b 100644 --- a/man/sampleDepthData.Rd +++ b/man/sampleDepthData.Rd @@ -5,7 +5,8 @@ \alias{sampleDepthData} \title{Sample of Experimental Data for Forecast Verification In Function Of Latitudes And Depths} -\format{The data set provides with a variable named 'sampleDepthData'.\cr\cr +\format{ +The data set provides with a variable named 'sampleDepthData'.\cr\cr sampleDepthData$exp is an array that contains the experimental data and the dimension meanings and values are:\cr @@ -18,7 +19,8 @@ but in this sample is not defined (NULL).\cr\cr sampleDepthData$depths is an array with the 7 longitudes covered by the data.\cr\cr -sampleDepthData$lat is an array with the 21 latitudes covered by the data.\cr\cr} +sampleDepthData$lat is an array with the 21 latitudes covered by the data.\cr\cr +} \usage{ data(sampleDepthData) } diff --git a/man/sampleMap.Rd b/man/sampleMap.Rd index eaf8aa5..e4ec5a5 100644 --- a/man/sampleMap.Rd +++ b/man/sampleMap.Rd @@ -4,7 +4,8 @@ \name{sampleMap} \alias{sampleMap} \title{Sample Of Observational And Experimental Data For Forecast Verification In Function Of Longitudes And Latitudes} -\format{The data set provides with a variable named 'sampleMap'.\cr\cr +\format{ +The data set provides with a variable named 'sampleMap'.\cr\cr sampleMap$mod is an array that contains the experimental data and the dimension meanings and values are:\cr c(# of experimental datasets, # of members, # of starting dates, # of lead-times, # of latitudes, # of longitudes)\cr @@ -16,7 +17,8 @@ sampleMap$obs is an array that contains the observational data and the dimension sampleMap$lat is an array with the 2 latitudes covered by the data (see examples on Load() for details on why such low resolution).\cr\cr - sampleMap$lon is an array with the 3 longitudes covered by the data (see examples on Load() for details on why such low resolution).} + sampleMap$lon is an array with the 3 longitudes covered by the data (see examples on Load() for details on why such low resolution). +} \usage{ data(sampleMap) } diff --git a/man/sampleTimeSeries.Rd b/man/sampleTimeSeries.Rd index 05a8e79..7f058e2 100644 --- a/man/sampleTimeSeries.Rd +++ b/man/sampleTimeSeries.Rd @@ -4,7 +4,8 @@ \name{sampleTimeSeries} \alias{sampleTimeSeries} \title{Sample Of Observational And Experimental Data For Forecast Verification As Area Averages} -\format{The data set provides with a variable named 'sampleTimeSeries'.\cr\cr +\format{ +The data set provides with a variable named 'sampleTimeSeries'.\cr\cr sampleTimeSeries$mod is an array that contains the experimental data and the dimension meanings and values are:\cr c(# of experimental datasets, # of members, # of starting dates, # of lead-times)\cr @@ -16,7 +17,8 @@ sampleTimeSeries$obs is an array that contains the observational data and the di sampleTimeSeries$lat is an array with the 2 latitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution).\cr\cr -sampleTimeSeries$lon is an array with the 3 longitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution).} +sampleTimeSeries$lon is an array with the 3 longitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution). +} \usage{ data(sampleTimeSeries) } -- GitLab From 4664a335c692ef327e5b544436589ae14366ee73 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 6 Jul 2022 16:22:03 +0200 Subject: [PATCH 020/140] Correct case memb parameter and delete repeated lines --- R/Corr.R | 163 +++++++++---------------------------------------------- 1 file changed, 25 insertions(+), 138 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index d9e8144..124e525 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -241,137 +241,23 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', rm(obs_sub, outrows) } - if (is.null(memb_dim)) { - if (is.null(dat_dim)) { - res <- Apply(list(exp, obs), - target_dims = list(c(time_dim), - c(time_dim)), - fun = .Corr, - dat_dim = dat_dim, memb_dim = memb_dim, - time_dim = time_dim, method = method, - pval = pval, conf = conf, conf.lev = conf.lev, - ncores = ncores) - } else { - # Define output_dims - if (conf & pval) { - output_dims <- list(corr = c('nexp', 'nobs'), - p.val = c('nexp', 'nobs'), - conf.lower = c('nexp', 'nobs'), - conf.upper = c('nexp', 'nobs')) - } else if (conf & !pval) { - output_dims <- list(corr = c('nexp', 'nobs'), - conf.lower = c('nexp', 'nobs'), - conf.upper = c('nexp', 'nobs')) - } else if (!conf & pval) { - output_dims <- list(corr = c('nexp', 'nobs'), - p.val = c('nexp', 'nobs')) - } else { - output_dims <- list(corr = c('nexp', 'nobs')) - } - - res <- Apply(list(exp, obs), - target_dims = list(c(time_dim, dat_dim), - c(time_dim, dat_dim)), - output_dims = output_dims, - fun = .Corr, - dat_dim = dat_dim, memb_dim = memb_dim, - time_dim = time_dim, method = method, - pval = pval, conf = conf, conf.lev = conf.lev, - ncores = ncores) - - }} else { + if (!is.null(memb_dim)) { if (!memb) { #ensemble mean name_exp <- names(dim(exp)) margin_dims_ind <- c(1:length(name_exp))[-which(name_exp == memb_dim)] exp <- apply(exp, margin_dims_ind, mean, na.rm = TRUE) #NOTE: remove NAs here obs <- apply(obs, margin_dims_ind, mean, na.rm = TRUE) - - # Define output_dims - if (conf & pval) { - output_dims <- list(corr = c('nexp', 'nobs'), - p.val = c('nexp', 'nobs'), - conf.lower = c('nexp', 'nobs'), - conf.upper = c('nexp', 'nobs')) - } else if (conf & !pval) { - output_dims <- list(corr = c('nexp', 'nobs'), - conf.lower = c('nexp', 'nobs'), - conf.upper = c('nexp', 'nobs')) - } else if (!conf & pval) { - output_dims <- list(corr = c('nexp', 'nobs'), - p.val = c('nexp', 'nobs')) - } else { - output_dims <- list(corr = c('nexp', 'nobs')) - } - - res <- Apply(list(exp, obs), - target_dims = list(c(time_dim, dat_dim), - c(time_dim, dat_dim)), - output_dims = output_dims, - fun = .Corr, - dat_dim = dat_dim, memb_dim = NULL, - time_dim = time_dim, method = method, - pval = pval, conf = conf, conf.lev = conf.lev, ncores_input = ncores, - ncores = ncores) - - } else { # correlation for each member - if (is.null(dat_dim)) { - # Define output_dims - if (conf & pval) { - output_dims <- list(corr = c('exp_memb', 'obs_memb'), - p.val = c('exp_memb', 'obs_memb'), - conf.lower = c('exp_memb', 'obs_memb'), - conf.upper = c('exp_memb', 'obs_memb')) - } else if (conf & !pval) { - output_dims <- list(corr = c('exp_memb', 'obs_memb'), - conf.lower = c('exp_memb', 'obs_memb'), - conf.upper = c('exp_memb', 'obs_memb')) - } else if (!conf & pval) { - output_dims <- list(corr = c('exp_memb', 'obs_memb'), - p.val = c('exp_memb', 'obs_memb')) - } else { - output_dims <- list(corr = c('exp_memb', 'obs_memb')) - } - - res <- Apply(list(exp, obs), - target_dims = list(c(time_dim, memb_dim), - c(time_dim, memb_dim)), - output_dims = output_dims, - fun = .Corr, - dat_dim = dat_dim, memb_dim = memb_dim, - time_dim = time_dim, method = method, - pval = pval, conf = conf, conf.lev = conf.lev, ncores_input = ncores, - ncores = ncores) - } else { - - # Define output_dims - if (conf & pval) { - output_dims <- list(corr = c('nexp', 'nobs', 'exp_memb', 'obs_memb'), - p.val = c('nexp', 'nobs', 'exp_memb', 'obs_memb'), - conf.lower = c('nexp', 'nobs', 'exp_memb', 'obs_memb'), - conf.upper = c('nexp', 'nobs', 'exp_memb', 'obs_memb')) - } else if (conf & !pval) { - output_dims <- list(corr = c('nexp', 'nobs', 'exp_memb', 'obs_memb'), - conf.lower = c('nexp', 'nobs', 'exp_memb', 'obs_memb'), - conf.upper = c('nexp', 'nobs', 'exp_memb', 'obs_memb')) - } else if (!conf & pval) { - output_dims <- list(corr = c('nexp', 'nobs', 'exp_memb', 'obs_memb'), - p.val = c('nexp', 'nobs', 'exp_memb', 'obs_memb')) - } else { - output_dims <- list(corr = c('nexp', 'nobs', 'exp_memb', 'obs_memb')) - } - - res <- Apply(list(exp, obs), + memb_dim <- NULL + } + } + res <- Apply(list(exp, obs), target_dims = list(c(time_dim, dat_dim, memb_dim), c(time_dim, dat_dim, memb_dim)), - output_dims = output_dims, fun = .Corr, dat_dim = dat_dim, memb_dim = memb_dim, time_dim = time_dim, method = method, pval = pval, conf = conf, conf.lev = conf.lev, ncores_input = ncores, - ncores = ncores) - } - } - } + ncores = ncores) return(res) } @@ -385,28 +271,29 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', # obs: [sdate] nexp <- 1 nobs <- 1 + CORR <- array(dim = c(nexp = nexp, nobs = nobs)) CORR <- cor(exp, obs,use = "pairwise.complete.obs",method = method) } else { # exp: [sdate, dat_exp] # obs: [sdate, dat_obs] nexp <- as.numeric(dim(exp)[dat_dim]) nobs <- as.numeric(dim(obs)[dat_dim]) + CORR <- array(dim = c(nexp = nexp, nobs = nobs)) + + for (j in 1:nobs) { + for (y in 1:nexp) { + + if (any(!is.na(exp[,y])) && sum(!is.na(obs[, j])) > 2) { + CORR[y, j] <- cor(exp[, y], obs[, j], + use = "pairwise.complete.obs", + method = method) + } else { + NA #CORR[, i] <- NA + } -# NOTE: Use sapply to replace the for loop - CORR <- sapply(1:nobs, function(i) { - sapply(1:nexp, function (x) { - if (any(!is.na(exp[, x])) && sum(!is.na(obs[, i])) > 2) { #NOTE: Is this necessary? - cor(exp[, x], obs[, i], - use = "pairwise.complete.obs", - method = method) - } else { - NA #CORR[, i] <- NA + } } - }) - }) - if (is.null(dim(CORR))) { - CORR <- array(CORR, dim = c(1, 1)) - }} + } } else { # member if (is.null(dat_dim)) { @@ -434,10 +321,10 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } else { # exp: [sdate, dat_exp, memb_exp] # obs: [sdate, dat_obs, memb_obs] - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) - exp_memb <- as.numeric(dim(exp)[3]) - obs_memb <- as.numeric(dim(obs)[3]) + nexp <- as.numeric(dim(exp)[dat_dim]) + nobs <- as.numeric(dim(obs)[dat_dim]) + exp_memb <- as.numeric(dim(exp)[memb_dim]) + obs_memb <- as.numeric(dim(obs)[memb_dim]) CORR <- array(dim = c(nexp = nexp, nobs = nobs, exp_memb = exp_memb, obs_memb = obs_memb)) -- GitLab From e2d0bb064eee6c786cc332db0a004dc2b67b7c99 Mon Sep 17 00:00:00 2001 From: aho Date: Fri, 8 Jul 2022 16:44:33 +0200 Subject: [PATCH 021/140] Formatting and code improvement --- R/Corr.R | 99 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index 124e525..efea0c8 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -243,13 +243,16 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', if (!is.null(memb_dim)) { if (!memb) { #ensemble mean - name_exp <- names(dim(exp)) - margin_dims_ind <- c(1:length(name_exp))[-which(name_exp == memb_dim)] - exp <- apply(exp, margin_dims_ind, mean, na.rm = TRUE) #NOTE: remove NAs here - obs <- apply(obs, margin_dims_ind, mean, na.rm = TRUE) + exp <- MeanDims(exp, memb_dim, na.rm = TRUE) + obs <- MeanDims(obs, memb_dim, na.rm = TRUE) +# name_exp <- names(dim(exp)) +# margin_dims_ind <- c(1:length(name_exp))[-which(name_exp == memb_dim)] +# exp <- apply(exp, margin_dims_ind, mean, na.rm = TRUE) #NOTE: remove NAs here +# obs <- apply(obs, margin_dims_ind, mean, na.rm = TRUE) memb_dim <- NULL } } + res <- Apply(list(exp, obs), target_dims = list(c(time_dim, dat_dim, memb_dim), c(time_dim, dat_dim, memb_dim)), @@ -264,7 +267,6 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', .Corr <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', time_dim = 'sdate', method = 'pearson', conf = TRUE, pval = TRUE, conf.lev = 0.95, ncores_input = NULL) { - if (is.null(memb_dim)) { if (is.null(dat_dim)) { # exp: [sdate] @@ -272,59 +274,70 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', nexp <- 1 nobs <- 1 CORR <- array(dim = c(nexp = nexp, nobs = nobs)) - CORR <- cor(exp, obs,use = "pairwise.complete.obs",method = method) + if (any(!is.na(exp)) && sum(!is.na(obs)) > 2) { + CORR <- cor(exp, obs, use = "pairwise.complete.obs", method = method) + } } else { - # exp: [sdate, dat_exp] - # obs: [sdate, dat_obs] - nexp <- as.numeric(dim(exp)[dat_dim]) - nobs <- as.numeric(dim(obs)[dat_dim]) - CORR <- array(dim = c(nexp = nexp, nobs = nobs)) - + # exp: [sdate, dat_exp] + # obs: [sdate, dat_obs] + nexp <- as.numeric(dim(exp)[dat_dim]) + nobs <- as.numeric(dim(obs)[dat_dim]) + CORR <- array(dim = c(nexp = nexp, nobs = nobs)) for (j in 1:nobs) { - for (y in 1:nexp) { - - if (any(!is.na(exp[,y])) && sum(!is.na(obs[, j])) > 2) { - CORR[y, j] <- cor(exp[, y], obs[, j], - use = "pairwise.complete.obs", - method = method) - } else { - NA #CORR[, i] <- NA - } - - } + for (y in 1:nexp) { + if (any(!is.na(exp[, y])) && sum(!is.na(obs[, j])) > 2) { + CORR[y, j] <- cor(exp[, y], obs[, j], + use = "pairwise.complete.obs", + method = method) + } + } } - } +#---------------------------------------- +# Same as above calculation. +#TODO: Compare which is faster. +# CORR <- sapply(1:nobs, function(i) { +# sapply(1:nexp, function (x) { +# if (any(!is.na(exp[, x])) && sum(!is.na(obs[, i])) > 2) { +# cor(exp[, x], obs[, i], +# use = "pairwise.complete.obs", +# method = method) +# } else { +# NA +# } +# }) +# }) +#----------------------------------------- + } - } else { # member - if (is.null(dat_dim)) { + } else { # memb_dim != NULL + exp_memb <- as.numeric(dim(exp)[memb_dim]) # memb_dim + obs_memb <- as.numeric(dim(obs)[memb_dim]) + + if (is.null(dat_dim)) { # exp: [sdate, memb_exp] # obs: [sdate, memb_obs] nexp <- 1 nobs <- 1 - exp_memb <- as.numeric(dim(exp)[memb_dim]) # memb_dim - obs_memb <- as.numeric(dim(obs)[memb_dim]) CORR <- array(dim = c(nexp = nexp, nobs = nobs, exp_memb = exp_memb, obs_memb = obs_memb)) for (j in 1:obs_memb) { - for (y in 1:exp_memb) { + for (y in 1:exp_memb) { if (any(!is.na(exp[,y])) && sum(!is.na(obs[, j])) > 2) { - CORR[, , y, j] <- cor(exp[, y], obs[, j], - use = "pairwise.complete.obs", - method = method) - } else { - NA #CORR[, i] <- NA - } - + CORR[, , y, j] <- cor(exp[, y], obs[, j], + use = "pairwise.complete.obs", + method = method) + } else { + NA #CORR[, i] <- NA } + + } } } else { # exp: [sdate, dat_exp, memb_exp] # obs: [sdate, dat_obs, memb_obs] nexp <- as.numeric(dim(exp)[dat_dim]) nobs <- as.numeric(dim(obs)[dat_dim]) - exp_memb <- as.numeric(dim(exp)[memb_dim]) - obs_memb <- as.numeric(dim(obs)[memb_dim]) CORR <- array(dim = c(nexp = nexp, nobs = nobs, exp_memb = exp_memb, obs_memb = obs_memb)) @@ -400,16 +413,16 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', confhigh <- tanh(atanh(CORR) + qnorm(conf.upper) / sqrt(eno_expand - 3)) } -################################### NEW -# Remove nexp and nobs if dat_dim = NULL +################################### + # Remove nexp and nobs if dat_dim = NULL if (is.null(dat_dim) & !is.null(memb_dim)) { dim(CORR) <- dim(CORR)[3:length(dim(CORR))] if (pval) { - dim(p.val) <- dim(p.val)[3:length(dim(p.val))] + dim(p.val) <- dim(p.val)[3:length(dim(p.val))] } if (conf) { - dim(conflow) <- dim(conflow)[3:length(dim(conflow))] - dim(confhigh) <- dim(confhigh)[3:length(dim(confhigh))] + dim(conflow) <- dim(conflow)[3:length(dim(conflow))] + dim(confhigh) <- dim(confhigh)[3:length(dim(confhigh))] } } -- GitLab From 81272e111de70302341d0bc24e503bb8bb499644 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 14 Jul 2022 10:06:51 +0200 Subject: [PATCH 022/140] Fix code corr method 'kendall' and 'spearman' --- R/Corr.R | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index efea0c8..3e400b3 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -289,7 +289,7 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', CORR[y, j] <- cor(exp[, y], obs[, j], use = "pairwise.complete.obs", method = method) - } + } } } #---------------------------------------- @@ -319,18 +319,13 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', nexp <- 1 nobs <- 1 CORR <- array(dim = c(nexp = nexp, nobs = nobs, exp_memb = exp_memb, obs_memb = obs_memb)) - for (j in 1:obs_memb) { for (y in 1:exp_memb) { - if (any(!is.na(exp[,y])) && sum(!is.na(obs[, j])) > 2) { CORR[, , y, j] <- cor(exp[, y], obs[, j], use = "pairwise.complete.obs", method = method) - } else { - NA #CORR[, i] <- NA } - } } } else { @@ -338,9 +333,7 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', # obs: [sdate, dat_obs, memb_obs] nexp <- as.numeric(dim(exp)[dat_dim]) nobs <- as.numeric(dim(obs)[dat_dim]) - CORR <- array(dim = c(nexp = nexp, nobs = nobs, exp_memb = exp_memb, obs_memb = obs_memb)) - for (j in 1:obs_memb) { for (y in 1:exp_memb) { CORR[, , y, j] <- sapply(1:nobs, function(i) { @@ -349,19 +342,14 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', cor(exp[, x, y], obs[, i, j], use = "pairwise.complete.obs", method = method) - } else { - NA #CORR[, i] <- NA } }) }) - } } } - } - # if (pval) { # for (i in 1:nobs) { # p.val[, i] <- try(sapply(1:nexp, @@ -375,14 +363,13 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', # } if (pval | conf) { - if (method == "kendall" | method == "spearman") { + if ((method == "kendall" | method == "spearman") && (!is.null(dat_dim) | !is.null(memb_dim))) { tmp <- apply(obs, c(1:length(dim(obs)))[-1], rank) # for memb_dim = NULL, 2; for memb_dim, c(2, 3) names(dim(tmp))[1] <- time_dim eno <- Eno(tmp, time_dim, ncores = ncores_input) - } else if (method == "pearson") { - eno <- Eno(obs, time_dim, ncores = ncores_input) + } else { # (dat_dim = NULL and memb_dim = NULL) or method = 'pearson' + eno <- Eno(obs, time_dim, ncores = ncores_input) } - if (is.null(memb_dim)) { eno_expand <- array(dim = c(nexp = nexp, nobs = nobs)) for (i in 1:nexp) { -- GitLab From e7f4ec1b7cb43572601b7a3d82682d25cdd5f791 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 14 Jul 2022 13:13:34 +0200 Subject: [PATCH 023/140] Revert changes due to failed pipeline --- R/Corr.R | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index 3e400b3..efea0c8 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -289,7 +289,7 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', CORR[y, j] <- cor(exp[, y], obs[, j], use = "pairwise.complete.obs", method = method) - } + } } } #---------------------------------------- @@ -319,13 +319,18 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', nexp <- 1 nobs <- 1 CORR <- array(dim = c(nexp = nexp, nobs = nobs, exp_memb = exp_memb, obs_memb = obs_memb)) + for (j in 1:obs_memb) { for (y in 1:exp_memb) { + if (any(!is.na(exp[,y])) && sum(!is.na(obs[, j])) > 2) { CORR[, , y, j] <- cor(exp[, y], obs[, j], use = "pairwise.complete.obs", method = method) + } else { + NA #CORR[, i] <- NA } + } } } else { @@ -333,7 +338,9 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', # obs: [sdate, dat_obs, memb_obs] nexp <- as.numeric(dim(exp)[dat_dim]) nobs <- as.numeric(dim(obs)[dat_dim]) + CORR <- array(dim = c(nexp = nexp, nobs = nobs, exp_memb = exp_memb, obs_memb = obs_memb)) + for (j in 1:obs_memb) { for (y in 1:exp_memb) { CORR[, , y, j] <- sapply(1:nobs, function(i) { @@ -342,14 +349,19 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', cor(exp[, x, y], obs[, i, j], use = "pairwise.complete.obs", method = method) + } else { + NA #CORR[, i] <- NA } }) }) + } } } + } + # if (pval) { # for (i in 1:nobs) { # p.val[, i] <- try(sapply(1:nexp, @@ -363,13 +375,14 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', # } if (pval | conf) { - if ((method == "kendall" | method == "spearman") && (!is.null(dat_dim) | !is.null(memb_dim))) { + if (method == "kendall" | method == "spearman") { tmp <- apply(obs, c(1:length(dim(obs)))[-1], rank) # for memb_dim = NULL, 2; for memb_dim, c(2, 3) names(dim(tmp))[1] <- time_dim eno <- Eno(tmp, time_dim, ncores = ncores_input) - } else { # (dat_dim = NULL and memb_dim = NULL) or method = 'pearson' - eno <- Eno(obs, time_dim, ncores = ncores_input) + } else if (method == "pearson") { + eno <- Eno(obs, time_dim, ncores = ncores_input) } + if (is.null(memb_dim)) { eno_expand <- array(dim = c(nexp = nexp, nobs = nobs)) for (i in 1:nexp) { -- GitLab From 053a121892109017305f94ec7f5843124e1adda4 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 14 Jul 2022 13:25:46 +0200 Subject: [PATCH 024/140] Correct case corr method 'kendall' or 'spearman' --- R/Corr.R | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index efea0c8..fb24787 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -327,8 +327,6 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', CORR[, , y, j] <- cor(exp[, y], obs[, j], use = "pairwise.complete.obs", method = method) - } else { - NA #CORR[, i] <- NA } } @@ -349,8 +347,6 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', cor(exp[, x, y], obs[, i, j], use = "pairwise.complete.obs", method = method) - } else { - NA #CORR[, i] <- NA } }) }) @@ -375,11 +371,11 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', # } if (pval | conf) { - if (method == "kendall" | method == "spearman") { + if ((method == "kendall" | method == "spearman") && (!is.null(dat_dim) | !is.null(memb_dim))) { tmp <- apply(obs, c(1:length(dim(obs)))[-1], rank) # for memb_dim = NULL, 2; for memb_dim, c(2, 3) names(dim(tmp))[1] <- time_dim eno <- Eno(tmp, time_dim, ncores = ncores_input) - } else if (method == "pearson") { + } else { eno <- Eno(obs, time_dim, ncores = ncores_input) } -- GitLab From ee9b8c85ebb1ed8833633a11095cb80809db06b2 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 14 Jul 2022 13:41:03 +0200 Subject: [PATCH 025/140] Revert commit --- R/Corr.R | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index fb24787..efea0c8 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -327,6 +327,8 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', CORR[, , y, j] <- cor(exp[, y], obs[, j], use = "pairwise.complete.obs", method = method) + } else { + NA #CORR[, i] <- NA } } @@ -347,6 +349,8 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', cor(exp[, x, y], obs[, i, j], use = "pairwise.complete.obs", method = method) + } else { + NA #CORR[, i] <- NA } }) }) @@ -371,11 +375,11 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', # } if (pval | conf) { - if ((method == "kendall" | method == "spearman") && (!is.null(dat_dim) | !is.null(memb_dim))) { + if (method == "kendall" | method == "spearman") { tmp <- apply(obs, c(1:length(dim(obs)))[-1], rank) # for memb_dim = NULL, 2; for memb_dim, c(2, 3) names(dim(tmp))[1] <- time_dim eno <- Eno(tmp, time_dim, ncores = ncores_input) - } else { + } else if (method == "pearson") { eno <- Eno(obs, time_dim, ncores = ncores_input) } -- GitLab From 7b3ee5f54ede45e03303fda3a60ea7a1809ee9c8 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 14 Jul 2022 17:06:45 +0200 Subject: [PATCH 026/140] Correct case 'kendall' and 'separman' corr type --- R/Corr.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index efea0c8..d99a1bf 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -375,12 +375,12 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', # } if (pval | conf) { - if (method == "kendall" | method == "spearman") { + if ((method == "kendall" | method == "spearman") && (!is.null(dat_dim) | !is.null(memb_dim))) { tmp <- apply(obs, c(1:length(dim(obs)))[-1], rank) # for memb_dim = NULL, 2; for memb_dim, c(2, 3) names(dim(tmp))[1] <- time_dim eno <- Eno(tmp, time_dim, ncores = ncores_input) - } else if (method == "pearson") { - eno <- Eno(obs, time_dim, ncores = ncores_input) + } else { + eno <- Eno(obs, time_dim, ncores = ncores_input) } if (is.null(memb_dim)) { -- GitLab From 3b4d67a367f3718a000f4ecab292faee213d7bca Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 14 Jul 2022 17:33:58 +0200 Subject: [PATCH 027/140] Delete comments #CORR[, i] <- NA --- R/Corr.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index d99a1bf..b3be256 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -328,7 +328,7 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', use = "pairwise.complete.obs", method = method) } else { - NA #CORR[, i] <- NA + NA } } @@ -350,7 +350,7 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', use = "pairwise.complete.obs", method = method) } else { - NA #CORR[, i] <- NA + NA } }) }) -- GitLab From 662e8393d5462f050fd4f1b0e8ccaeeed25d8bef Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Fri, 15 Jul 2022 11:34:03 +0200 Subject: [PATCH 028/140] Delete else part test --- R/Corr.R | 4 ---- 1 file changed, 4 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index b3be256..245b93a 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -327,8 +327,6 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', CORR[, , y, j] <- cor(exp[, y], obs[, j], use = "pairwise.complete.obs", method = method) - } else { - NA } } @@ -349,8 +347,6 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', cor(exp[, x, y], obs[, i, j], use = "pairwise.complete.obs", method = method) - } else { - NA } }) }) -- GitLab From 92f741219faeb9d93449d64db4be23cd83052965 Mon Sep 17 00:00:00 2001 From: aho Date: Fri, 15 Jul 2022 11:46:23 +0200 Subject: [PATCH 029/140] Add 'else' back for necessary part --- R/Corr.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/Corr.R b/R/Corr.R index 245b93a..d72df52 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -347,6 +347,8 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', cor(exp[, x, y], obs[, i, j], use = "pairwise.complete.obs", method = method) + } else { + NA } }) }) -- GitLab From 099de1fe3a37de68942a34e1bdd76f20e2c76689 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Fri, 15 Jul 2022 15:18:41 +0200 Subject: [PATCH 030/140] Correct case for rank corr method dat_dim and memb_dim NULL --- R/Corr.R | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/R/Corr.R b/R/Corr.R index d72df52..ef710b5 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -373,12 +373,19 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', # } if (pval | conf) { - if ((method == "kendall" | method == "spearman") && (!is.null(dat_dim) | !is.null(memb_dim))) { - tmp <- apply(obs, c(1:length(dim(obs)))[-1], rank) # for memb_dim = NULL, 2; for memb_dim, c(2, 3) - names(dim(tmp))[1] <- time_dim - eno <- Eno(tmp, time_dim, ncores = ncores_input) - } else { - eno <- Eno(obs, time_dim, ncores = ncores_input) + if (method == "kendall" | method == "spearman") { + if (!is.null(dat_dim) | !is.null(memb_dim)) { + tmp <- apply(obs, c(1:length(dim(obs)))[-1], rank) # for memb_dim = NULL, 2; for memb_dim, c(2, 3) + names(dim(tmp))[1] <- time_dim + eno <- Eno(tmp, time_dim, ncores = ncores_input) + } else { + tmp <- rank(obs) + tmp <- array(tmp) + names(dim(tmp)) <- time_dim + eno <- Eno(tmp, time_dim, ncores = ncores_input) + } + } else if (method == "pearson") { + eno <- Eno(obs, time_dim, ncores = ncores_input) } if (is.null(memb_dim)) { -- GitLab From 5eb89fd16643d0a66fb90d36c1f3432ee0403c15 Mon Sep 17 00:00:00 2001 From: aho Date: Fri, 15 Jul 2022 20:48:50 +0200 Subject: [PATCH 031/140] Add test for kendall --- tests/testthat/test-Corr.R | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-Corr.R b/tests/testthat/test-Corr.R index 7d477ad..6e3c016 100644 --- a/tests/testthat/test-Corr.R +++ b/tests/testthat/test-Corr.R @@ -455,6 +455,23 @@ test_that("7. Output checks: dat6", { 0.1084488, tolerance = 0.0001 ) + # kendall + expect_equal( + as.vector(Corr(exp6, obs6, dat_dim = NULL, memb_dim = NULL, method = 'kendall')$corr[2:4]), + c(0.0, 0.6, 0.2), + tolerance = 0.0001 + ) + expect_equal( + as.vector(Corr(exp6, obs6, dat_dim = NULL, memb_dim = NULL, method = 'kendall')$p[2:4]), + c(0.5000000, 0.1423785, 0.3735300), + tolerance = 0.0001 + ) + expect_equal( + as.vector(Corr(exp6, obs6, dat_dim = NULL, memb_dim = NULL, method = 'kendall')$conf.low[2:4]), + c(-0.8822664, -0.5997500, -0.8284490), + tolerance = 0.0001 + ) + }) ############################################## test_that("8. Output checks: dat6 and dat7", { @@ -464,4 +481,4 @@ test_that("8. Output checks: dat6 and dat7", { tolerance = 0.0001 ) }) -############################################## \ No newline at end of file +############################################## -- GitLab From 2a1349d00d41d2ea3da692d392f202886c50c29e Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 12:43:24 +0200 Subject: [PATCH 032/140] Add space in text output --- R/Corr.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/Corr.R b/R/Corr.R index ef710b5..67efb09 100644 --- a/R/Corr.R +++ b/R/Corr.R @@ -137,7 +137,7 @@ Corr <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", - "Set it as NULL if there is no dataset dimension.") + " Set it as NULL if there is no dataset dimension.") } } ## comp_dim -- GitLab From b16946d180c3bae0f0db7d905cfb8cf6c1387b70 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 12:50:06 +0200 Subject: [PATCH 033/140] Allow dat_dim NULL in RMS, RMSSS and added tests for both --- R/RMS.R | 36 +++-- R/RMSSS.R | 259 +++++++++++++++++++++--------------- tests/testthat/test-RMS.R | 50 +++++++ tests/testthat/test-RMSSS.R | 27 ++++ 4 files changed, 258 insertions(+), 114 deletions(-) diff --git a/R/RMS.R b/R/RMS.R index 20da981..7a38a72 100644 --- a/R/RMS.R +++ b/R/RMS.R @@ -156,8 +156,8 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) if (!is.null(dat_dim)) { - name_exp <- name_exp[-which(name_exp == dat_dim)] - name_obs <- name_obs[-which(name_obs == dat_dim)] + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", @@ -202,12 +202,22 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } .RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', - conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { - - # exp: [sdate, dat_exp] - # obs: [sdate, dat_obs] - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) + conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { + if (is.null(dat_dim)) { + # exp: [sdate] + # obs: [sdate] + nexp <- 1 + nobs <- 1 + ini_dims <- dim(exp) + dim(exp) <- c(ini_dims, dat_dim = 1) + dim(obs) <- c(ini_dims, dat_dim = 1) + } else { + # exp: [sdate, dat_exp] + # obs: [sdate, dat_obs] + nexp <- as.numeric(dim(exp)[2]) + nobs <- as.numeric(dim(obs)[2]) + } + nsdate <- as.numeric(dim(exp)[1]) dif <- array(dim = c(sdate = nsdate, nexp = nexp, nobs = nobs)) @@ -242,6 +252,16 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', conf.upper <- (eno * rms ** 2 / chi) ** 0.5 } + ################################### + # Remove nexp and nobs if dat_dim = NULL + if (is.null(dat_dim)) { + dim(rms) <- NULL + dim(conf.lower) <- NULL + dim(conf.upper) <- NULL + } + + ################################### + if (conf) { res <- list(rms = rms, conf.lower = conf.lower, conf.upper = conf.upper) } else { diff --git a/R/RMSSS.R b/R/RMSSS.R index 9526517..7a38a72 100644 --- a/R/RMSSS.R +++ b/R/RMSSS.R @@ -1,64 +1,75 @@ -#'Compute root mean square error skill score +#'Compute root mean square error #' -#'Compute the root mean square error skill score (RMSSS) between an array of -#'forecast 'exp' and an array of observation 'obs'. The two arrays should -#'have the same dimensions except along dat_dim, where the length can be -#'different, with the number of experiments/models (nexp) and the number of -#'observational datasets (nobs).\cr -#'RMSSS computes the root mean square error skill score of each jexp in 1:nexp -#'against each jobs in 1:nobs which gives nexp * nobs RMSSS for each other -#'grid point of the array.\cr -#'The RMSSS are computed along the time_dim dimension which should corresponds -#'to the startdate dimension.\cr -#'The p-value is optionally provided by an one-sided Fisher test.\cr +#'Compute the root mean square error for an array of forecasts and an array of +#'observations. The RMSEs are computed along time_dim, the dimension which +#'corresponds to the startdate dimension. If comp_dim is given, the RMSEs are +#'computed only if obs along the comp_dim dimension are complete between +#'limits[1] and limits[2], i.e. there are no NAs between limits[1] and +#'limits[2]. This option can be activated if the user wishes to account only +#'for the forecasts for which the corresponding observations are available at +#'all leadtimes.\cr +#'The confidence interval is computed by the chi2 distribution.\cr #' -#'@param exp A named numeric array of experimental data which contains at least -#' two dimensions for dat_dim and time_dim. It can also be a vector with the +#'@param exp A named numeric array of experimental data, with at least two +#' dimensions 'time_dim' and 'dat_dim'. It can also be a vector with the #' same length as 'obs', then the vector will automatically be 'time_dim' and #' 'dat_dim' will be 1. -#'@param obs A named numeric array of observational data which contains at least -#' two dimensions for dat_dim and time_dim. The dimensions should be the same -#' as paramter 'exp' except the length of 'dat_dim' dimension. The order of -#' dimension can be different. It can also be a vector with the same length as -#' 'exp', then the vector will automatically be 'time_dim' and 'dat_dim' will -#' be 1. -#'@param dat_dim A character string indicating the name of dataset (nobs/nexp) +#'@param obs A named numeric array of observational data, same dimensions as +#' parameter 'exp' except along dat_dim. It can also be a vector with the same +#' length as 'exp', then the vector will automatically be 'time_dim' and +#' 'dat_dim' will be 1. +#'@param time_dim A character string indicating the name of dimension along +#' which the correlations are computed. The default value is 'sdate'. +#'@param dat_dim A character string indicating the name of member (nobs/nexp) #' dimension. The default value is 'dataset'. If there is no dataset #' dimension, set NULL. -#'@param time_dim A character string indicating the name of dimension along -#' which the RMSSS are computed. The default value is 'sdate'. -#'@param pval A logical value indicating whether to compute or not the p-value -#' of the test Ho: RMSSS = 0. If pval = TRUE, the insignificant RMSSS will -#' return NA. The default value is TRUE. +#'@param comp_dim A character string indicating the name of dimension along which +#' obs is taken into account only if it is complete. The default value +#' is NULL. +#'@param limits A vector of two integers indicating the range along comp_dim to +#' be completed. The default value is c(1, length(comp_dim dimension)). +#'@param conf A logical value indicating whether to retrieve the confidence +#' intervals or not. The default value is TRUE. +#'@param conf.lev A numeric indicating the confidence level for the +#' regression computation. The default value is 0.95. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' -#'@return +#'@return #'A list containing the numeric arrays with dimension:\cr #' c(nexp, nobs, all other dimensions of exp except time_dim).\cr #'nexp is the number of experiment (i.e., dat_dim in exp), and nobs is the #'number of observation (i.e., dat_dim in obs).\cr -#'\item{$rmsss}{ -#' The root mean square error skill score. +#'\item{$rms}{ +#' The root mean square error. +#'} +#'\item{$conf.lower}{ +#' The lower confidence interval. Only present if \code{conf = TRUE}. #'} -#'\item{$p.val}{ -#' The p-value. Only present if \code{pval = TRUE}. +#'\item{$conf.upper}{ +#' The upper confidence interval. Only present if \code{conf = TRUE}. #'} #' #'@examples -#' set.seed(1) -#' exp <- array(rnorm(30), dim = c(dataset = 2, time = 3, memb = 5)) -#' set.seed(2) -#' obs <- array(rnorm(15), dim = c(time = 3, memb = 5, dataset = 1)) -#' res <- RMSSS(exp, obs, time_dim = 'time') +#'# Load sample data as in Load() example: +#' set.seed(1) +#' exp1 <- array(rnorm(120), dim = c(dataset = 3, sdate = 5, ftime = 2, lon = 1, lat = 4)) +#' set.seed(2) +#' obs1 <- array(rnorm(80), dim = c(dataset = 2, sdate = 5, ftime = 2, lon = 1, lat = 4)) +#' set.seed(2) +#' na <- floor(runif(10, min = 1, max = 80)) +#' obs1[na] <- NA +#' res <- RMS(exp1, obs1, comp_dim = 'ftime') +#' # Renew example when Ano and Smoothing are ready #' -#'@rdname RMSSS +#'@rdname RMS #'@import multiApply -#'@importFrom stats pf +#'@importFrom ClimProjDiags Subset +#'@importFrom stats qchisq #'@export -RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', - pval = TRUE, ncores = NULL) { - +RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', + comp_dim = NULL, limits = NULL, + conf = TRUE, conf.lev = 0.95, ncores = NULL) { # Check inputs ## exp and obs (1) if (is.null(exp) | is.null(obs)) { @@ -87,7 +98,7 @@ RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } if(!all(names(dim(exp)) %in% names(dim(obs))) | !all(names(dim(obs)) %in% names(dim(exp)))) { - stop("Parameter 'exp' and 'obs' must have same dimension name.") + stop("Parameter 'exp' and 'obs' must have same dimension name") } ## time_dim if (!is.character(time_dim) | length(time_dim) > 1) { @@ -106,9 +117,33 @@ RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', "Set it as NULL if there is no dataset dimension.") } } - ## pval - if (!is.logical(pval) | length(pval) > 1) { - stop("Parameter 'pval' must be one logical value.") + ## comp_dim + if (!is.null(comp_dim)) { + if (!is.character(comp_dim) | length(comp_dim) > 1) { + stop("Parameter 'comp_dim' must be a character string.") + } + if (!comp_dim %in% names(dim(exp)) | !comp_dim %in% names(dim(obs))) { + stop("Parameter 'comp_dim' is not found in 'exp' or 'obs' dimension.") + } + } + ## limits + if (!is.null(limits)) { + if (is.null(comp_dim)) { + stop("Paramter 'comp_dim' cannot be NULL if 'limits' is assigned.") + } + if (!is.numeric(limits) | any(limits %% 1 != 0) | any(limits < 0) | + length(limits) != 2 | any(limits > dim(exp)[comp_dim])) { + stop(paste0("Parameter 'limits' must be a vector of two positive ", + "integers smaller than the length of paramter 'comp_dim'.")) + } + } + ## conf + if (!is.logical(conf) | length(conf) > 1) { + stop("Parameter 'conf' must be one logical value.") + } + ## conf.lev + if (!is.numeric(conf.lev) | conf.lev < 0 | conf.lev > 1 | length(conf.lev) > 1) { + stop("Parameter 'conf.lev' must be a numeric number between 0 and 1.") } ## ncores if (!is.null(ncores)) { @@ -121,17 +156,17 @@ RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) if (!is.null(dat_dim)) { - name_exp <- name_exp[-which(name_exp == dat_dim)] - name_obs <- name_obs[-which(name_obs == dat_dim)] + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", "all dimension except 'dat_dim'.")) } - if (dim(exp)[time_dim] <= 2) { - stop("The length of time_dim must be more than 2 to compute RMSSS.") + if (dim(exp)[time_dim] < 2) { + stop("The length of time_dim must be at least 2 to compute RMS.") } - + ############################### # Sort dimension @@ -142,85 +177,97 @@ RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ############################### - # Calculate RMSSS + # Calculate RMS + + # Remove data along comp_dim dim if there is at least one NA between limits + if (!is.null(comp_dim)) { + if (is.null(limits)) { + limits <- c(1, dim(obs)[comp_dim]) + } + pos <- which(names(dim(obs)) == comp_dim) + obs_sub <- Subset(obs, pos, list(limits[1]:limits[2])) + outrows <- is.na(MeanDims(obs_sub, pos, na.rm = FALSE)) + outrows <- InsertDim(outrows, pos, dim(obs)[comp_dim]) + obs[which(outrows)] <- NA + } res <- Apply(list(exp, obs), target_dims = list(c(time_dim, dat_dim), c(time_dim, dat_dim)), - fun = .RMSSS, + fun = .RMS, time_dim = time_dim, dat_dim = dat_dim, - pval = pval, ncores_input = ncores, + conf = conf, conf.lev = conf.lev, ncores_input = ncores, ncores = ncores) - return(res) } -.RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', pval = TRUE, - ncores_input = NULL) { - # exp: [sdate, dat_exp] - # obs: [sdate, dat_obs] - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) - nsdate <- as.numeric(dim(exp)[1]) +.RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', + conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { + if (is.null(dat_dim)) { + # exp: [sdate] + # obs: [sdate] + nexp <- 1 + nobs <- 1 + ini_dims <- dim(exp) + dim(exp) <- c(ini_dims, dat_dim = 1) + dim(obs) <- c(ini_dims, dat_dim = 1) + } else { + # exp: [sdate, dat_exp] + # obs: [sdate, dat_obs] + nexp <- as.numeric(dim(exp)[2]) + nobs <- as.numeric(dim(obs)[2]) + } - p_val <- array(dim = c(nexp = nexp, nobs = nobs)) - dif1 <- array(dim = c(nsdate, nexp, nobs)) - names(dim(dif1)) <- c(time_dim, 'nexp', 'nobs') + nsdate <- as.numeric(dim(exp)[1]) -# if (conf) { -# conflow <- (1 - conf.lev) / 2 -# confhigh <- 1 - conflow -# conf_low <- array(dim = c(nexp = nexp, nobs = nobs)) -# conf_high <- array(dim = c(nexp = nexp, nobs = nobs)) -# } + dif <- array(dim = c(sdate = nsdate, nexp = nexp, nobs = nobs)) + chi <- array(dim = c(nexp = nexp, nobs = nobs)) + if (conf) { + conflow <- (1 - conf.lev) / 2 + confhigh <- 1 - conflow + conf.lower <- array(dim = c(nexp = nexp, nobs = nobs)) + conf.upper <- array(dim = c(nexp = nexp, nobs = nobs)) + } - # dif1 + # dif for (i in 1:nobs) { - dif1[, , i] <- sapply(1:nexp, function(x) {exp[, x] - obs[, i]}) + dif[, , i] <- sapply(1:nexp, function(x) {exp[, x] - obs[, i]}) } + rms <- apply(dif^2, c(2, 3), mean, na.rm = TRUE)^0.5 #array(dim = c(_exp, nobs)) - # rms1 and eno1 - rms1 <- apply(dif1^2, c(2, 3), mean, na.rm = TRUE)^0.5 #array(dim = c(nexp, nobs)) - # rms2 and eno2 - rms2 <- array(colMeans(obs^2, na.rm = TRUE)^0.5, dim = c(nobs = nobs)) - rms2[which(abs(rms2) <= (max(abs(rms2), na.rm = TRUE) / 1000))] <- max(abs( - rms2), na.rm = TRUE) / 1000 - #rms2 above: [nobs] - rms2 <- array(rms2, dim = c(nobs = nobs, nexp = nexp)) - #rms2 above: [nobs, nexp] - rms2 <- Reorder(rms2, c(2, 1)) - #rms2 above: [nexp, nobs] + if (conf) { + #eno <- Eno(dif, 1) #count effective sample along sdate. dim = c(nexp, nobs) + eno <- Eno(dif, time_dim, ncores = ncores_input) #change to this line when Eno() is done - # use rms1 and rms2 to calculate rmsss - rmsss <- 1 - rms1/rms2 + # conf.lower + chi <- sapply(1:nobs, function(i) { + qchisq(confhigh, eno[, i] - 1) + }) + conf.lower <- (eno * rms ** 2 / chi) ** 0.5 - ## pval and conf - if (pval) { - eno1 <- Eno(dif1, time_dim, ncores = ncores_input) - eno2 <- Eno(obs, time_dim, ncores = ncores_input) - eno2 <- array(eno2, dim = c(nobs = nobs, nexp = nexp)) - eno2 <- Reorder(eno2, c(2, 1)) + # conf.upper + chi <- sapply(1:nobs, function(i) { + qchisq(conflow, eno[, i] - 1) + }) + conf.upper <- (eno * rms ** 2 / chi) ** 0.5 } - # pval - if (pval) { - - F.stat <- (eno2 * rms2^2 / (eno2- 1)) / ((eno1 * rms1^2 / (eno1- 1))) - tmp <- !is.na(eno1) & !is.na(eno2) & eno1 > 2 & eno2 > 2 - p_val <- 1 - pf(F.stat, eno1 - 1, eno2 - 1) - p_val[which(!tmp)] <- NA - - # change not significant rmsss to NA - rmsss[which(!tmp)] <- NA + ################################### + # Remove nexp and nobs if dat_dim = NULL + if (is.null(dat_dim)) { + dim(rms) <- NULL + dim(conf.lower) <- NULL + dim(conf.upper) <- NULL } - # output - if (pval) { - res <- list(rmsss = rmsss, p.val = p_val) + ################################### + if (conf) { + res <- list(rms = rms, conf.lower = conf.lower, conf.upper = conf.upper) } else { - res <- list(rmsss = rmsss) - } + res <- list(rms = rms) + } return(res) + } diff --git a/tests/testthat/test-RMS.R b/tests/testthat/test-RMS.R index f1083dd..65edfac 100644 --- a/tests/testthat/test-RMS.R +++ b/tests/testthat/test-RMS.R @@ -17,6 +17,13 @@ context("s2dv::RMS tests") set.seed(6) obs2 <- rnorm(10) + # dat3 + set.seed(1) + exp3 <- array(rnorm(120), dim = c(sdate = 5, ftime = 2, lon = 1, lat = 4)) + + set.seed(2) + obs3 <- array(rnorm(80), dim = c(sdate = 5, ftime = 2, lon = 1, lat = 4)) + ############################################## test_that("1. Input checks", { @@ -186,3 +193,46 @@ test_that("3. Output checks: dat2", { }) ############################################## +test_that("4. Output checks: dat3", { + + expect_equal( + dim(RMS(exp3, obs3, dat_dim = NULL)$rms), + c(ftime = 2, lon = 1, lat = 4) + ) + + suppressWarnings( + expect_equal( + RMS(exp3, obs3, dat_dim = NULL)$rms[1:6], + c(1.6458118, 0.8860392, 0.8261295, 1.1681939, 2.1693538, 1.3064454), + tolerance = 0.001 + ) +) +suppressWarnings( + expect_equal( + length(which(is.na(RMS(exp3, obs3, dat_dim = NULL)$conf.lower))), + 0 + ) +) +suppressWarnings( + expect_equal( + max(RMS(exp3, obs3, dat_dim = NULL)$conf.lower, na.rm = T), + 1.453144, + tolerance = 0.001 + ) +) +suppressWarnings( + expect_equal( + min(RMS(exp3, obs3, dat_dim = NULL, conf.lev = 0.99)$conf.upper, na.rm = TRUE), + 2.646274, + tolerance = 0.0001 + ) +) +suppressWarnings( + expect_equal( + length(RMS(exp3, obs3, dat_dim = NULL, conf = FALSE)), + 1 + ) +) +}) + +############################################## \ No newline at end of file diff --git a/tests/testthat/test-RMSSS.R b/tests/testthat/test-RMSSS.R index 91ef930..60b4672 100644 --- a/tests/testthat/test-RMSSS.R +++ b/tests/testthat/test-RMSSS.R @@ -25,6 +25,12 @@ context("s2dv::RMSSS tests") set.seed(6) obs3 <- rnorm(10) + # case 4 + set.seed(7) + exp4 <- array(rnorm(120), dim = c(sdate = 10, dat = 1, lon = 3, lat = 2)) + set.seed(8) + obs4 <- array(rnorm(60), dim = c(dat = 1, sdate = 10, lat = 2, lon = 3)) + ############################################## test_that("1. Input checks", { @@ -157,5 +163,26 @@ test_that("4. Output checks: case 3", { tolerance = 0.00001 ) +############################################## +test_that("4. Output checks: case 4", { + + expect_equal( + dim(RMSSS(exp4, obs4, dat_dim = NULL)$rmsss), + c(dat = 1, lon = 3, lat = 2) + ) + expect_equal( + mean(RMSSS(exp4, obs4, dat_dim = NULL)$rmsss), + -0.3114424, + tolerance = 0.00001 + ) + expect_equal( + range(RMSSS(exp4, obs4, dat_dim = NULL)$p.val), + c(0.3560534, 0.9192801), + tolerance = 0.00001 + ) + +}) + +############################################## }) -- GitLab From 9015838a870424b2e7d08129fab0fac7db232e52 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 12:52:14 +0200 Subject: [PATCH 034/140] Allow dat_dim NULL in UltimateBrier and added test --- R/UltimateBrier.R | 51 +++++++--- tests/testthat/test-UltimateBrier.R | 142 ++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+), 11 deletions(-) diff --git a/R/UltimateBrier.R b/R/UltimateBrier.R index a2c594e..18c8e5b 100644 --- a/R/UltimateBrier.R +++ b/R/UltimateBrier.R @@ -107,10 +107,10 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti stop("Parameter 'dat_dim' must be a character string or NULL.") } if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", - "Set it as NULL if there is no dataset dimension.") + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") } } + ## memb_dim if (!is.character(memb_dim) | length(memb_dim) > 1) { stop("Parameter 'memb_dim' must be a character string.") @@ -136,10 +136,8 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti name_obs <- sort(names(dim(obs))) name_exp <- name_exp[-which(name_exp == memb_dim)] name_obs <- name_obs[-which(name_obs == memb_dim)] - if (!is.null(dat_dim)) { name_exp <- name_exp[-which(name_exp == dat_dim)] name_obs <- name_obs[-which(name_obs == dat_dim)] - } if (any(name_exp != name_obs)) { stop(paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", "of all the dimensions except 'dat_dim' and 'memb_dim'.")) @@ -188,6 +186,7 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti target_dims = list(c(time_dim, dat_dim, memb_dim), c(time_dim, dat_dim, memb_dim)), fun = .UltimateBrier, + dat_dim = dat_dim, memb_dim = memb_dim, thr = thr, type = type, decomposition = decomposition, ncores = ncores)$output1 @@ -208,6 +207,7 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti target_dims = list(c(time_dim, dat_dim), c(time_dim, dat_dim)), fun = .UltimateBrier, + dat_dim = dat_dim, memb_dim = memb_dim, thr = thr, type = type, decomposition = decomposition, ncores = ncores) @@ -222,8 +222,8 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti return(res) } -.UltimateBrier <- function(exp, obs, thr = c(5/100, 95/100), type = 'BS', - decomposition = TRUE) { +.UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', thr = c(5/100, 95/100), + type = 'BS', decomposition = TRUE) { # If exp and obs are probablistics # exp: [sdate, nexp] # obs: [sdate, nobs] @@ -234,6 +234,10 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti #NOTE: 'thr' is used in 'FairEnsembleBSS' and 'FairEnsembleBS'. But if quantile = F and # thr is real value, does it work? if (type == 'FairEnsembleBSS') { + if (is.null(dat_dim)) { + obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'dataset') + exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'dataset') + } size_ens_ref <- prod(dim(obs)[c(1, 3)]) res <- array(dim = c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2]), @@ -250,6 +254,9 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti } } } + if (is.null(dat_dim)) { + dim(res) <- dim(res)[3:length(dim(res))] + } } else if (type == 'FairEnsembleBS') { #NOTE: The calculation in s2dverification::UltimateBrier is wrong. In the final stage, @@ -257,6 +264,10 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti # but the 3rd dim of result is 'bins' instead of decomposition. 'FairEnsembleBS' does # not have decomposition. # The calculation is fixed here. + if (is.null(dat_dim)) { + obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'dataset') + exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'dataset') + } res <- array(dim = c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2]), bin = length(thr) + 1)) @@ -269,10 +280,17 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti } } } + if (is.null(dat_dim)) { + dim(res) <- dim(res)[3:length(dim(res))] + } # tmp <- res[, , 1] - res[, , 2] + res[, , 3] # res <- array(tmp, dim = c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2]))) } else if (type == 'BS') { + if (is.null(dat_dim)) { + obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'dataset') + exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'dataset') + } comp <- array(dim = c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2]), comp = 3)) @@ -285,17 +303,28 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti } if (decomposition) { rel <- comp[, , 1] - dim(rel) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) res <- comp[, , 2] - dim(res) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) unc <- comp[, , 3] - dim(unc) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) bs <- rel - res + unc - dim(bs) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + if (is.null(dat_dim)) { + dim(rel) <- NULL + dim(res) <- NULL + dim(unc) <- NULL + dim(bs) <- NULL + } else { + dim(rel) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + dim(res) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + dim(unc) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + dim(bs) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + } res <- list(bs = bs, rel = rel, res = res, unc = unc) } else { bs <- comp[, , 1] - comp[, , 2] + comp[, , 3] - dim(bs) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + if (is.null(dat_dim)) { + dim(bs) <- NULL + } else { + dim(bs) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + } res <- list(bs = bs) } diff --git a/tests/testthat/test-UltimateBrier.R b/tests/testthat/test-UltimateBrier.R index 0eb0118..e31f807 100644 --- a/tests/testthat/test-UltimateBrier.R +++ b/tests/testthat/test-UltimateBrier.R @@ -7,6 +7,12 @@ exp1 <- array(rnorm(30), dim = c(dataset = 1, member = 3, sdate = 5, ftime = 2)) set.seed(2) obs1 <- array(round(rnorm(10)), dim = c(dataset = 1, sdate = 5, ftime = 2)) +# dat2 +set.seed(1) +exp2 <- array(rnorm(30), dim = c(member = 3, sdate = 5, ftime = 2)) +set.seed(2) +obs2 <- array(round(rnorm(10)), dim = c(sdate = 5, ftime = 2)) + ############################################## test_that("1. Input checks", { @@ -238,3 +244,139 @@ tolerance = 0.0001 }) +############################################## +test_that("3. Output checks: dat2", { + +# 'BS' +expect_equal( +is.list(UltimateBrier(exp2, obs2, dat_dim = NULL)), +TRUE +) +expect_equal( +names(UltimateBrier(exp2, obs2, dat_dim = NULL)), +c('bs', 'rel', 'res', 'unc') +) +expect_equal( +is.list(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE)), +FALSE +) +expect_equal( +dim(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE)), +c(bin = 3, ftime = 2) +) +expect_equal( +dim(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE, thr = c(0.25, 0.5, 0.75))), +c(bin = 4, ftime = 2) +) +expect_equal( +UltimateBrier(exp2, obs2, dat_dim = NULL)$bs, +UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE) +) +expect_equal( +as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL)$bs), +c(0.42222222, 0.44444444, 0.02222222, 0.48888889, 0.37777778, 0.02222222), +tolerance = 0.0001 +) +expect_equal( +as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL)$rel), +c(0.22222222, 0.31111111, 0.02222222, 0.28888889, 0.24444444, 0.02222222), +tolerance = 0.0001 +) +expect_equal( +as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL)$res), +c(0.0400000, 0.1066667, 0.0000000, 0.0400000, 0.1066667, 0.0000000), +tolerance = 0.0001 +) +expect_equal( +as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL)$unc), +c(0.24, 0.24, 0.00, 0.24, 0.24, 0.00), +tolerance = 0.0001 +) + +# 'BSS' +expect_equal( +dim(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'BSS')), +c(bin = 3, ftime = 2) +) +expect_equal( +as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'BSS')), +c(-0.7592593, -0.8518519, -Inf, -1.0370370, -0.5740741, -Inf), +tolerance = 0.0001 +) + +# 'FairStartDatesBS' +expect_equal( +is.list(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')), +TRUE +) +expect_equal( +names(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')), +c('bs', 'rel', 'res', 'unc') +) +expect_equal( +is.list(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE, type = 'FairStartDatesBS')), +FALSE +) +expect_equal( +dim(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE, type = 'FairStartDatesBS')), +c(bin = 3, ftime = 2) +) +expect_equal( +UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$bs, +UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE, type = 'FairStartDatesBS') +) +expect_equal( +as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$bs), +c(0.42222222, 0.44444444, 0.02222222, 0.48888889, 0.37777778, 0.02222222), +tolerance = 0.0001 +) +expect_equal( +as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$rel), +c(0.22222222, 0.31111111, 0.02222222, 0.28888889, 0.24444444, 0.02222222), +tolerance = 0.0001 +) +expect_equal( +as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$res), +c(0.0400000, 0.1066667, 0.0000000, 0.0400000, 0.1066667, 0.0000000), +tolerance = 0.0001 +) +expect_equal( +as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$unc), +c(0.24, 0.24, 0.00, 0.24, 0.24, 0.00), +tolerance = 0.0001 +) + +# 'FairStartDatesBSS' +expect_equal( +dim(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBSS')), +c(bin = 3, ftime = 2) +) +expect_equal( +as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBSS')), +c(-0.7592593, -0.8518519, -Inf, -1.0370370, -0.5740741, -Inf), +tolerance = 0.0001 +) +# 'FairEnsembleBS' +expect_equal( +dim(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairEnsembleBS')), +c(bin = 3, ftime = 2) +) +expect_equal( +as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairEnsembleBS')), +c(0.1333333, 0.2000000, 0.2000000, 0.1333333, 0.4000000, 0.2000000), +tolerance = 0.0001 +) +# 'FairEnsembleBSS' +expect_equal( +dim(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairEnsembleBSS')), +c(bin = 3, ftime = 2) +) +expect_equal( +as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairEnsembleBSS')), +c(-0.1111111, -0.6666667, -0.6666667, 0.2592593, -1.2222222, -0.6666667), +tolerance = 0.0001 +) + +}) + + -- GitLab From 966701a0ae263fe90836b02ab38350186fadfe22 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 13:17:41 +0200 Subject: [PATCH 035/140] Revert commits --- R/RMS.R | 56 ++---- R/RMSSS.R | 275 +++++++++++----------------- R/UltimateBrier.R | 62 ++----- tests/testthat/test-RMS.R | 52 +----- tests/testthat/test-RMSSS.R | 29 +-- tests/testthat/test-UltimateBrier.R | 146 +-------------- 6 files changed, 144 insertions(+), 476 deletions(-) diff --git a/R/RMS.R b/R/RMS.R index 7a38a72..b3c8ad4 100644 --- a/R/RMS.R +++ b/R/RMS.R @@ -21,8 +21,7 @@ #'@param time_dim A character string indicating the name of dimension along #' which the correlations are computed. The default value is 'sdate'. #'@param dat_dim A character string indicating the name of member (nobs/nexp) -#' dimension. The default value is 'dataset'. If there is no dataset -#' dimension, set NULL. +#' dimension. The default value is 'dataset'. #'@param comp_dim A character string indicating the name of dimension along which #' obs is taken into account only if it is complete. The default value #' is NULL. @@ -108,14 +107,11 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.null(dat_dim)) { - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string or NULL.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", - "Set it as NULL if there is no dataset dimension.") - } + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") } ## comp_dim if (!is.null(comp_dim)) { @@ -155,13 +151,11 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) - if (!is.null(dat_dim)) { - name_exp <- name_exp[-which(name_exp == dat_dim)] - name_obs <- name_obs[-which(name_obs == dat_dim)] - } + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension except 'dat_dim'.")) + "all dimension expect 'dat_dim'.")) } if (dim(exp)[time_dim] < 2) { stop("The length of time_dim must be at least 2 to compute RMS.") @@ -202,22 +196,12 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } .RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', - conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { - if (is.null(dat_dim)) { - # exp: [sdate] - # obs: [sdate] - nexp <- 1 - nobs <- 1 - ini_dims <- dim(exp) - dim(exp) <- c(ini_dims, dat_dim = 1) - dim(obs) <- c(ini_dims, dat_dim = 1) - } else { - # exp: [sdate, dat_exp] - # obs: [sdate, dat_obs] - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) - } - + conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { + + # exp: [sdate, dat_exp] + # obs: [sdate, dat_obs] + nexp <- as.numeric(dim(exp)[2]) + nobs <- as.numeric(dim(obs)[2]) nsdate <- as.numeric(dim(exp)[1]) dif <- array(dim = c(sdate = nsdate, nexp = nexp, nobs = nobs)) @@ -252,16 +236,6 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', conf.upper <- (eno * rms ** 2 / chi) ** 0.5 } - ################################### - # Remove nexp and nobs if dat_dim = NULL - if (is.null(dat_dim)) { - dim(rms) <- NULL - dim(conf.lower) <- NULL - dim(conf.upper) <- NULL - } - - ################################### - if (conf) { res <- list(rms = rms, conf.lower = conf.lower, conf.upper = conf.upper) } else { diff --git a/R/RMSSS.R b/R/RMSSS.R index 7a38a72..5fa9659 100644 --- a/R/RMSSS.R +++ b/R/RMSSS.R @@ -1,75 +1,63 @@ -#'Compute root mean square error +#'Compute root mean square error skill score #' -#'Compute the root mean square error for an array of forecasts and an array of -#'observations. The RMSEs are computed along time_dim, the dimension which -#'corresponds to the startdate dimension. If comp_dim is given, the RMSEs are -#'computed only if obs along the comp_dim dimension are complete between -#'limits[1] and limits[2], i.e. there are no NAs between limits[1] and -#'limits[2]. This option can be activated if the user wishes to account only -#'for the forecasts for which the corresponding observations are available at -#'all leadtimes.\cr -#'The confidence interval is computed by the chi2 distribution.\cr +#'Compute the root mean square error skill score (RMSSS) between an array of +#'forecast 'exp' and an array of observation 'obs'. The two arrays should +#'have the same dimensions except along dat_dim, where the length can be +#'different, with the number of experiments/models (nexp) and the number of +#'observational datasets (nobs).\cr +#'RMSSS computes the root mean square error skill score of each jexp in 1:nexp +#'against each jobs in 1:nobs which gives nexp * nobs RMSSS for each other +#'grid point of the array.\cr +#'The RMSSS are computed along the time_dim dimension which should corresponds +#'to the startdate dimension.\cr +#'The p-value is optionally provided by an one-sided Fisher test.\cr #' -#'@param exp A named numeric array of experimental data, with at least two -#' dimensions 'time_dim' and 'dat_dim'. It can also be a vector with the +#'@param exp A named numeric array of experimental data which contains at least +#' two dimensions for dat_dim and time_dim. It can also be a vector with the #' same length as 'obs', then the vector will automatically be 'time_dim' and #' 'dat_dim' will be 1. -#'@param obs A named numeric array of observational data, same dimensions as -#' parameter 'exp' except along dat_dim. It can also be a vector with the same -#' length as 'exp', then the vector will automatically be 'time_dim' and -#' 'dat_dim' will be 1. +#'@param obs A named numeric array of observational data which contains at least +#' two dimensions for dat_dim and time_dim. The dimensions should be the same +#' as paramter 'exp' except the length of 'dat_dim' dimension. The order of +#' dimension can be different. It can also be a vector with the same length as +#' 'exp', then the vector will automatically be 'time_dim' and 'dat_dim' will +#' be 1. +#'@param dat_dim A character string indicating the name of dataset (nobs/nexp) +#' dimension. The default value is 'dataset'. #'@param time_dim A character string indicating the name of dimension along -#' which the correlations are computed. The default value is 'sdate'. -#'@param dat_dim A character string indicating the name of member (nobs/nexp) -#' dimension. The default value is 'dataset'. If there is no dataset -#' dimension, set NULL. -#'@param comp_dim A character string indicating the name of dimension along which -#' obs is taken into account only if it is complete. The default value -#' is NULL. -#'@param limits A vector of two integers indicating the range along comp_dim to -#' be completed. The default value is c(1, length(comp_dim dimension)). -#'@param conf A logical value indicating whether to retrieve the confidence -#' intervals or not. The default value is TRUE. -#'@param conf.lev A numeric indicating the confidence level for the -#' regression computation. The default value is 0.95. +#' which the RMSSS are computed. The default value is 'sdate'. +#'@param pval A logical value indicating whether to compute or not the p-value +#' of the test Ho: RMSSS = 0. If pval = TRUE, the insignificant RMSSS will +#' return NA. The default value is TRUE. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' -#'@return +#'@return #'A list containing the numeric arrays with dimension:\cr #' c(nexp, nobs, all other dimensions of exp except time_dim).\cr #'nexp is the number of experiment (i.e., dat_dim in exp), and nobs is the #'number of observation (i.e., dat_dim in obs).\cr -#'\item{$rms}{ -#' The root mean square error. -#'} -#'\item{$conf.lower}{ -#' The lower confidence interval. Only present if \code{conf = TRUE}. +#'\item{$rmsss}{ +#' The root mean square error skill score. #'} -#'\item{$conf.upper}{ -#' The upper confidence interval. Only present if \code{conf = TRUE}. +#'\item{$p.val}{ +#' The p-value. Only present if \code{pval = TRUE}. #'} #' #'@examples -#'# Load sample data as in Load() example: -#' set.seed(1) -#' exp1 <- array(rnorm(120), dim = c(dataset = 3, sdate = 5, ftime = 2, lon = 1, lat = 4)) -#' set.seed(2) -#' obs1 <- array(rnorm(80), dim = c(dataset = 2, sdate = 5, ftime = 2, lon = 1, lat = 4)) -#' set.seed(2) -#' na <- floor(runif(10, min = 1, max = 80)) -#' obs1[na] <- NA -#' res <- RMS(exp1, obs1, comp_dim = 'ftime') -#' # Renew example when Ano and Smoothing are ready +#' set.seed(1) +#' exp <- array(rnorm(30), dim = c(dataset = 2, time = 3, memb = 5)) +#' set.seed(2) +#' obs <- array(rnorm(15), dim = c(time = 3, memb = 5, dataset = 1)) +#' res <- RMSSS(exp, obs, time_dim = 'time') #' -#'@rdname RMS +#'@rdname RMSSS #'@import multiApply -#'@importFrom ClimProjDiags Subset -#'@importFrom stats qchisq +#'@importFrom stats pf #'@export -RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', - comp_dim = NULL, limits = NULL, - conf = TRUE, conf.lev = 0.95, ncores = NULL) { +RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', + pval = TRUE, ncores = NULL) { + # Check inputs ## exp and obs (1) if (is.null(exp) | is.null(obs)) { @@ -98,7 +86,7 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } if(!all(names(dim(exp)) %in% names(dim(obs))) | !all(names(dim(obs)) %in% names(dim(exp)))) { - stop("Parameter 'exp' and 'obs' must have same dimension name") + stop("Parameter 'exp' and 'obs' must have same dimension name.") } ## time_dim if (!is.character(time_dim) | length(time_dim) > 1) { @@ -108,42 +96,15 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.null(dat_dim)) { - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string or NULL.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", - "Set it as NULL if there is no dataset dimension.") - } + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string.") } - ## comp_dim - if (!is.null(comp_dim)) { - if (!is.character(comp_dim) | length(comp_dim) > 1) { - stop("Parameter 'comp_dim' must be a character string.") - } - if (!comp_dim %in% names(dim(exp)) | !comp_dim %in% names(dim(obs))) { - stop("Parameter 'comp_dim' is not found in 'exp' or 'obs' dimension.") - } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") } - ## limits - if (!is.null(limits)) { - if (is.null(comp_dim)) { - stop("Paramter 'comp_dim' cannot be NULL if 'limits' is assigned.") - } - if (!is.numeric(limits) | any(limits %% 1 != 0) | any(limits < 0) | - length(limits) != 2 | any(limits > dim(exp)[comp_dim])) { - stop(paste0("Parameter 'limits' must be a vector of two positive ", - "integers smaller than the length of paramter 'comp_dim'.")) - } - } - ## conf - if (!is.logical(conf) | length(conf) > 1) { - stop("Parameter 'conf' must be one logical value.") - } - ## conf.lev - if (!is.numeric(conf.lev) | conf.lev < 0 | conf.lev > 1 | length(conf.lev) > 1) { - stop("Parameter 'conf.lev' must be a numeric number between 0 and 1.") + ## pval + if (!is.logical(pval) | length(pval) > 1) { + stop("Parameter 'pval' must be one logical value.") } ## ncores if (!is.null(ncores)) { @@ -155,18 +116,16 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) - if (!is.null(dat_dim)) { - name_exp <- name_exp[-which(name_exp == dat_dim)] - name_obs <- name_obs[-which(name_obs == dat_dim)] - } + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension except 'dat_dim'.")) + "all dimension expect 'dat_dim'.")) } - if (dim(exp)[time_dim] < 2) { - stop("The length of time_dim must be at least 2 to compute RMS.") + if (dim(exp)[time_dim] <= 2) { + stop("The length of time_dim must be more than 2 to compute RMSSS.") } - + ############################### # Sort dimension @@ -177,97 +136,85 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ############################### - # Calculate RMS - - # Remove data along comp_dim dim if there is at least one NA between limits - if (!is.null(comp_dim)) { - if (is.null(limits)) { - limits <- c(1, dim(obs)[comp_dim]) - } - pos <- which(names(dim(obs)) == comp_dim) - obs_sub <- Subset(obs, pos, list(limits[1]:limits[2])) - outrows <- is.na(MeanDims(obs_sub, pos, na.rm = FALSE)) - outrows <- InsertDim(outrows, pos, dim(obs)[comp_dim]) - obs[which(outrows)] <- NA - } + # Calculate RMSSS res <- Apply(list(exp, obs), target_dims = list(c(time_dim, dat_dim), c(time_dim, dat_dim)), - fun = .RMS, + fun = .RMSSS, time_dim = time_dim, dat_dim = dat_dim, - conf = conf, conf.lev = conf.lev, ncores_input = ncores, + pval = pval, ncores_input = ncores, ncores = ncores) + return(res) } -.RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', - conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { - if (is.null(dat_dim)) { - # exp: [sdate] - # obs: [sdate] - nexp <- 1 - nobs <- 1 - ini_dims <- dim(exp) - dim(exp) <- c(ini_dims, dat_dim = 1) - dim(obs) <- c(ini_dims, dat_dim = 1) - } else { - # exp: [sdate, dat_exp] - # obs: [sdate, dat_obs] - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) - } - +.RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', pval = TRUE, + ncores_input = NULL) { + # exp: [sdate, dat_exp] + # obs: [sdate, dat_obs] + nexp <- as.numeric(dim(exp)[2]) + nobs <- as.numeric(dim(obs)[2]) nsdate <- as.numeric(dim(exp)[1]) + + p_val <- array(dim = c(nexp = nexp, nobs = nobs)) + dif1 <- array(dim = c(nsdate, nexp, nobs)) + names(dim(dif1)) <- c(time_dim, 'nexp', 'nobs') - dif <- array(dim = c(sdate = nsdate, nexp = nexp, nobs = nobs)) - chi <- array(dim = c(nexp = nexp, nobs = nobs)) - if (conf) { - conflow <- (1 - conf.lev) / 2 - confhigh <- 1 - conflow - conf.lower <- array(dim = c(nexp = nexp, nobs = nobs)) - conf.upper <- array(dim = c(nexp = nexp, nobs = nobs)) - } +# if (conf) { +# conflow <- (1 - conf.lev) / 2 +# confhigh <- 1 - conflow +# conf_low <- array(dim = c(nexp = nexp, nobs = nobs)) +# conf_high <- array(dim = c(nexp = nexp, nobs = nobs)) +# } - # dif + # dif1 for (i in 1:nobs) { - dif[, , i] <- sapply(1:nexp, function(x) {exp[, x] - obs[, i]}) + dif1[, , i] <- sapply(1:nexp, function(x) {exp[, x] - obs[, i]}) } - rms <- apply(dif^2, c(2, 3), mean, na.rm = TRUE)^0.5 #array(dim = c(_exp, nobs)) - if (conf) { - #eno <- Eno(dif, 1) #count effective sample along sdate. dim = c(nexp, nobs) - eno <- Eno(dif, time_dim, ncores = ncores_input) #change to this line when Eno() is done + # rms1 and eno1 + rms1 <- apply(dif1^2, c(2, 3), mean, na.rm = TRUE)^0.5 #array(dim = c(nexp, nobs)) + # rms2 and eno2 + rms2 <- array(colMeans(obs^2, na.rm = TRUE)^0.5, dim = c(nobs = nobs)) + rms2[which(abs(rms2) <= (max(abs(rms2), na.rm = TRUE) / 1000))] <- max(abs( + rms2), na.rm = TRUE) / 1000 + #rms2 above: [nobs] + rms2 <- array(rms2, dim = c(nobs = nobs, nexp = nexp)) + #rms2 above: [nobs, nexp] + rms2 <- Reorder(rms2, c(2, 1)) + #rms2 above: [nexp, nobs] - # conf.lower - chi <- sapply(1:nobs, function(i) { - qchisq(confhigh, eno[, i] - 1) - }) - conf.lower <- (eno * rms ** 2 / chi) ** 0.5 + # use rms1 and rms2 to calculate rmsss + rmsss <- 1 - rms1/rms2 - # conf.upper - chi <- sapply(1:nobs, function(i) { - qchisq(conflow, eno[, i] - 1) - }) - conf.upper <- (eno * rms ** 2 / chi) ** 0.5 + ## pval and conf + if (pval) { + eno1 <- Eno(dif1, time_dim, ncores = ncores_input) + eno2 <- Eno(obs, time_dim, ncores = ncores_input) + eno2 <- array(eno2, dim = c(nobs = nobs, nexp = nexp)) + eno2 <- Reorder(eno2, c(2, 1)) } - ################################### - # Remove nexp and nobs if dat_dim = NULL - if (is.null(dat_dim)) { - dim(rms) <- NULL - dim(conf.lower) <- NULL - dim(conf.upper) <- NULL + # pval + if (pval) { + + F.stat <- (eno2 * rms2^2 / (eno2- 1)) / ((eno1 * rms1^2 / (eno1- 1))) + tmp <- !is.na(eno1) & !is.na(eno2) & eno1 > 2 & eno2 > 2 + p_val <- 1 - pf(F.stat, eno1 - 1, eno2 - 1) + p_val[which(!tmp)] <- NA + + # change not significant rmsss to NA + rmsss[which(!tmp)] <- NA } - ################################### + # output + if (pval) { + res <- list(rmsss = rmsss, p.val = p_val) - if (conf) { - res <- list(rms = rms, conf.lower = conf.lower, conf.upper = conf.upper) } else { - res <- list(rms = rms) - } + res <- list(rmsss = rmsss) + } return(res) - } diff --git a/R/UltimateBrier.R b/R/UltimateBrier.R index 18c8e5b..aeaddcd 100644 --- a/R/UltimateBrier.R +++ b/R/UltimateBrier.R @@ -102,15 +102,12 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti stop("Parameter 'exp' and 'obs' must have dimension names.") } ## dat_dim - if (!is.null(dat_dim)) { - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string or NULL.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") - } + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") } - ## memb_dim if (!is.character(memb_dim) | length(memb_dim) > 1) { stop("Parameter 'memb_dim' must be a character string.") @@ -140,11 +137,11 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti name_obs <- name_obs[-which(name_obs == dat_dim)] if (any(name_exp != name_obs)) { stop(paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions except 'dat_dim' and 'memb_dim'.")) + "of all the dimensions expect 'dat_dim' and 'memb_dim'.")) } if (!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions except 'dat_dim' and 'memb_dim'.")) + "of all the dimensions expect 'dat_dim' and 'memb_dim'.")) } ## quantile if (!is.logical(quantile) | length(quantile) > 1) { @@ -186,7 +183,6 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti target_dims = list(c(time_dim, dat_dim, memb_dim), c(time_dim, dat_dim, memb_dim)), fun = .UltimateBrier, - dat_dim = dat_dim, memb_dim = memb_dim, thr = thr, type = type, decomposition = decomposition, ncores = ncores)$output1 @@ -207,7 +203,6 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti target_dims = list(c(time_dim, dat_dim), c(time_dim, dat_dim)), fun = .UltimateBrier, - dat_dim = dat_dim, memb_dim = memb_dim, thr = thr, type = type, decomposition = decomposition, ncores = ncores) @@ -222,8 +217,8 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti return(res) } -.UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', thr = c(5/100, 95/100), - type = 'BS', decomposition = TRUE) { +.UltimateBrier <- function(exp, obs, thr = c(5/100, 95/100), type = 'BS', + decomposition = TRUE) { # If exp and obs are probablistics # exp: [sdate, nexp] # obs: [sdate, nobs] @@ -234,10 +229,6 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti #NOTE: 'thr' is used in 'FairEnsembleBSS' and 'FairEnsembleBS'. But if quantile = F and # thr is real value, does it work? if (type == 'FairEnsembleBSS') { - if (is.null(dat_dim)) { - obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'dataset') - exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'dataset') - } size_ens_ref <- prod(dim(obs)[c(1, 3)]) res <- array(dim = c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2]), @@ -254,9 +245,6 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti } } } - if (is.null(dat_dim)) { - dim(res) <- dim(res)[3:length(dim(res))] - } } else if (type == 'FairEnsembleBS') { #NOTE: The calculation in s2dverification::UltimateBrier is wrong. In the final stage, @@ -264,10 +252,6 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti # but the 3rd dim of result is 'bins' instead of decomposition. 'FairEnsembleBS' does # not have decomposition. # The calculation is fixed here. - if (is.null(dat_dim)) { - obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'dataset') - exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'dataset') - } res <- array(dim = c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2]), bin = length(thr) + 1)) @@ -280,17 +264,10 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti } } } - if (is.null(dat_dim)) { - dim(res) <- dim(res)[3:length(dim(res))] - } # tmp <- res[, , 1] - res[, , 2] + res[, , 3] # res <- array(tmp, dim = c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2]))) } else if (type == 'BS') { - if (is.null(dat_dim)) { - obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'dataset') - exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'dataset') - } comp <- array(dim = c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2]), comp = 3)) @@ -303,28 +280,17 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti } if (decomposition) { rel <- comp[, , 1] + dim(rel) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) res <- comp[, , 2] + dim(res) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) unc <- comp[, , 3] + dim(unc) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) bs <- rel - res + unc - if (is.null(dat_dim)) { - dim(rel) <- NULL - dim(res) <- NULL - dim(unc) <- NULL - dim(bs) <- NULL - } else { - dim(rel) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) - dim(res) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) - dim(unc) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) - dim(bs) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) - } + dim(bs) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) res <- list(bs = bs, rel = rel, res = res, unc = unc) } else { bs <- comp[, , 1] - comp[, , 2] + comp[, , 3] - if (is.null(dat_dim)) { - dim(bs) <- NULL - } else { - dim(bs) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) - } + dim(bs) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) res <- list(bs = bs) } diff --git a/tests/testthat/test-RMS.R b/tests/testthat/test-RMS.R index 65edfac..6e18bee 100644 --- a/tests/testthat/test-RMS.R +++ b/tests/testthat/test-RMS.R @@ -17,13 +17,6 @@ context("s2dv::RMS tests") set.seed(6) obs2 <- rnorm(10) - # dat3 - set.seed(1) - exp3 <- array(rnorm(120), dim = c(sdate = 5, ftime = 2, lon = 1, lat = 4)) - - set.seed(2) - obs3 <- array(rnorm(80), dim = c(sdate = 5, ftime = 2, lon = 1, lat = 4)) - ############################################## test_that("1. Input checks", { @@ -96,7 +89,7 @@ test_that("1. Input checks", { expect_error( RMS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), obs = array(1:4, dim = c(a = 1, sdate = 2, dataset = 2))), - "Parameter 'exp' and 'obs' must have same length of all dimension except 'dat_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimension expect 'dat_dim'." ) expect_error( RMS(exp = array(1:5, dim = c(sdate = 1, dataset = 5, a = 1)), @@ -193,46 +186,3 @@ test_that("3. Output checks: dat2", { }) ############################################## -test_that("4. Output checks: dat3", { - - expect_equal( - dim(RMS(exp3, obs3, dat_dim = NULL)$rms), - c(ftime = 2, lon = 1, lat = 4) - ) - - suppressWarnings( - expect_equal( - RMS(exp3, obs3, dat_dim = NULL)$rms[1:6], - c(1.6458118, 0.8860392, 0.8261295, 1.1681939, 2.1693538, 1.3064454), - tolerance = 0.001 - ) -) -suppressWarnings( - expect_equal( - length(which(is.na(RMS(exp3, obs3, dat_dim = NULL)$conf.lower))), - 0 - ) -) -suppressWarnings( - expect_equal( - max(RMS(exp3, obs3, dat_dim = NULL)$conf.lower, na.rm = T), - 1.453144, - tolerance = 0.001 - ) -) -suppressWarnings( - expect_equal( - min(RMS(exp3, obs3, dat_dim = NULL, conf.lev = 0.99)$conf.upper, na.rm = TRUE), - 2.646274, - tolerance = 0.0001 - ) -) -suppressWarnings( - expect_equal( - length(RMS(exp3, obs3, dat_dim = NULL, conf = FALSE)), - 1 - ) -) -}) - -############################################## \ No newline at end of file diff --git a/tests/testthat/test-RMSSS.R b/tests/testthat/test-RMSSS.R index 60b4672..7d2a16d 100644 --- a/tests/testthat/test-RMSSS.R +++ b/tests/testthat/test-RMSSS.R @@ -25,12 +25,6 @@ context("s2dv::RMSSS tests") set.seed(6) obs3 <- rnorm(10) - # case 4 - set.seed(7) - exp4 <- array(rnorm(120), dim = c(sdate = 10, dat = 1, lon = 3, lat = 2)) - set.seed(8) - obs4 <- array(rnorm(60), dim = c(dat = 1, sdate = 10, lat = 2, lon = 3)) - ############################################## test_that("1. Input checks", { @@ -83,7 +77,7 @@ test_that("1. Input checks", { expect_error( RMSSS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), obs = array(1:4, dim = c(a = 1, sdate = 2, dataset = 2))), - "Parameter 'exp' and 'obs' must have same length of all dimension except 'dat_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimension expect 'dat_dim'." ) expect_error( RMSSS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), @@ -163,26 +157,5 @@ test_that("4. Output checks: case 3", { tolerance = 0.00001 ) -############################################## -test_that("4. Output checks: case 4", { - - expect_equal( - dim(RMSSS(exp4, obs4, dat_dim = NULL)$rmsss), - c(dat = 1, lon = 3, lat = 2) - ) - expect_equal( - mean(RMSSS(exp4, obs4, dat_dim = NULL)$rmsss), - -0.3114424, - tolerance = 0.00001 - ) - expect_equal( - range(RMSSS(exp4, obs4, dat_dim = NULL)$p.val), - c(0.3560534, 0.9192801), - tolerance = 0.00001 - ) - -}) - -############################################## }) diff --git a/tests/testthat/test-UltimateBrier.R b/tests/testthat/test-UltimateBrier.R index e31f807..654aad0 100644 --- a/tests/testthat/test-UltimateBrier.R +++ b/tests/testthat/test-UltimateBrier.R @@ -7,12 +7,6 @@ exp1 <- array(rnorm(30), dim = c(dataset = 1, member = 3, sdate = 5, ftime = 2)) set.seed(2) obs1 <- array(round(rnorm(10)), dim = c(dataset = 1, sdate = 5, ftime = 2)) -# dat2 -set.seed(1) -exp2 <- array(rnorm(30), dim = c(member = 3, sdate = 5, ftime = 2)) -set.seed(2) -obs2 <- array(round(rnorm(10)), dim = c(sdate = 5, ftime = 2)) - ############################################## test_that("1. Input checks", { @@ -64,12 +58,12 @@ test_that("1. Input checks", { expect_error( UltimateBrier(exp1, array(1:10, dim = c(dataset = 1, sdate = 6, ftime = 2))), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions except 'dat_dim' and 'memb_dim'.") + "of all the dimensions expect 'dat_dim' and 'memb_dim'.") ) expect_error( UltimateBrier(exp1, array(1:10, dim = c(dataset = 1, sdate = 5, time = 2))), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions except 'dat_dim' and 'memb_dim'.") + "of all the dimensions expect 'dat_dim' and 'memb_dim'.") ) # quantile expect_error( @@ -244,139 +238,3 @@ tolerance = 0.0001 }) -############################################## -test_that("3. Output checks: dat2", { - -# 'BS' -expect_equal( -is.list(UltimateBrier(exp2, obs2, dat_dim = NULL)), -TRUE -) -expect_equal( -names(UltimateBrier(exp2, obs2, dat_dim = NULL)), -c('bs', 'rel', 'res', 'unc') -) -expect_equal( -is.list(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE)), -FALSE -) -expect_equal( -dim(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE)), -c(bin = 3, ftime = 2) -) -expect_equal( -dim(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE, thr = c(0.25, 0.5, 0.75))), -c(bin = 4, ftime = 2) -) -expect_equal( -UltimateBrier(exp2, obs2, dat_dim = NULL)$bs, -UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE) -) -expect_equal( -as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL)$bs), -c(0.42222222, 0.44444444, 0.02222222, 0.48888889, 0.37777778, 0.02222222), -tolerance = 0.0001 -) -expect_equal( -as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL)$rel), -c(0.22222222, 0.31111111, 0.02222222, 0.28888889, 0.24444444, 0.02222222), -tolerance = 0.0001 -) -expect_equal( -as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL)$res), -c(0.0400000, 0.1066667, 0.0000000, 0.0400000, 0.1066667, 0.0000000), -tolerance = 0.0001 -) -expect_equal( -as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL)$unc), -c(0.24, 0.24, 0.00, 0.24, 0.24, 0.00), -tolerance = 0.0001 -) - -# 'BSS' -expect_equal( -dim(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'BSS')), -c(bin = 3, ftime = 2) -) -expect_equal( -as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'BSS')), -c(-0.7592593, -0.8518519, -Inf, -1.0370370, -0.5740741, -Inf), -tolerance = 0.0001 -) - -# 'FairStartDatesBS' -expect_equal( -is.list(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')), -TRUE -) -expect_equal( -names(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')), -c('bs', 'rel', 'res', 'unc') -) -expect_equal( -is.list(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE, type = 'FairStartDatesBS')), -FALSE -) -expect_equal( -dim(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE, type = 'FairStartDatesBS')), -c(bin = 3, ftime = 2) -) -expect_equal( -UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$bs, -UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE, type = 'FairStartDatesBS') -) -expect_equal( -as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$bs), -c(0.42222222, 0.44444444, 0.02222222, 0.48888889, 0.37777778, 0.02222222), -tolerance = 0.0001 -) -expect_equal( -as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$rel), -c(0.22222222, 0.31111111, 0.02222222, 0.28888889, 0.24444444, 0.02222222), -tolerance = 0.0001 -) -expect_equal( -as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$res), -c(0.0400000, 0.1066667, 0.0000000, 0.0400000, 0.1066667, 0.0000000), -tolerance = 0.0001 -) -expect_equal( -as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$unc), -c(0.24, 0.24, 0.00, 0.24, 0.24, 0.00), -tolerance = 0.0001 -) - -# 'FairStartDatesBSS' -expect_equal( -dim(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBSS')), -c(bin = 3, ftime = 2) -) -expect_equal( -as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBSS')), -c(-0.7592593, -0.8518519, -Inf, -1.0370370, -0.5740741, -Inf), -tolerance = 0.0001 -) -# 'FairEnsembleBS' -expect_equal( -dim(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairEnsembleBS')), -c(bin = 3, ftime = 2) -) -expect_equal( -as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairEnsembleBS')), -c(0.1333333, 0.2000000, 0.2000000, 0.1333333, 0.4000000, 0.2000000), -tolerance = 0.0001 -) -# 'FairEnsembleBSS' -expect_equal( -dim(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairEnsembleBSS')), -c(bin = 3, ftime = 2) -) -expect_equal( -as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairEnsembleBSS')), -c(-0.1111111, -0.6666667, -0.6666667, 0.2592593, -1.2222222, -0.6666667), -tolerance = 0.0001 -) - -}) - - -- GitLab From 85c975bd58c29fcf45ff7367b7ff03d97a146e87 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 14:47:15 +0200 Subject: [PATCH 036/140] Allow dat_dim NULL in RMS() --- R/RMS.R | 56 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/R/RMS.R b/R/RMS.R index b3c8ad4..7a38a72 100644 --- a/R/RMS.R +++ b/R/RMS.R @@ -21,7 +21,8 @@ #'@param time_dim A character string indicating the name of dimension along #' which the correlations are computed. The default value is 'sdate'. #'@param dat_dim A character string indicating the name of member (nobs/nexp) -#' dimension. The default value is 'dataset'. +#' dimension. The default value is 'dataset'. If there is no dataset +#' dimension, set NULL. #'@param comp_dim A character string indicating the name of dimension along which #' obs is taken into account only if it is complete. The default value #' is NULL. @@ -107,11 +108,14 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string or NULL.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + "Set it as NULL if there is no dataset dimension.") + } } ## comp_dim if (!is.null(comp_dim)) { @@ -151,11 +155,13 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) - name_exp <- name_exp[-which(name_exp == dat_dim)] - name_obs <- name_obs[-which(name_obs == dat_dim)] + if (!is.null(dat_dim)) { + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] + } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension expect 'dat_dim'.")) + "all dimension except 'dat_dim'.")) } if (dim(exp)[time_dim] < 2) { stop("The length of time_dim must be at least 2 to compute RMS.") @@ -196,12 +202,22 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } .RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', - conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { - - # exp: [sdate, dat_exp] - # obs: [sdate, dat_obs] - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) + conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { + if (is.null(dat_dim)) { + # exp: [sdate] + # obs: [sdate] + nexp <- 1 + nobs <- 1 + ini_dims <- dim(exp) + dim(exp) <- c(ini_dims, dat_dim = 1) + dim(obs) <- c(ini_dims, dat_dim = 1) + } else { + # exp: [sdate, dat_exp] + # obs: [sdate, dat_obs] + nexp <- as.numeric(dim(exp)[2]) + nobs <- as.numeric(dim(obs)[2]) + } + nsdate <- as.numeric(dim(exp)[1]) dif <- array(dim = c(sdate = nsdate, nexp = nexp, nobs = nobs)) @@ -236,6 +252,16 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', conf.upper <- (eno * rms ** 2 / chi) ** 0.5 } + ################################### + # Remove nexp and nobs if dat_dim = NULL + if (is.null(dat_dim)) { + dim(rms) <- NULL + dim(conf.lower) <- NULL + dim(conf.upper) <- NULL + } + + ################################### + if (conf) { res <- list(rms = rms, conf.lower = conf.lower, conf.upper = conf.upper) } else { -- GitLab From 4e39b3383a715464cc2e680fdd5bb6adec5587c5 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 15:02:02 +0200 Subject: [PATCH 037/140] revert commit --- R/RMS.R | 56 +++++++++++++++----------------------------------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/R/RMS.R b/R/RMS.R index 7a38a72..b3c8ad4 100644 --- a/R/RMS.R +++ b/R/RMS.R @@ -21,8 +21,7 @@ #'@param time_dim A character string indicating the name of dimension along #' which the correlations are computed. The default value is 'sdate'. #'@param dat_dim A character string indicating the name of member (nobs/nexp) -#' dimension. The default value is 'dataset'. If there is no dataset -#' dimension, set NULL. +#' dimension. The default value is 'dataset'. #'@param comp_dim A character string indicating the name of dimension along which #' obs is taken into account only if it is complete. The default value #' is NULL. @@ -108,14 +107,11 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.null(dat_dim)) { - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string or NULL.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", - "Set it as NULL if there is no dataset dimension.") - } + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") } ## comp_dim if (!is.null(comp_dim)) { @@ -155,13 +151,11 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) - if (!is.null(dat_dim)) { - name_exp <- name_exp[-which(name_exp == dat_dim)] - name_obs <- name_obs[-which(name_obs == dat_dim)] - } + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension except 'dat_dim'.")) + "all dimension expect 'dat_dim'.")) } if (dim(exp)[time_dim] < 2) { stop("The length of time_dim must be at least 2 to compute RMS.") @@ -202,22 +196,12 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } .RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', - conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { - if (is.null(dat_dim)) { - # exp: [sdate] - # obs: [sdate] - nexp <- 1 - nobs <- 1 - ini_dims <- dim(exp) - dim(exp) <- c(ini_dims, dat_dim = 1) - dim(obs) <- c(ini_dims, dat_dim = 1) - } else { - # exp: [sdate, dat_exp] - # obs: [sdate, dat_obs] - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) - } - + conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { + + # exp: [sdate, dat_exp] + # obs: [sdate, dat_obs] + nexp <- as.numeric(dim(exp)[2]) + nobs <- as.numeric(dim(obs)[2]) nsdate <- as.numeric(dim(exp)[1]) dif <- array(dim = c(sdate = nsdate, nexp = nexp, nobs = nobs)) @@ -252,16 +236,6 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', conf.upper <- (eno * rms ** 2 / chi) ** 0.5 } - ################################### - # Remove nexp and nobs if dat_dim = NULL - if (is.null(dat_dim)) { - dim(rms) <- NULL - dim(conf.lower) <- NULL - dim(conf.upper) <- NULL - } - - ################################### - if (conf) { res <- list(rms = rms, conf.lower = conf.lower, conf.upper = conf.upper) } else { -- GitLab From c3c1b92524356c44f4e6f47fbce12d1e823f6388 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 15:14:46 +0200 Subject: [PATCH 038/140] allow dat_dim NULL in RMS() --- R/RMS.R | 53 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/R/RMS.R b/R/RMS.R index b3c8ad4..cc013e6 100644 --- a/R/RMS.R +++ b/R/RMS.R @@ -107,11 +107,14 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string or NULL.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension. ", + "Set it as NULL if there is no dataset dimension.") + } } ## comp_dim if (!is.null(comp_dim)) { @@ -151,11 +154,13 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) - name_exp <- name_exp[-which(name_exp == dat_dim)] - name_obs <- name_obs[-which(name_obs == dat_dim)] + if (!is.null(dat_dim)) { + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] + } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension expect 'dat_dim'.")) + "all dimension except 'dat_dim'.")) } if (dim(exp)[time_dim] < 2) { stop("The length of time_dim must be at least 2 to compute RMS.") @@ -196,12 +201,22 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } .RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', - conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { - - # exp: [sdate, dat_exp] - # obs: [sdate, dat_obs] - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) + conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { + if (is.null(dat_dim)) { + # exp: [sdate] + # obs: [sdate] + nexp <- 1 + nobs <- 1 + ini_dims <- dim(exp) + dim(exp) <- c(ini_dims, dat_dim = 1) + dim(obs) <- c(ini_dims, dat_dim = 1) + } else { + # exp: [sdate, dat_exp] + # obs: [sdate, dat_obs] + nexp <- as.numeric(dim(exp)[2]) + nobs <- as.numeric(dim(obs)[2]) + } + nsdate <- as.numeric(dim(exp)[1]) dif <- array(dim = c(sdate = nsdate, nexp = nexp, nobs = nobs)) @@ -236,6 +251,16 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', conf.upper <- (eno * rms ** 2 / chi) ** 0.5 } + ################################### + # Remove nexp and nobs if dat_dim = NULL + if (is.null(dat_dim)) { + dim(rms) <- NULL + dim(conf.lower) <- NULL + dim(conf.upper) <- NULL + } + + ################################### + if (conf) { res <- list(rms = rms, conf.lower = conf.lower, conf.upper = conf.upper) } else { -- GitLab From 138e3be98fb8aef41e6ebf5e06799f64c45e3b24 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 15:31:05 +0200 Subject: [PATCH 039/140] Corrected typo in test file --- tests/testthat/test-RMS.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-RMS.R b/tests/testthat/test-RMS.R index 6e18bee..f1083dd 100644 --- a/tests/testthat/test-RMS.R +++ b/tests/testthat/test-RMS.R @@ -89,7 +89,7 @@ test_that("1. Input checks", { expect_error( RMS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), obs = array(1:4, dim = c(a = 1, sdate = 2, dataset = 2))), - "Parameter 'exp' and 'obs' must have same length of all dimension expect 'dat_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimension except 'dat_dim'." ) expect_error( RMS(exp = array(1:5, dim = c(sdate = 1, dataset = 5, a = 1)), -- GitLab From c3f9d73c63be6a8e6ee73eff031f28973223d02b Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 15:53:12 +0200 Subject: [PATCH 040/140] Revert commit --- R/RMS.R | 53 ++++++++++++++--------------------------------------- 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/R/RMS.R b/R/RMS.R index cc013e6..b3c8ad4 100644 --- a/R/RMS.R +++ b/R/RMS.R @@ -107,14 +107,11 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.null(dat_dim)) { - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string or NULL.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension. ", - "Set it as NULL if there is no dataset dimension.") - } + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") } ## comp_dim if (!is.null(comp_dim)) { @@ -154,13 +151,11 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) - if (!is.null(dat_dim)) { - name_exp <- name_exp[-which(name_exp == dat_dim)] - name_obs <- name_obs[-which(name_obs == dat_dim)] - } + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension except 'dat_dim'.")) + "all dimension expect 'dat_dim'.")) } if (dim(exp)[time_dim] < 2) { stop("The length of time_dim must be at least 2 to compute RMS.") @@ -201,22 +196,12 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } .RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', - conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { - if (is.null(dat_dim)) { - # exp: [sdate] - # obs: [sdate] - nexp <- 1 - nobs <- 1 - ini_dims <- dim(exp) - dim(exp) <- c(ini_dims, dat_dim = 1) - dim(obs) <- c(ini_dims, dat_dim = 1) - } else { - # exp: [sdate, dat_exp] - # obs: [sdate, dat_obs] - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) - } - + conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { + + # exp: [sdate, dat_exp] + # obs: [sdate, dat_obs] + nexp <- as.numeric(dim(exp)[2]) + nobs <- as.numeric(dim(obs)[2]) nsdate <- as.numeric(dim(exp)[1]) dif <- array(dim = c(sdate = nsdate, nexp = nexp, nobs = nobs)) @@ -251,16 +236,6 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', conf.upper <- (eno * rms ** 2 / chi) ** 0.5 } - ################################### - # Remove nexp and nobs if dat_dim = NULL - if (is.null(dat_dim)) { - dim(rms) <- NULL - dim(conf.lower) <- NULL - dim(conf.upper) <- NULL - } - - ################################### - if (conf) { res <- list(rms = rms, conf.lower = conf.lower, conf.upper = conf.upper) } else { -- GitLab From beffc1fcbc4a2774ae6758257cb4a5e4f88a381f Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 16:06:03 +0200 Subject: [PATCH 041/140] Allow dat_dim NULL in RMS --- R/RMS.R | 53 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/R/RMS.R b/R/RMS.R index b3c8ad4..b3188fc 100644 --- a/R/RMS.R +++ b/R/RMS.R @@ -107,11 +107,14 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string or NULL.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + "Set it as NULL if there is no dataset dimension.") + } } ## comp_dim if (!is.null(comp_dim)) { @@ -151,11 +154,13 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) - name_exp <- name_exp[-which(name_exp == dat_dim)] - name_obs <- name_obs[-which(name_obs == dat_dim)] + if (!is.null(dat_dim)) { + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] + } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension expect 'dat_dim'.")) + "all dimension except 'dat_dim'.")) } if (dim(exp)[time_dim] < 2) { stop("The length of time_dim must be at least 2 to compute RMS.") @@ -196,12 +201,22 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', } .RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', - conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { - - # exp: [sdate, dat_exp] - # obs: [sdate, dat_obs] - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) + conf = TRUE, conf.lev = 0.95, ncores_input = NULL) { + if (is.null(dat_dim)) { + # exp: [sdate] + # obs: [sdate] + nexp <- 1 + nobs <- 1 + ini_dims <- dim(exp) + dim(exp) <- c(ini_dims, dat_dim = 1) + dim(obs) <- c(ini_dims, dat_dim = 1) + } else { + # exp: [sdate, dat_exp] + # obs: [sdate, dat_obs] + nexp <- as.numeric(dim(exp)[2]) + nobs <- as.numeric(dim(obs)[2]) + } + nsdate <- as.numeric(dim(exp)[1]) dif <- array(dim = c(sdate = nsdate, nexp = nexp, nobs = nobs)) @@ -236,6 +251,16 @@ RMS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', conf.upper <- (eno * rms ** 2 / chi) ** 0.5 } + ################################### + # Remove nexp and nobs if dat_dim = NULL + if (is.null(dat_dim)) { + dim(rms) <- NULL + dim(conf.lower) <- NULL + dim(conf.upper) <- NULL + } + + ################################### + if (conf) { res <- list(rms = rms, conf.lower = conf.lower, conf.upper = conf.upper) } else { -- GitLab From 7f870ec234b4ca6271365a891b50c5cb4a6afa9b Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 16:18:02 +0200 Subject: [PATCH 042/140] Added tests for RMS and corrected typo in tests ultimatebrier and rmsss --- tests/testthat/test-RMS.R | 50 +++++++++++++++++++++++++++++ tests/testthat/test-RMSSS.R | 2 +- tests/testthat/test-UltimateBrier.R | 4 +-- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-RMS.R b/tests/testthat/test-RMS.R index f1083dd..65edfac 100644 --- a/tests/testthat/test-RMS.R +++ b/tests/testthat/test-RMS.R @@ -17,6 +17,13 @@ context("s2dv::RMS tests") set.seed(6) obs2 <- rnorm(10) + # dat3 + set.seed(1) + exp3 <- array(rnorm(120), dim = c(sdate = 5, ftime = 2, lon = 1, lat = 4)) + + set.seed(2) + obs3 <- array(rnorm(80), dim = c(sdate = 5, ftime = 2, lon = 1, lat = 4)) + ############################################## test_that("1. Input checks", { @@ -186,3 +193,46 @@ test_that("3. Output checks: dat2", { }) ############################################## +test_that("4. Output checks: dat3", { + + expect_equal( + dim(RMS(exp3, obs3, dat_dim = NULL)$rms), + c(ftime = 2, lon = 1, lat = 4) + ) + + suppressWarnings( + expect_equal( + RMS(exp3, obs3, dat_dim = NULL)$rms[1:6], + c(1.6458118, 0.8860392, 0.8261295, 1.1681939, 2.1693538, 1.3064454), + tolerance = 0.001 + ) +) +suppressWarnings( + expect_equal( + length(which(is.na(RMS(exp3, obs3, dat_dim = NULL)$conf.lower))), + 0 + ) +) +suppressWarnings( + expect_equal( + max(RMS(exp3, obs3, dat_dim = NULL)$conf.lower, na.rm = T), + 1.453144, + tolerance = 0.001 + ) +) +suppressWarnings( + expect_equal( + min(RMS(exp3, obs3, dat_dim = NULL, conf.lev = 0.99)$conf.upper, na.rm = TRUE), + 2.646274, + tolerance = 0.0001 + ) +) +suppressWarnings( + expect_equal( + length(RMS(exp3, obs3, dat_dim = NULL, conf = FALSE)), + 1 + ) +) +}) + +############################################## \ No newline at end of file diff --git a/tests/testthat/test-RMSSS.R b/tests/testthat/test-RMSSS.R index 7d2a16d..91ef930 100644 --- a/tests/testthat/test-RMSSS.R +++ b/tests/testthat/test-RMSSS.R @@ -77,7 +77,7 @@ test_that("1. Input checks", { expect_error( RMSSS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), obs = array(1:4, dim = c(a = 1, sdate = 2, dataset = 2))), - "Parameter 'exp' and 'obs' must have same length of all dimension expect 'dat_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimension except 'dat_dim'." ) expect_error( RMSSS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), diff --git a/tests/testthat/test-UltimateBrier.R b/tests/testthat/test-UltimateBrier.R index 654aad0..0eb0118 100644 --- a/tests/testthat/test-UltimateBrier.R +++ b/tests/testthat/test-UltimateBrier.R @@ -58,12 +58,12 @@ test_that("1. Input checks", { expect_error( UltimateBrier(exp1, array(1:10, dim = c(dataset = 1, sdate = 6, ftime = 2))), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.") + "of all the dimensions except 'dat_dim' and 'memb_dim'.") ) expect_error( UltimateBrier(exp1, array(1:10, dim = c(dataset = 1, sdate = 5, time = 2))), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.") + "of all the dimensions except 'dat_dim' and 'memb_dim'.") ) # quantile expect_error( -- GitLab From 71a0ced64c0391eb2d4ea716fd3876eed6dd2bc0 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 16:31:04 +0200 Subject: [PATCH 043/140] Revert commit --- tests/testthat/test-RMSSS.R | 2 +- tests/testthat/test-UltimateBrier.R | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-RMSSS.R b/tests/testthat/test-RMSSS.R index 91ef930..7d2a16d 100644 --- a/tests/testthat/test-RMSSS.R +++ b/tests/testthat/test-RMSSS.R @@ -77,7 +77,7 @@ test_that("1. Input checks", { expect_error( RMSSS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), obs = array(1:4, dim = c(a = 1, sdate = 2, dataset = 2))), - "Parameter 'exp' and 'obs' must have same length of all dimension except 'dat_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimension expect 'dat_dim'." ) expect_error( RMSSS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), diff --git a/tests/testthat/test-UltimateBrier.R b/tests/testthat/test-UltimateBrier.R index 0eb0118..654aad0 100644 --- a/tests/testthat/test-UltimateBrier.R +++ b/tests/testthat/test-UltimateBrier.R @@ -58,12 +58,12 @@ test_that("1. Input checks", { expect_error( UltimateBrier(exp1, array(1:10, dim = c(dataset = 1, sdate = 6, ftime = 2))), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions except 'dat_dim' and 'memb_dim'.") + "of all the dimensions expect 'dat_dim' and 'memb_dim'.") ) expect_error( UltimateBrier(exp1, array(1:10, dim = c(dataset = 1, sdate = 5, time = 2))), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions except 'dat_dim' and 'memb_dim'.") + "of all the dimensions expect 'dat_dim' and 'memb_dim'.") ) # quantile expect_error( -- GitLab From 92a1b16040e7a870b9b73c9fae031cf4498029c6 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 16:47:51 +0200 Subject: [PATCH 044/140] Correct typo --- tests/testthat/test-RMS.R | 50 --------------------------------------- 1 file changed, 50 deletions(-) diff --git a/tests/testthat/test-RMS.R b/tests/testthat/test-RMS.R index 65edfac..f1083dd 100644 --- a/tests/testthat/test-RMS.R +++ b/tests/testthat/test-RMS.R @@ -17,13 +17,6 @@ context("s2dv::RMS tests") set.seed(6) obs2 <- rnorm(10) - # dat3 - set.seed(1) - exp3 <- array(rnorm(120), dim = c(sdate = 5, ftime = 2, lon = 1, lat = 4)) - - set.seed(2) - obs3 <- array(rnorm(80), dim = c(sdate = 5, ftime = 2, lon = 1, lat = 4)) - ############################################## test_that("1. Input checks", { @@ -193,46 +186,3 @@ test_that("3. Output checks: dat2", { }) ############################################## -test_that("4. Output checks: dat3", { - - expect_equal( - dim(RMS(exp3, obs3, dat_dim = NULL)$rms), - c(ftime = 2, lon = 1, lat = 4) - ) - - suppressWarnings( - expect_equal( - RMS(exp3, obs3, dat_dim = NULL)$rms[1:6], - c(1.6458118, 0.8860392, 0.8261295, 1.1681939, 2.1693538, 1.3064454), - tolerance = 0.001 - ) -) -suppressWarnings( - expect_equal( - length(which(is.na(RMS(exp3, obs3, dat_dim = NULL)$conf.lower))), - 0 - ) -) -suppressWarnings( - expect_equal( - max(RMS(exp3, obs3, dat_dim = NULL)$conf.lower, na.rm = T), - 1.453144, - tolerance = 0.001 - ) -) -suppressWarnings( - expect_equal( - min(RMS(exp3, obs3, dat_dim = NULL, conf.lev = 0.99)$conf.upper, na.rm = TRUE), - 2.646274, - tolerance = 0.0001 - ) -) -suppressWarnings( - expect_equal( - length(RMS(exp3, obs3, dat_dim = NULL, conf = FALSE)), - 1 - ) -) -}) - -############################################## \ No newline at end of file -- GitLab From fe9572df91fb98dbe98d58de82e070b607512275 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 17:03:15 +0200 Subject: [PATCH 045/140] Allow dat_dim NULL in RMSSS and changed test --- R/RMSSS.R | 39 +++++++++++++++++++++++++++++-------- tests/testthat/test-RMSSS.R | 5 +++-- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/R/RMSSS.R b/R/RMSSS.R index 5fa9659..e44105c 100644 --- a/R/RMSSS.R +++ b/R/RMSSS.R @@ -96,11 +96,14 @@ RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string or NULL.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + " Set it as NULL if there is no dataset dimension.") + } } ## pval if (!is.logical(pval) | length(pval) > 1) { @@ -116,11 +119,13 @@ RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) + if (!is.null(dat_dim)) { name_exp <- name_exp[-which(name_exp == dat_dim)] name_obs <- name_obs[-which(name_obs == dat_dim)] + } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimension expect 'dat_dim'.")) + "all dimension except 'dat_dim'.")) } if (dim(exp)[time_dim] <= 2) { stop("The length of time_dim must be more than 2 to compute RMSSS.") @@ -151,10 +156,20 @@ RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', .RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', pval = TRUE, ncores_input = NULL) { + if (is.null(dat_dim)) { + # exp: [sdate] + # obs: [sdate] + nexp <- 1 + nobs <- 1 + dim(exp) <- c(dim(exp), nexp = nexp) + dim(obs) <- c(dim(obs), nobs = nobs) + } else { # exp: [sdate, dat_exp] # obs: [sdate, dat_obs] - nexp <- as.numeric(dim(exp)[2]) - nobs <- as.numeric(dim(obs)[2]) + nexp <- as.numeric(dim(exp)[2]) + nobs <- as.numeric(dim(obs)[2]) + } + nsdate <- as.numeric(dim(exp)[1]) p_val <- array(dim = c(nexp = nexp, nobs = nobs)) @@ -208,6 +223,14 @@ RMSSS <- function(exp, obs, time_dim = 'sdate', dat_dim = 'dataset', rmsss[which(!tmp)] <- NA } + ################################### + # Remove extra dimensions if dat_dim = NULL + if (is.null(dat_dim)) { + dim(rmsss) <- NULL + dim(p_val) <- NULL + } + ################################### + # output if (pval) { res <- list(rmsss = rmsss, p.val = p_val) diff --git a/tests/testthat/test-RMSSS.R b/tests/testthat/test-RMSSS.R index 7d2a16d..6064508 100644 --- a/tests/testthat/test-RMSSS.R +++ b/tests/testthat/test-RMSSS.R @@ -64,7 +64,8 @@ test_that("1. Input checks", { ) expect_error( RMSSS(exp0, obs0, dat_dim = 'memb'), - "Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension." + paste0("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + " Set it as NULL if there is no dataset dimension.") ) expect_error( RMSSS(exp0, obs0, pval = c(T, T)), @@ -77,7 +78,7 @@ test_that("1. Input checks", { expect_error( RMSSS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), obs = array(1:4, dim = c(a = 1, sdate = 2, dataset = 2))), - "Parameter 'exp' and 'obs' must have same length of all dimension expect 'dat_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimension except 'dat_dim'." ) expect_error( RMSSS(exp = array(1:10, dim = c(sdate = 1, dataset = 5, a = 1)), -- GitLab From f66380ad20c623d51c011e5bdd835046346c89cf Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 17:21:54 +0200 Subject: [PATCH 046/140] Allow dat_dim NULL and add test --- R/UltimateBrier.R | 62 ++++- tests/testthat/test-UltimateBrier.R | 403 +++++++++++++++++++--------- 2 files changed, 318 insertions(+), 147 deletions(-) diff --git a/R/UltimateBrier.R b/R/UltimateBrier.R index aeaddcd..18c8e5b 100644 --- a/R/UltimateBrier.R +++ b/R/UltimateBrier.R @@ -102,12 +102,15 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti stop("Parameter 'exp' and 'obs' must have dimension names.") } ## dat_dim - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string or NULL.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") + } } + ## memb_dim if (!is.character(memb_dim) | length(memb_dim) > 1) { stop("Parameter 'memb_dim' must be a character string.") @@ -137,11 +140,11 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti name_obs <- name_obs[-which(name_obs == dat_dim)] if (any(name_exp != name_obs)) { stop(paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.")) + "of all the dimensions except 'dat_dim' and 'memb_dim'.")) } if (!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.")) + "of all the dimensions except 'dat_dim' and 'memb_dim'.")) } ## quantile if (!is.logical(quantile) | length(quantile) > 1) { @@ -183,6 +186,7 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti target_dims = list(c(time_dim, dat_dim, memb_dim), c(time_dim, dat_dim, memb_dim)), fun = .UltimateBrier, + dat_dim = dat_dim, memb_dim = memb_dim, thr = thr, type = type, decomposition = decomposition, ncores = ncores)$output1 @@ -203,6 +207,7 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti target_dims = list(c(time_dim, dat_dim), c(time_dim, dat_dim)), fun = .UltimateBrier, + dat_dim = dat_dim, memb_dim = memb_dim, thr = thr, type = type, decomposition = decomposition, ncores = ncores) @@ -217,8 +222,8 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti return(res) } -.UltimateBrier <- function(exp, obs, thr = c(5/100, 95/100), type = 'BS', - decomposition = TRUE) { +.UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', thr = c(5/100, 95/100), + type = 'BS', decomposition = TRUE) { # If exp and obs are probablistics # exp: [sdate, nexp] # obs: [sdate, nobs] @@ -229,6 +234,10 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti #NOTE: 'thr' is used in 'FairEnsembleBSS' and 'FairEnsembleBS'. But if quantile = F and # thr is real value, does it work? if (type == 'FairEnsembleBSS') { + if (is.null(dat_dim)) { + obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'dataset') + exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'dataset') + } size_ens_ref <- prod(dim(obs)[c(1, 3)]) res <- array(dim = c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2]), @@ -245,6 +254,9 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti } } } + if (is.null(dat_dim)) { + dim(res) <- dim(res)[3:length(dim(res))] + } } else if (type == 'FairEnsembleBS') { #NOTE: The calculation in s2dverification::UltimateBrier is wrong. In the final stage, @@ -252,6 +264,10 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti # but the 3rd dim of result is 'bins' instead of decomposition. 'FairEnsembleBS' does # not have decomposition. # The calculation is fixed here. + if (is.null(dat_dim)) { + obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'dataset') + exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'dataset') + } res <- array(dim = c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2]), bin = length(thr) + 1)) @@ -264,10 +280,17 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti } } } + if (is.null(dat_dim)) { + dim(res) <- dim(res)[3:length(dim(res))] + } # tmp <- res[, , 1] - res[, , 2] + res[, , 3] # res <- array(tmp, dim = c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2]))) } else if (type == 'BS') { + if (is.null(dat_dim)) { + obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'dataset') + exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'dataset') + } comp <- array(dim = c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2]), comp = 3)) @@ -280,17 +303,28 @@ UltimateBrier <- function(exp, obs, dat_dim = 'dataset', memb_dim = 'member', ti } if (decomposition) { rel <- comp[, , 1] - dim(rel) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) res <- comp[, , 2] - dim(res) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) unc <- comp[, , 3] - dim(unc) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) bs <- rel - res + unc - dim(bs) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + if (is.null(dat_dim)) { + dim(rel) <- NULL + dim(res) <- NULL + dim(unc) <- NULL + dim(bs) <- NULL + } else { + dim(rel) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + dim(res) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + dim(unc) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + dim(bs) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + } res <- list(bs = bs, rel = rel, res = res, unc = unc) } else { bs <- comp[, , 1] - comp[, , 2] + comp[, , 3] - dim(bs) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + if (is.null(dat_dim)) { + dim(bs) <- NULL + } else { + dim(bs) <- c(nexp = as.numeric(dim(exp)[2]), nobs = as.numeric(dim(obs)[2])) + } res <- list(bs = bs) } diff --git a/tests/testthat/test-UltimateBrier.R b/tests/testthat/test-UltimateBrier.R index 654aad0..09412f0 100644 --- a/tests/testthat/test-UltimateBrier.R +++ b/tests/testthat/test-UltimateBrier.R @@ -7,7 +7,11 @@ exp1 <- array(rnorm(30), dim = c(dataset = 1, member = 3, sdate = 5, ftime = 2)) set.seed(2) obs1 <- array(round(rnorm(10)), dim = c(dataset = 1, sdate = 5, ftime = 2)) - +# dat2 +set.seed(1) +exp2 <- array(rnorm(30), dim = c(member = 3, sdate = 5, ftime = 2)) +set.seed(2) +obs2 <- array(round(rnorm(10)), dim = c(sdate = 5, ftime = 2)) ############################################## test_that("1. Input checks", { # exp and obs @@ -26,7 +30,7 @@ test_that("1. Input checks", { # dat_dim expect_error( UltimateBrier(exp1, obs1, dat_dim = 2), - "Parameter 'dat_dim' must be a character string." + "Parameter 'dat_dim' must be a character string or NULL." ) expect_error( UltimateBrier(exp1, obs1, dat_dim = 'dat'), @@ -58,12 +62,12 @@ test_that("1. Input checks", { expect_error( UltimateBrier(exp1, array(1:10, dim = c(dataset = 1, sdate = 6, ftime = 2))), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.") + "of all the dimensions except 'dat_dim' and 'memb_dim'.") ) expect_error( UltimateBrier(exp1, array(1:10, dim = c(dataset = 1, sdate = 5, time = 2))), paste0("Parameter 'exp' and 'obs' must have the same names and lengths ", - "of all the dimensions expect 'dat_dim' and 'memb_dim'.") + "of all the dimensions except 'dat_dim' and 'memb_dim'.") ) # quantile expect_error( @@ -105,136 +109,269 @@ test_that("1. Input checks", { ############################################## test_that("2. Output checks: dat1", { -# 'BS' -expect_equal( -is.list(UltimateBrier(exp1, obs1)), -TRUE -) -expect_equal( -names(UltimateBrier(exp1, obs1)), -c('bs', 'rel', 'res', 'unc') -) -expect_equal( -is.list(UltimateBrier(exp1, obs1, decomposition = FALSE)), -FALSE -) -expect_equal( -dim(UltimateBrier(exp1, obs1, decomposition = FALSE)), -c(nexp = 1, nobs = 1, bin = 3, ftime = 2) -) -expect_equal( -dim(UltimateBrier(exp1, obs1, decomposition = FALSE, thr = c(0.25, 0.5, 0.75))), -c(nexp = 1, nobs = 1, bin = 4, ftime = 2) -) -expect_equal( -UltimateBrier(exp1, obs1)$bs, -UltimateBrier(exp1, obs1, decomposition = FALSE) -) -expect_equal( -as.vector(UltimateBrier(exp1, obs1)$bs), -c(0.42222222, 0.44444444, 0.02222222, 0.48888889, 0.37777778, 0.02222222), -tolerance = 0.0001 -) -expect_equal( -as.vector(UltimateBrier(exp1, obs1)$rel), -c(0.22222222, 0.31111111, 0.02222222, 0.28888889, 0.24444444, 0.02222222), -tolerance = 0.0001 -) -expect_equal( -as.vector(UltimateBrier(exp1, obs1)$res), -c(0.0400000, 0.1066667, 0.0000000, 0.0400000, 0.1066667, 0.0000000), -tolerance = 0.0001 -) -expect_equal( -as.vector(UltimateBrier(exp1, obs1)$unc), -c(0.24, 0.24, 0.00, 0.24, 0.24, 0.00), -tolerance = 0.0001 -) - -# 'BSS' -expect_equal( -dim(UltimateBrier(exp1, obs1, type = 'BSS')), -c(nexp = 1, nobs = 1, bin = 3, ftime = 2) -) -expect_equal( -as.vector(UltimateBrier(exp1, obs1, type = 'BSS')), -c(-0.7592593, -0.8518519, -Inf, -1.0370370, -0.5740741, -Inf), -tolerance = 0.0001 -) - -# 'FairStartDatesBS' -expect_equal( -is.list(UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')), -TRUE -) -expect_equal( -names(UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')), -c('bs', 'rel', 'res', 'unc') -) -expect_equal( -is.list(UltimateBrier(exp1, obs1, decomposition = FALSE, type = 'FairStartDatesBS')), -FALSE -) -expect_equal( -dim(UltimateBrier(exp1, obs1, decomposition = FALSE, type = 'FairStartDatesBS')), -c(nexp = 1, nobs = 1, bin = 3, ftime = 2) -) -expect_equal( -UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')$bs, -UltimateBrier(exp1, obs1, decomposition = FALSE, type = 'FairStartDatesBS') -) -expect_equal( -as.vector(UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')$bs), -c(0.42222222, 0.44444444, 0.02222222, 0.48888889, 0.37777778, 0.02222222), -tolerance = 0.0001 -) -expect_equal( -as.vector(UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')$rel), -c(0.22222222, 0.31111111, 0.02222222, 0.28888889, 0.24444444, 0.02222222), -tolerance = 0.0001 -) -expect_equal( -as.vector(UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')$res), -c(0.0400000, 0.1066667, 0.0000000, 0.0400000, 0.1066667, 0.0000000), -tolerance = 0.0001 -) -expect_equal( -as.vector(UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')$unc), -c(0.24, 0.24, 0.00, 0.24, 0.24, 0.00), -tolerance = 0.0001 -) - -# 'FairStartDatesBSS' -expect_equal( -dim(UltimateBrier(exp1, obs1, type = 'FairStartDatesBSS')), -c(nexp = 1, nobs = 1, bin = 3, ftime = 2) -) -expect_equal( -as.vector(UltimateBrier(exp1, obs1, type = 'FairStartDatesBSS')), -c(-0.7592593, -0.8518519, -Inf, -1.0370370, -0.5740741, -Inf), -tolerance = 0.0001 -) -# 'FairEnsembleBS' -expect_equal( -dim(UltimateBrier(exp1, obs1, type = 'FairEnsembleBS')), -c(nexp = 1, nobs = 1, bin = 3, ftime = 2) -) -expect_equal( -as.vector(UltimateBrier(exp1, obs1, type = 'FairEnsembleBS')), -c(0.1333333, 0.2000000, 0.2000000, 0.1333333, 0.4000000, 0.2000000), -tolerance = 0.0001 -) -# 'FairEnsembleBSS' -expect_equal( -dim(UltimateBrier(exp1, obs1, type = 'FairEnsembleBSS')), -c(nexp = 1, nobs = 1, bin = 3, ftime = 2) -) -expect_equal( -as.vector(UltimateBrier(exp1, obs1, type = 'FairEnsembleBSS')), -c(-0.1111111, -0.6666667, -0.6666667, 0.2592593, -1.2222222, -0.6666667), -tolerance = 0.0001 -) + # 'BS' + expect_equal( + is.list(UltimateBrier(exp1, obs1)), + TRUE + ) + expect_equal( + names(UltimateBrier(exp1, obs1)), + c('bs', 'rel', 'res', 'unc') + ) + expect_equal( + is.list(UltimateBrier(exp1, obs1, decomposition = FALSE)), + FALSE + ) + expect_equal( + dim(UltimateBrier(exp1, obs1, decomposition = FALSE)), + c(nexp = 1, nobs = 1, bin = 3, ftime = 2) + ) + expect_equal( + dim(UltimateBrier(exp1, obs1, decomposition = FALSE, thr = c(0.25, 0.5, 0.75))), + c(nexp = 1, nobs = 1, bin = 4, ftime = 2) + ) + expect_equal( + UltimateBrier(exp1, obs1)$bs, + UltimateBrier(exp1, obs1, decomposition = FALSE) + ) + expect_equal( + as.vector(UltimateBrier(exp1, obs1)$bs), + c(0.42222222, 0.44444444, 0.02222222, 0.48888889, 0.37777778, 0.02222222), + tolerance = 0.0001 + ) + expect_equal( + as.vector(UltimateBrier(exp1, obs1)$rel), + c(0.22222222, 0.31111111, 0.02222222, 0.28888889, 0.24444444, 0.02222222), + tolerance = 0.0001 + ) + expect_equal( + as.vector(UltimateBrier(exp1, obs1)$res), + c(0.0400000, 0.1066667, 0.0000000, 0.0400000, 0.1066667, 0.0000000), + tolerance = 0.0001 + ) + expect_equal( + as.vector(UltimateBrier(exp1, obs1)$unc), + c(0.24, 0.24, 0.00, 0.24, 0.24, 0.00), + tolerance = 0.0001 + ) + + # 'BSS' + expect_equal( + dim(UltimateBrier(exp1, obs1, type = 'BSS')), + c(nexp = 1, nobs = 1, bin = 3, ftime = 2) + ) + expect_equal( + as.vector(UltimateBrier(exp1, obs1, type = 'BSS')), + c(-0.7592593, -0.8518519, -Inf, -1.0370370, -0.5740741, -Inf), + tolerance = 0.0001 + ) + + # 'FairStartDatesBS' + expect_equal( + is.list(UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')), + TRUE + ) + expect_equal( + names(UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')), + c('bs', 'rel', 'res', 'unc') + ) + expect_equal( + is.list(UltimateBrier(exp1, obs1, decomposition = FALSE, type = 'FairStartDatesBS')), + FALSE + ) + expect_equal( + dim(UltimateBrier(exp1, obs1, decomposition = FALSE, type = 'FairStartDatesBS')), + c(nexp = 1, nobs = 1, bin = 3, ftime = 2) + ) + expect_equal( + UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')$bs, + UltimateBrier(exp1, obs1, decomposition = FALSE, type = 'FairStartDatesBS') + ) + expect_equal( + as.vector(UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')$bs), + c(0.42222222, 0.44444444, 0.02222222, 0.48888889, 0.37777778, 0.02222222), + tolerance = 0.0001 + ) + expect_equal( + as.vector(UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')$rel), + c(0.22222222, 0.31111111, 0.02222222, 0.28888889, 0.24444444, 0.02222222), + tolerance = 0.0001 + ) + expect_equal( + as.vector(UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')$res), + c(0.0400000, 0.1066667, 0.0000000, 0.0400000, 0.1066667, 0.0000000), + tolerance = 0.0001 + ) + expect_equal( + as.vector(UltimateBrier(exp1, obs1, type = 'FairStartDatesBS')$unc), + c(0.24, 0.24, 0.00, 0.24, 0.24, 0.00), + tolerance = 0.0001 + ) + + # 'FairStartDatesBSS' + expect_equal( + dim(UltimateBrier(exp1, obs1, type = 'FairStartDatesBSS')), + c(nexp = 1, nobs = 1, bin = 3, ftime = 2) + ) + expect_equal( + as.vector(UltimateBrier(exp1, obs1, type = 'FairStartDatesBSS')), + c(-0.7592593, -0.8518519, -Inf, -1.0370370, -0.5740741, -Inf), + tolerance = 0.0001 + ) + # 'FairEnsembleBS' + expect_equal( + dim(UltimateBrier(exp1, obs1, type = 'FairEnsembleBS')), + c(nexp = 1, nobs = 1, bin = 3, ftime = 2) + ) + expect_equal( + as.vector(UltimateBrier(exp1, obs1, type = 'FairEnsembleBS')), + c(0.1333333, 0.2000000, 0.2000000, 0.1333333, 0.4000000, 0.2000000), + tolerance = 0.0001 + ) + # 'FairEnsembleBSS' + expect_equal( + dim(UltimateBrier(exp1, obs1, type = 'FairEnsembleBSS')), + c(nexp = 1, nobs = 1, bin = 3, ftime = 2) + ) + expect_equal( + as.vector(UltimateBrier(exp1, obs1, type = 'FairEnsembleBSS')), + c(-0.1111111, -0.6666667, -0.6666667, 0.2592593, -1.2222222, -0.6666667), + tolerance = 0.0001 + ) }) +############################################## +test_that("3. Output checks: dat2", { + # 'BS' + expect_equal( + is.list(UltimateBrier(exp2, obs2, dat_dim = NULL)), + TRUE + ) + expect_equal( + names(UltimateBrier(exp2, obs2, dat_dim = NULL)), + c('bs', 'rel', 'res', 'unc') + ) + expect_equal( + is.list(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE)), + FALSE + ) + expect_equal( + dim(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE)), + c(bin = 3, ftime = 2) + ) + expect_equal( + dim(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE, thr = c(0.25, 0.5, 0.75))), + c(bin = 4, ftime = 2) + ) + expect_equal( + UltimateBrier(exp2, obs2, dat_dim = NULL)$bs, + UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE) + ) + expect_equal( + as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL)$bs), + c(0.42222222, 0.44444444, 0.02222222, 0.48888889, 0.37777778, 0.02222222), + tolerance = 0.0001 + ) + expect_equal( + as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL)$rel), + c(0.22222222, 0.31111111, 0.02222222, 0.28888889, 0.24444444, 0.02222222), + tolerance = 0.0001 + ) + expect_equal( + as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL)$res), + c(0.0400000, 0.1066667, 0.0000000, 0.0400000, 0.1066667, 0.0000000), + tolerance = 0.0001 + ) + expect_equal( + as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL)$unc), + c(0.24, 0.24, 0.00, 0.24, 0.24, 0.00), + tolerance = 0.0001 + ) + + # 'BSS' + expect_equal( + dim(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'BSS')), + c(bin = 3, ftime = 2) + ) + expect_equal( + as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'BSS')), + c(-0.7592593, -0.8518519, -Inf, -1.0370370, -0.5740741, -Inf), + tolerance = 0.0001 + ) + + # 'FairStartDatesBS' + expect_equal( + is.list(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')), + TRUE + ) + expect_equal( + names(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')), + c('bs', 'rel', 'res', 'unc') + ) + expect_equal( + is.list(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE, type = 'FairStartDatesBS')), + FALSE + ) + expect_equal( + dim(UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE, type = 'FairStartDatesBS')), + c(bin = 3, ftime = 2) + ) + expect_equal( + UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$bs, + UltimateBrier(exp2, obs2, dat_dim = NULL, decomposition = FALSE, type = 'FairStartDatesBS') + ) + expect_equal( + as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$bs), + c(0.42222222, 0.44444444, 0.02222222, 0.48888889, 0.37777778, 0.02222222), + tolerance = 0.0001 + ) + expect_equal( + as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$rel), + c(0.22222222, 0.31111111, 0.02222222, 0.28888889, 0.24444444, 0.02222222), + tolerance = 0.0001 + ) + expect_equal( + as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$res), + c(0.0400000, 0.1066667, 0.0000000, 0.0400000, 0.1066667, 0.0000000), + tolerance = 0.0001 + ) + expect_equal( + as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBS')$unc), + c(0.24, 0.24, 0.00, 0.24, 0.24, 0.00), + tolerance = 0.0001 + ) + + # 'FairStartDatesBSS' + expect_equal( + dim(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBSS')), + c(bin = 3, ftime = 2) + ) + expect_equal( + as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairStartDatesBSS')), + c(-0.7592593, -0.8518519, -Inf, -1.0370370, -0.5740741, -Inf), + tolerance = 0.0001 + ) + # 'FairEnsembleBS' + expect_equal( + dim(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairEnsembleBS')), + c(bin = 3, ftime = 2) + ) + expect_equal( + as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairEnsembleBS')), + c(0.1333333, 0.2000000, 0.2000000, 0.1333333, 0.4000000, 0.2000000), + tolerance = 0.0001 + ) + # 'FairEnsembleBSS' + expect_equal( + dim(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairEnsembleBSS')), + c(bin = 3, ftime = 2) + ) + expect_equal( + as.vector(UltimateBrier(exp2, obs2, dat_dim = NULL, type = 'FairEnsembleBSS')), + c(-0.1111111, -0.6666667, -0.6666667, 0.2592593, -1.2222222, -0.6666667), + tolerance = 0.0001 + ) + +}) -- GitLab From e40e8d8c73b2957ead96c3e49224672a1344bd31 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 17:34:20 +0200 Subject: [PATCH 047/140] Add test for dat_dim NULL --- tests/testthat/test-RMSSS.R | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/testthat/test-RMSSS.R b/tests/testthat/test-RMSSS.R index 6064508..d592130 100644 --- a/tests/testthat/test-RMSSS.R +++ b/tests/testthat/test-RMSSS.R @@ -25,6 +25,12 @@ context("s2dv::RMSSS tests") set.seed(6) obs3 <- rnorm(10) + # case 4 + set.seed(7) + exp4 <- array(rnorm(120), dim = c(sdate = 10, dat = 1, lon = 3, lat = 2)) + set.seed(8) + obs4 <- array(rnorm(60), dim = c(dat = 1, sdate = 10, lat = 2, lon = 3)) + ############################################## test_that("1. Input checks", { @@ -158,5 +164,24 @@ test_that("4. Output checks: case 3", { tolerance = 0.00001 ) +}) + +############################################## +test_that("5. Output checks: case 4", { + + expect_equal( + dim(RMSSS(exp4, obs4, dat_dim = NULL)$rmsss), + c(dat = 1, lon = 3, lat = 2) + ) + expect_equal( + mean(RMSSS(exp4, obs4, dat_dim = NULL)$rmsss), + -0.3114424, + tolerance = 0.00001 + ) + expect_equal( + range(RMSSS(exp4, obs4, dat_dim = NULL)$p.val), + c(0.3560534, 0.9192801), + tolerance = 0.00001 + ) }) -- GitLab From 10573fe6c2d48b41c948190911f308aee8a72fc9 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 17:44:26 +0200 Subject: [PATCH 048/140] Add test for dat_dim NULL --- tests/testthat/test-RMS.R | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/testthat/test-RMS.R b/tests/testthat/test-RMS.R index f1083dd..65edfac 100644 --- a/tests/testthat/test-RMS.R +++ b/tests/testthat/test-RMS.R @@ -17,6 +17,13 @@ context("s2dv::RMS tests") set.seed(6) obs2 <- rnorm(10) + # dat3 + set.seed(1) + exp3 <- array(rnorm(120), dim = c(sdate = 5, ftime = 2, lon = 1, lat = 4)) + + set.seed(2) + obs3 <- array(rnorm(80), dim = c(sdate = 5, ftime = 2, lon = 1, lat = 4)) + ############################################## test_that("1. Input checks", { @@ -186,3 +193,46 @@ test_that("3. Output checks: dat2", { }) ############################################## +test_that("4. Output checks: dat3", { + + expect_equal( + dim(RMS(exp3, obs3, dat_dim = NULL)$rms), + c(ftime = 2, lon = 1, lat = 4) + ) + + suppressWarnings( + expect_equal( + RMS(exp3, obs3, dat_dim = NULL)$rms[1:6], + c(1.6458118, 0.8860392, 0.8261295, 1.1681939, 2.1693538, 1.3064454), + tolerance = 0.001 + ) +) +suppressWarnings( + expect_equal( + length(which(is.na(RMS(exp3, obs3, dat_dim = NULL)$conf.lower))), + 0 + ) +) +suppressWarnings( + expect_equal( + max(RMS(exp3, obs3, dat_dim = NULL)$conf.lower, na.rm = T), + 1.453144, + tolerance = 0.001 + ) +) +suppressWarnings( + expect_equal( + min(RMS(exp3, obs3, dat_dim = NULL, conf.lev = 0.99)$conf.upper, na.rm = TRUE), + 2.646274, + tolerance = 0.0001 + ) +) +suppressWarnings( + expect_equal( + length(RMS(exp3, obs3, dat_dim = NULL, conf = FALSE)), + 1 + ) +) +}) + +############################################## \ No newline at end of file -- GitLab From 48aebf43d73283e0041c9d41abd12f8d77757a56 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 21 Jul 2022 17:57:11 +0200 Subject: [PATCH 049/140] Changed tests RMS --- tests/testthat/test-RMS.R | 34 +++------------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/tests/testthat/test-RMS.R b/tests/testthat/test-RMS.R index 65edfac..f69cfd8 100644 --- a/tests/testthat/test-RMS.R +++ b/tests/testthat/test-RMS.R @@ -200,39 +200,11 @@ test_that("4. Output checks: dat3", { c(ftime = 2, lon = 1, lat = 4) ) - suppressWarnings( expect_equal( - RMS(exp3, obs3, dat_dim = NULL)$rms[1:6], - c(1.6458118, 0.8860392, 0.8261295, 1.1681939, 2.1693538, 1.3064454), - tolerance = 0.001 - ) -) -suppressWarnings( - expect_equal( - length(which(is.na(RMS(exp3, obs3, dat_dim = NULL)$conf.lower))), - 0 - ) -) -suppressWarnings( - expect_equal( - max(RMS(exp3, obs3, dat_dim = NULL)$conf.lower, na.rm = T), - 1.453144, - tolerance = 0.001 - ) -) -suppressWarnings( - expect_equal( - min(RMS(exp3, obs3, dat_dim = NULL, conf.lev = 0.99)$conf.upper, na.rm = TRUE), - 2.646274, - tolerance = 0.0001 - ) -) -suppressWarnings( - expect_equal( - length(RMS(exp3, obs3, dat_dim = NULL, conf = FALSE)), - 1 + as.vector(RMS(exp3, obs3, dat_dim = NULL)$rms), + c(1.6458118, 0.8860392, 0.8261295, 1.1681939, 2.1693538, 1.3064454, 0.5384229, 1.1215333), + tolerance = 0.00001 ) -) }) ############################################## \ No newline at end of file -- GitLab From bfd7e9dc9a01a8a6fff16871ccc4abc5b8cb4d4e Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Fri, 22 Jul 2022 12:07:14 +0200 Subject: [PATCH 050/140] Revert changes Consist_trend and documentation --- R/Consist_Trend.R | 21 +++++++-------------- man/Consist_Trend.Rd | 6 ++---- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/R/Consist_Trend.R b/R/Consist_Trend.R index 84b7699..ee95684 100644 --- a/R/Consist_Trend.R +++ b/R/Consist_Trend.R @@ -14,8 +14,7 @@ #'@param dat_dim A character string indicating the name of the dataset #' dimensions. If data at some point of 'time_dim' are not complete along #' 'dat_dim' in both 'exp' and 'obs', this point in all 'dat_dim' will be -#' discarded. The default value is 'dataset'. If there is no dataset -#' dimension, set NULL. +#' discarded. The default value is 'dataset'. #'@param time_dim A character string indicating the name of dimension along #' which the trend is computed. The default value is 'sdate'. #'@param interval A positive numeric indicating the unit length between two @@ -30,8 +29,7 @@ #' with dimensions c(stats = 2, nexp + nobs, the rest dimensions of 'exp' and #' 'obs' except time_dim), where 'nexp' is the length of 'dat_dim' in 'exp' #' and 'nobs' is the length of 'dat_dim' in 'obs. The 'stats' dimension -#' contains the intercept and the slope. If dat_dim is NULL, nexp and nobs are -#' omitted. +#' contains the intercept and the slope. #'} #'\item{$conf.lower}{ #' A numeric array of the lower limit of 95\% confidence interval with @@ -111,24 +109,19 @@ Consist_Trend <- function(exp, obs, dat_dim = 'dataset', time_dim = 'sdate', int stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.null(dat_dim)) { - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string or NULL.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", - "Set it as NULL if there is no dataset dimension.") - } + if (!is.character(dat_dim)) { + stop("Parameter 'dat_dim' must be a character string.") + } + if (!all(dat_dim %in% names(dim(exp))) | !all(dat_dim %in% names(dim(obs)))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") } ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) - if (!is.null(dat_dim)) { for (i in 1:length(dat_dim)) { name_exp <- name_exp[-which(name_exp == dat_dim[i])] name_obs <- name_obs[-which(name_obs == dat_dim[i])] } - } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", "all dimension except 'dat_dim'.")) diff --git a/man/Consist_Trend.Rd b/man/Consist_Trend.Rd index b93dad5..2ac7d42 100644 --- a/man/Consist_Trend.Rd +++ b/man/Consist_Trend.Rd @@ -23,8 +23,7 @@ parameter 'exp' except along 'dat_dim'.} \item{dat_dim}{A character string indicating the name of the dataset dimensions. If data at some point of 'time_dim' are not complete along 'dat_dim' in both 'exp' and 'obs', this point in all 'dat_dim' will be -discarded. The default value is 'dataset'. If there is no dataset -dimension, set NULL.} +discarded. The default value is 'dataset'.} \item{time_dim}{A character string indicating the name of dimension along which the trend is computed. The default value is 'sdate'.} @@ -42,8 +41,7 @@ A list containing: with dimensions c(stats = 2, nexp + nobs, the rest dimensions of 'exp' and 'obs' except time_dim), where 'nexp' is the length of 'dat_dim' in 'exp' and 'nobs' is the length of 'dat_dim' in 'obs. The 'stats' dimension - contains the intercept and the slope. If dat_dim is NULL, nexp and nobs are - omitted. + contains the intercept and the slope. } \item{$conf.lower}{ A numeric array of the lower limit of 95\% confidence interval with -- GitLab From 8cf0bfa6456714b548354d9fac342f49cb211ef1 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Fri, 22 Jul 2022 12:26:29 +0200 Subject: [PATCH 051/140] Update documentation with allow dat_dim NULL for UltimateBrier() --- R/UltimateBrier.R | 9 +++++---- man/UltimateBrier.Rd | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/R/UltimateBrier.R b/R/UltimateBrier.R index 18c8e5b..d2c4ac9 100644 --- a/R/UltimateBrier.R +++ b/R/UltimateBrier.R @@ -5,14 +5,15 @@ #'to choose. #' #'@param exp A numeric array of forecast anomalies with named dimensions that -#' at least include 'dat_dim', 'memb_dim', and 'time_dim'. It can be provided +#' at least include 'memb_dim', and 'time_dim'. It can be provided #' by \code{Ano()}. #'@param obs A numeric array of observational reference anomalies with named -#' dimensions that at least include 'dat_dim' and 'time_dim'. If it has +#' dimensions that at least include 'time_dim'. If it has #' 'memb_dim', the length must be 1. The dimensions should be consistent with #' 'exp' except 'dat_dim' and 'memb_dim'. It can be provided by \code{Ano()}. #'@param dat_dim A character string indicating the name of the dataset -#' dimension in 'exp' and 'obs'. The default value is 'dataset'. +#' dimension in 'exp' and 'obs'. The default value is 'dataset'. If there is no dataset +#' dimension, set NULL. #'@param memb_dim A character string indicating the name of the member #' dimension in 'exp' (and 'obs') for ensemble mean calculation. The default #' value is 'member'. @@ -55,7 +56,7 @@ #'same dimensions: #'c(nexp, nobs, no. of bins, the rest dimensions of 'exp' except 'time_dim' and #''memb_dim'). 'nexp' and 'nobs' is the length of dataset dimension in 'exp' -#'and 'obs' respectively.\cr +#'and 'obs' respectively. If dat_dim is NULL, nexp and nobs are omitted.\cr #'The list of 4 includes: #' \itemize{ #' \item{$bs: Brier Score} diff --git a/man/UltimateBrier.Rd b/man/UltimateBrier.Rd index 2cad133..0dfa772 100644 --- a/man/UltimateBrier.Rd +++ b/man/UltimateBrier.Rd @@ -19,16 +19,17 @@ UltimateBrier( } \arguments{ \item{exp}{A numeric array of forecast anomalies with named dimensions that -at least include 'dat_dim', 'memb_dim', and 'time_dim'. It can be provided +at least include 'memb_dim', and 'time_dim'. It can be provided by \code{Ano()}.} \item{obs}{A numeric array of observational reference anomalies with named -dimensions that at least include 'dat_dim' and 'time_dim'. If it has +dimensions that at least include 'time_dim'. If it has 'memb_dim', the length must be 1. The dimensions should be consistent with 'exp' except 'dat_dim' and 'memb_dim'. It can be provided by \code{Ano()}.} \item{dat_dim}{A character string indicating the name of the dataset -dimension in 'exp' and 'obs'. The default value is 'dataset'.} +dimension in 'exp' and 'obs'. The default value is 'dataset'. If there is no dataset +dimension, set NULL.} \item{memb_dim}{A character string indicating the name of the member dimension in 'exp' (and 'obs') for ensemble mean calculation. The default @@ -78,7 +79,7 @@ is an array of Brier scores or Brier skill scores. All the arrays have the same dimensions: c(nexp, nobs, no. of bins, the rest dimensions of 'exp' except 'time_dim' and 'memb_dim'). 'nexp' and 'nobs' is the length of dataset dimension in 'exp' -and 'obs' respectively.\cr +and 'obs' respectively. If dat_dim is NULL, nexp and nobs are omitted.\cr The list of 4 includes: \itemize{ \item{$bs: Brier Score} -- GitLab From 1c51947982b1570ff09b4b10cac16f1b7040fc3d Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Mon, 25 Jul 2022 16:32:10 +0200 Subject: [PATCH 052/140] Allow dat_dim NULL in Ano_CrossValid and added tests --- R/Ano_CrossValid.R | 113 +++++++++++++++++++-------- tests/testthat/test-Ano_CrossValid.R | 28 +++++++ 2 files changed, 110 insertions(+), 31 deletions(-) diff --git a/R/Ano_CrossValid.R b/R/Ano_CrossValid.R index 9920502..d1996b9 100644 --- a/R/Ano_CrossValid.R +++ b/R/Ano_CrossValid.R @@ -18,7 +18,8 @@ #'@param dat_dim A character vector indicating the name of the dataset and #' member dimensions. When calculating the climatology, if data at one #' startdate (i.e., 'time_dim') is not complete along 'dat_dim', this startdate -#' along 'dat_dim' will be discarded. The default value is +#' along 'dat_dim' will be discarded. If there is no dataset dimension, it can be NULL. +#' The default value is #' "c('dataset', 'member')". #'@param memb_dim A character string indicating the name of the member #' dimension. Only used when parameter 'memb' is FALSE. It must be one element @@ -83,11 +84,25 @@ Ano_CrossValid <- function(exp, obs, time_dim = 'sdate', dat_dim = c('dataset', stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## dat_dim - if (!is.character(dat_dim)) { - stop("Parameter 'dat_dim' must be a character vector.") - } - if (!all(dat_dim %in% names(dim(exp))) | !all(dat_dim %in% names(dim(obs)))) { - stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.") + if (!is.null(dat_dim)) { + if (!is.character(dat_dim)) { + stop("Parameter 'dat_dim' must be a character vector.") + } + if (!all(dat_dim %in% names(dim(exp))) | !all(dat_dim %in% names(dim(obs)))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + " Set it as NULL if there is no dataset dimension.") + } + # If dat_dim is not in obs, add it in + if (any(!dat_dim %in% names(dim(obs)))) { + reset_obs_dim <- TRUE + ori_obs_dim <- dim(obs) + dim(obs) <- c(dim(obs), rep(1, length(dat_dim[which(!dat_dim %in% names(dim(obs)))]))) + names(dim(obs)) <- c(names(ori_obs_dim), dat_dim[which(!dat_dim %in% names(dim(obs)))]) + } else { + reset_obs_dim <- FALSE + } + } else { + reset_obs_dim <- FALSE } ## memb if (!is.logical(memb) | length(memb) > 1) { @@ -115,9 +130,11 @@ Ano_CrossValid <- function(exp, obs, time_dim = 'sdate', dat_dim = c('dataset', ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) - for (i in 1:length(dat_dim)) { - name_exp <- name_exp[-which(name_exp == dat_dim[i])] - name_obs <- name_obs[-which(name_obs == dat_dim[i])] + if (!is.null(dat_dim)) { + for (i in 1:length(dat_dim)) { + name_exp <- name_exp[-which(name_exp == dat_dim[i])] + name_obs <- name_obs[-which(name_obs == dat_dim[i])] + } } if(!all(dim(exp)[name_exp] == dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have the same length of ", @@ -135,36 +152,65 @@ Ano_CrossValid <- function(exp, obs, time_dim = 'sdate', dat_dim = c('dataset', #----------------------------------- # Per-paired method: If any sdate along dat_dim is NA, turn all sdate points along dat_dim into NA. - pos <- rep(0, length(dat_dim)) # dat_dim: [dataset, member] - for (i in 1:length(dat_dim)) { - pos[i] <- which(names(dim(obs)) == dat_dim[i]) - } - outrows_exp <- MeanDims(exp, pos, na.rm = FALSE) + - MeanDims(obs, pos, na.rm = FALSE) - outrows_obs <- outrows_exp - - for (i in 1:length(pos)) { - outrows_exp <- InsertDim(outrows_exp, pos[i], dim(exp)[pos[i]]) - outrows_obs <- InsertDim(outrows_obs, pos[i], dim(obs)[pos[i]]) - } - exp_for_clim <- exp - obs_for_clim <- obs - exp_for_clim[which(is.na(outrows_exp))] <- NA - obs_for_clim[which(is.na(outrows_obs))] <- NA + if (!is.null(dat_dim)) { + pos <- rep(0, length(dat_dim)) # dat_dim: [dataset, member] + for (i in 1:length(dat_dim)) { + pos[i] <- which(names(dim(obs)) == dat_dim[i]) + } + outrows_exp <- MeanDims(exp, pos, na.rm = FALSE) + + MeanDims(obs, pos, na.rm = FALSE) + outrows_obs <- outrows_exp + + for (i in 1:length(pos)) { + outrows_exp <- InsertDim(outrows_exp, pos[i], dim(exp)[pos[i]]) + outrows_obs <- InsertDim(outrows_obs, pos[i], dim(obs)[pos[i]]) + } + exp_for_clim <- exp + obs_for_clim <- obs + exp_for_clim[which(is.na(outrows_exp))] <- NA + obs_for_clim[which(is.na(outrows_obs))] <- NA + } else { + exp_for_clim <- exp + obs_for_clim <- obs + } + #----------------------------------- + res <- Apply(list(exp, obs, exp_for_clim, obs_for_clim), + target_dims = c(time_dim, dat_dim), + fun = .Ano_CrossValid, dat_dim = dat_dim, + memb_dim = memb_dim, memb = memb, + ncores = ncores) - res <- Apply(list(exp, obs, exp_for_clim, obs_for_clim), - target_dims = c(time_dim, dat_dim), - fun = .Ano_CrossValid, - memb_dim = memb_dim, memb = memb, - ncores = ncores) + # Remove dat_dim in obs if obs doesn't have at first place + if (reset_obs_dim) { + res_obs_dim <- ori_obs_dim[-which(names(ori_obs_dim) == time_dim)] + if (!memb & memb_dim %in% names(res_obs_dim)) { + res_obs_dim <- res_obs_dim[-which(names(res_obs_dim) == memb_dim)] + } + if (is.integer(res_obs_dim) & length(res_obs_dim) == 0) { + res$obs <- as.vector(res$obs) + } else { + res$obs <- array(res$obs, dim = res_obs_dim) + } + } return(res) } -.Ano_CrossValid <- function(exp, obs, exp_for_clim, obs_for_clim, +.Ano_CrossValid <- function(exp, obs, exp_for_clim, obs_for_clim, dat_dim = c('dataset', 'member'), memb_dim = 'member', memb = TRUE, ncores = NULL) { + if (is.null(dat_dim)) { + ini_dims_exp <- dim(exp) + ini_dims_obs <- dim(obs) + ini_dims_exp_for_clim <- dim(exp) + ini_dims_obs_for_clim <- dim(exp) + exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'dataset') + exp_for_clim <- InsertDim(exp_for_clim, posdim = 2, lendim = 1, name = 'dataset') + obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'dataset') + obs_for_clim <- InsertDim(obs_for_clim, posdim = 2, lendim = 1, name = 'dataset') + } + # exp: [sdate, dat_dim, memb_dim] # obs: [sdate, dat_dim, memb_dim] ano_exp_list <- vector('list', length = dim(exp)[1]) #length: [sdate] @@ -222,5 +268,10 @@ Ano_CrossValid <- function(exp, obs, time_dim = 'sdate', dat_dim = c('dataset', ano_obs <- array(unlist(ano_obs_list), dim = c(dim(obs)[-1], dim(obs)[1])) ano_obs <- Reorder(ano_obs, c(length(dim(obs)), 1:(length(dim(obs)) - 1))) + if (is.null(dat_dim)) { + ano_exp <- array(ano_exp, dim = ini_dims_exp) + ano_obs <- array(ano_obs, dim = ini_dims_obs) + } + return(list(exp = ano_exp, obs = ano_obs)) } diff --git a/tests/testthat/test-Ano_CrossValid.R b/tests/testthat/test-Ano_CrossValid.R index b66fc5f..c50938e 100644 --- a/tests/testthat/test-Ano_CrossValid.R +++ b/tests/testthat/test-Ano_CrossValid.R @@ -13,6 +13,12 @@ exp2 <- array(rnorm(30), dim = c(member = 3, ftime = 2, sdate = 5)) set.seed(2) obs2 <- array(rnorm(20), dim = c(ftime = 2, member = 2, sdate = 5)) +# dat3 +set.seed(1) +exp3 <- array(rnorm(30), dim = c(ftime = 2, sdate = 5)) +set.seed(2) +obs3 <- array(rnorm(20), dim = c(ftime = 2, sdate = 5)) + ############################################## test_that("1. Input checks", { @@ -136,6 +142,28 @@ test_that("3. dat2", { }) +############################################## +test_that("4. dat3", { + expect_equal( + names(Ano_CrossValid(exp3, obs3, dat_dim = NULL)), + c("exp", "obs") + ) + expect_equal( + dim(Ano_CrossValid(exp3, obs3, dat_dim = NULL)$exp), + c(sdate = 5, ftime = 2) + ) + expect_equal( + Ano_CrossValid(exp3, obs3, dat_dim = NULL)$exp[, 2], + c(-0.1182939, 1.6462530, -1.3734335, 0.5750579, -0.7295835), + tolerance = 0.0001 + ) + expect_equal( + Ano_CrossValid(exp3, obs3, dat_dim = NULL)$exp[, 2], + c(-0.1182939, 1.6462530, -1.3734335, 0.5750579, -0.7295835), + tolerance = 0.0001 + ) + +}) -- GitLab From 240c47cd90323ba3b572f69ada7785882e055350 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Mon, 25 Jul 2022 16:41:19 +0200 Subject: [PATCH 053/140] Added text in test-Ano_CrossValid --- tests/testthat/test-Ano_CrossValid.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-Ano_CrossValid.R b/tests/testthat/test-Ano_CrossValid.R index c50938e..2d7c00c 100644 --- a/tests/testthat/test-Ano_CrossValid.R +++ b/tests/testthat/test-Ano_CrossValid.R @@ -57,7 +57,7 @@ test_that("1. Input checks", { ) expect_error( Ano_CrossValid(exp1, obs1, dat_dim = 'dat'), - "Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension." + "Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension. Set it as NULL if there is no dataset dimension." ) # memb expect_error( -- GitLab From 2b83624290d1479b375833f7dade2d573f323e74 Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Thu, 28 Jul 2022 11:31:50 +0200 Subject: [PATCH 054/140] corrected two typos in the documentation --- R/RPS.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/RPS.R b/R/RPS.R index 8ded53e..b0f4c97 100644 --- a/R/RPS.R +++ b/R/RPS.R @@ -25,7 +25,7 @@ #'@param indices_for_clim A vector of the indices to be taken along 'time_dim' #' for computing the thresholds between the probabilistic categories. If NULL, #' the whole period is used. The default value is NULL. -#'@param Fair A logical indicating whether to compute the FairRPSS (the +#'@param Fair A logical indicating whether to compute the FairRPS (the #' potential RPSS that the forecast would have with an infinite ensemble size). #' The default value is FALSE. #'@param weights A named two-dimensional numerical array of the weights for each @@ -88,7 +88,7 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions expect 'memb_dim'.")) + "all dimensions except 'memb_dim'.")) } ## prob_thresholds if (!is.numeric(prob_thresholds) | !is.vector(prob_thresholds) | -- GitLab From ebfba76086cff7b42b44ac40e8b43def3acfc975 Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Thu, 28 Jul 2022 11:32:57 +0200 Subject: [PATCH 055/140] first version --- R/CRPS.R | 116 +++++++++++++++++++++++++++++++++++++ R/CRPSS.R | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 R/CRPS.R create mode 100644 R/CRPSS.R diff --git a/R/CRPS.R b/R/CRPS.R new file mode 100644 index 0000000..c3205ab --- /dev/null +++ b/R/CRPS.R @@ -0,0 +1,116 @@ +#'Compute the Continuous Ranked Probability Score +#' +#'The Continuous Ranked Probability Score (CRPS; Wilks, 2011) is the continuous +#'version of the Ranked Probability Score (RPS; Wilks, 2011). It is a skill metric +#'to evaluate the full distribution of probabilistic forecasts. It has a negative +#'orientation (i.e., the higher-quality forecast the smaller CRPS) and it rewards +#'the forecast that has probability concentration around the observed value. In case +#'of a deterministic forecast, the CRPS is reduced to the mean absolute error. It has +#'the same units as the data. The function is based on enscrps_cpp from SpecsVerification. +#' +#'@param exp A named numerical array of the forecast with at least time +#' dimension. +#'@param obs A named numerical array of the observation with at least time +#' dimension. The dimensions must be the same as 'exp' except 'memb_dim'. +#'@param time_dim A character string indicating the name of the time dimension. +#' The default value is 'sdate'. +#'@param memb_dim A character string indicating the name of the member dimension +#' to compute the probabilities of the forecast. The default value is 'member'. +#'@param Fair A logical indicating whether to compute the FairCRPS (the +#' potential RPSS that the forecast would have with an infinite ensemble size). +#' The default value is FALSE. +#'@param ncores An integer indicating the number of cores to use for parallel +#' computation. The default value is NULL. +#' +#'@return +#'A numerical array of CRPS with the same dimensions as "exp" except the +#''time_dim' and 'memb_dim' dimensions. +#' +#'@references +#'Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 +#' +#'@examples +#'exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +#'obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) +#'res <- CRPS(exp = exp, obs = obs) +#' +#'@import multiApply +#'@importFrom SpecsVerification enscrps_cpp +#'@export +CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', + Fair = FALSE, ncores = NULL) { + + # Check inputs + ## exp and obs (1) + if (!is.array(exp) | !is.numeric(exp)) + stop('Parameter "exp" must be a numeric array.') + if (!is.array(obs) | !is.numeric(obs)) + stop('Parameter "obs" must be a numeric array.') + if(any(is.null(names(dim(exp))))| any(nchar(names(dim(exp))) == 0) | + any(is.null(names(dim(obs))))| any(nchar(names(dim(obs))) == 0)) { + stop("Parameter 'exp' and 'obs' must have dimension names.") + } + ## time_dim + if (!is.character(time_dim) | length(time_dim) != 1) + stop('Parameter "time_dim" must be a character string.') + if (!time_dim %in% names(dim(exp)) | !time_dim %in% names(dim(obs))) { + stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") + } + ## memb_dim + if (!is.character(memb_dim) | length(memb_dim) > 1) { + stop("Parameter 'memb_dim' must be a character string.") + } + if (!memb_dim %in% names(dim(exp))) { + stop("Parameter 'memb_dim' is not found in 'exp' dimension.") + } + ## exp and obs (2) + name_exp <- sort(names(dim(exp))) + name_obs <- sort(names(dim(obs))) + name_exp <- name_exp[-which(name_exp == memb_dim)] + if (memb_dim %in% name_obs) { + stop('Not implemented for observations with members.') + } + if (!identical(length(name_exp), length(name_obs)) | + !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { + stop(paste0("Parameter 'exp' and 'obs' must have same length of ", + "all dimensions except 'memb_dim'.")) + } + ## Fair + if (!is.logical(Fair) | length(Fair) > 1) { + stop("Parameter 'Fair' must be either TRUE or FALSE.") + } + ## ncores + if (!is.null(ncores)) { + if (!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 | + length(ncores) > 1) { + stop("Parameter 'ncores' must be either NULL or a positive integer.") + } + } + + ############################### + + crps <- Apply(data = list(exp = exp, obs = obs), + target_dims = list(exp = c(time_dim, memb_dim), + obs = time_dim), + output_dims = time_dim, + fun = .CRPS, Fair = Fair, + ncores = ncores)$output1 + + # Return only the mean RPS + crps <- MeanDims(crps, time_dim, na.rm = FALSE) + + return(crps) +} + +.CRPS <- function(exp, obs, Fair = FALSE) { + # exp: [sdate, memb] + # obs: [sdate] + + if (Fair) { # FairCRPS + R_new <- Inf + } else {R_new <- NA} + + crps <- SpecsVerification::enscrps_cpp(ens = exp, obs = obs, R_new = R_new) + + return(crps) +} diff --git a/R/CRPSS.R b/R/CRPSS.R new file mode 100644 index 0000000..221236e --- /dev/null +++ b/R/CRPSS.R @@ -0,0 +1,169 @@ +#'Compute the Continuous Ranked Probability Skill Score +#' +#'The Continuous Ranked Probability Skill Score (CRPSS; Wilks, 2011) is the skill score +#'based on the Continuous Ranked Probability Score (CRPS; Wilks, 2011). It can be used to +#'assess whether a forecast presents an improvement or worsening with respect to +#'a reference forecast. The CRPSS ranges between minus infinite and 1. If the +#'CRPSS is positive, it indicates that the forecast has higher skill than the +#'reference forecast, while a negative value means that it has a lower skill. +#'Examples of reference forecasts are the climatological forecast (same +#'probabilities for all categories for all time steps), persistence, a previous +#'model version, and another model. It is computed as CRPSS = 1 - CRPS_exp / CRPS_ref. +#'The statistical significance is obtained based on a Random Walk test at the +#'95% confidence level (DelSole and Tippett, 2016). +#' +#'@param exp A named numerical array of the forecast with at least time +#' dimension. +#'@param obs A named numerical array of the observation with at least time +#' dimension. The dimensions must be the same as 'exp' except 'memb_dim'. +#'@param ref A named numerical array of the reference forecast data with at +#' least time dimension. The dimensions must be the same as 'exp' except +#' 'memb_dim'. If it is NULL, the climatological forecast is used as reference +#' forecast. The default value is NULL. +#'@param time_dim A character string indicating the name of the time dimension. +#' The default value is 'sdate'. +#'@param memb_dim A character string indicating the name of the member dimension +#' to compute the probabilities of the forecast and the reference forecast. The +#' default value is 'member'. +#'@param Fair A logical indicating whether to compute the FairCRPSS (the +#' potential CRPSS that the forecast would have with an infinite ensemble size). +#' The default value is FALSE. +#'@param ncores An integer indicating the number of cores to use for parallel +#' computation. The default value is NULL. +#' +#'@return +#'\item{$crpss}{ +#' A numerical array of the CRPSS with the same dimensions as "exp" except the +#' 'time_dim' and 'memb_dim' dimensions. +#'} +#'\item{$sign}{ +#' A logical array of the statistical significance of the CRPSS with the same +#' dimensions as 'exp' except the 'time_dim' and 'memb_dim' dimensions. +#'} +#' +#'@references +#'Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 +#'DelSole and Tippett, 2016; https://doi.org/10.1175/MWR-D-15-0218.1 +#' +#'@examples +#'exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +#'obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) +#'ref <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +#'res <- CRPSS(exp = exp, obs = obs) ## climatology as reference forecast +#'res <- CRPSS(exp = exp, obs = obs, ref = ref) ## ref as reference forecast +#' +#'@import multiApply +#'@export +CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', + Fair = FALSE, ncores = NULL) { + + # Check inputs + ## exp, obs, and ref (1) + if (!is.array(exp) | !is.numeric(exp)) + stop('Parameter "exp" must be a numeric array.') + if (!is.array(obs) | !is.numeric(obs)) + stop('Parameter "obs" must be a numeric array.') + if (!is.null(ref)) { + if (!is.array(ref) | !is.numeric(ref)) + stop('Parameter "ref" must be a numeric array.') + } + ## time_dim + if (!is.character(time_dim) | length(time_dim) != 1) + stop('Parameter "time_dim" must be a character string.') + if (!time_dim %in% names(dim(exp)) | !time_dim %in% names(dim(obs))) { + stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") + } + if (!is.null(ref) & !time_dim %in% names(dim(ref))) { + stop("Parameter 'time_dim' is not found in 'ref' dimension.") + } + ## memb_dim + if (!is.character(memb_dim) | length(memb_dim) > 1) { + stop("Parameter 'memb_dim' must be a character string.") + } + if (!memb_dim %in% names(dim(exp))) { + stop("Parameter 'memb_dim' is not found in 'exp' dimension.") + } + if (!is.null(ref) & !memb_dim %in% names(dim(ref))) { + stop("Parameter 'memb_dim' is not found in 'ref' dimension.") + } + ## exp and obs (2) + name_exp <- sort(names(dim(exp))) + name_obs <- sort(names(dim(obs))) + name_exp <- name_exp[-which(name_exp == memb_dim)] + if (memb_dim %in% name_obs) { + stop('Not implemented for observations with members.') + } + if (!identical(length(name_exp), length(name_obs)) | + !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { + stop(paste0("Parameter 'exp' and 'obs' must have same length of ", + "all dimensions expect 'memb_dim'.")) + } + if (!is.null(ref)) { + name_ref <- sort(names(dim(ref))) + name_ref <- name_ref[-which(name_ref == memb_dim)] + if (!identical(length(name_exp), length(name_ref)) | + !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { + stop(paste0("Parameter 'exp' and 'obs' must have same length of ", + "all dimensions expect 'memb_dim'.")) + } + } + ## Fair + if (!is.logical(Fair) | length(Fair) > 1) { + stop("Parameter 'Fair' must be either TRUE or FALSE.") + } + ## ncores + if (!is.null(ncores)) { + if (!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 | + length(ncores) > 1) { + stop("Parameter 'ncores' must be either NULL or a positive integer.") + } + } + + ############################### + + # Compute CRPSS + if (!is.null(ref)) { # use "ref" as reference forecast + data <- list(exp = exp, obs = obs, ref = ref) + target_dims = list(exp = c(time_dim, memb_dim), + obs = time_dim, + ref = c(time_dim, memb_dim)) + } else { + data <- list(exp = exp, obs = obs) + target_dims = list(exp = c(time_dim, memb_dim), + obs = time_dim) + } + output <- Apply(data, + target_dims = target_dims, + fun = .CRPSS, + Fair = Fair, + ncores = ncores) + + return(output) +} + +.CRPSS <- function(exp, obs, ref = NULL, Fair = FALSE) { + # exp: [sdate, memb] + # obs: [sdate] + # ref: [sdate, memb] or NULL + + # CRPS of the forecast + crps_exp <- .CRPS(exp = exp, obs = obs, Fair = Fair) + + # CRPS of the reference forecast + if (is.null(ref)){ + ## using climatology as reference forecast + ## all the time steps are used as if they were members + ## then, ref dimensions are [sdate, memb], both with length(sdate) + ref <- array(data = obs, dim = c(member = length(obs))) + ref <- InsertDim(data = ref, posdim = 1, lendim = length(obs), name = 'sdate') + } + crps_ref <- .CRPS(exp = ref, obs = obs, Fair = Fair) + + # CRPSS + crpss <- 1 - mean(crps_exp) / mean(crps_ref) + + # Significance + sign <- .RandomWalkTest(skill_A = crps_exp, skill_B = crps_ref)$signif + + return(list(crpss = crpss, sign = sign)) +} -- GitLab From 92cbed12abe64ce92e86ca8cf2527730f59bebd6 Mon Sep 17 00:00:00 2001 From: Carlos Delgado Torres Date: Thu, 28 Jul 2022 11:45:50 +0200 Subject: [PATCH 056/140] corrected typo in the check for 'exp' and 'obs' dimensions --- tests/testthat/test-RPS.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-RPS.R b/tests/testthat/test-RPS.R index 04df1bb..2029fd9 100644 --- a/tests/testthat/test-RPS.R +++ b/tests/testthat/test-RPS.R @@ -56,7 +56,7 @@ test_that("1. Input checks", { # exp, ref, and obs (2) expect_error( RPS(exp1, array(1:9, dim = c(sdate = 9))), - "Parameter 'exp' and 'obs' must have same length of all dimensions expect 'memb_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim'." ) # prob_thresholds expect_error( -- GitLab From 739c51b662396965e955607a8049e314d8a0615b Mon Sep 17 00:00:00 2001 From: aho Date: Thu, 28 Jul 2022 17:08:03 +0200 Subject: [PATCH 057/140] Update documentation --- man/Ano_CrossValid.Rd | 9 +++++---- man/RMS.Rd | 3 +-- man/RMSSS.Rd | 3 +-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/man/Ano_CrossValid.Rd b/man/Ano_CrossValid.Rd index d2234a1..2a82713 100644 --- a/man/Ano_CrossValid.Rd +++ b/man/Ano_CrossValid.Rd @@ -25,10 +25,11 @@ parameter 'exp' except along 'dat_dim'.} The default value is 'sdate'.} \item{dat_dim}{A character vector indicating the name of the dataset and -member dimensions. When calculating the climatology, if data at one -startdate (i.e., 'time_dim') is not complete along 'dat_dim', this startdate -along 'dat_dim' will be discarded. The default value is -"c('dataset', 'member')".} + member dimensions. When calculating the climatology, if data at one + startdate (i.e., 'time_dim') is not complete along 'dat_dim', this startdate + along 'dat_dim' will be discarded. If there is no dataset dimension, it can be NULL. +The default value is + "c('dataset', 'member')".} \item{memb_dim}{A character string indicating the name of the member dimension. Only used when parameter 'memb' is FALSE. It must be one element diff --git a/man/RMS.Rd b/man/RMS.Rd index a84f965..4391df4 100644 --- a/man/RMS.Rd +++ b/man/RMS.Rd @@ -31,8 +31,7 @@ length as 'exp', then the vector will automatically be 'time_dim' and which the correlations are computed. The default value is 'sdate'.} \item{dat_dim}{A character string indicating the name of member (nobs/nexp) -dimension. The default value is 'dataset'. If there is no dataset -dimension, set NULL.} +dimension. The default value is 'dataset'.} \item{comp_dim}{A character string indicating the name of dimension along which obs is taken into account only if it is complete. The default value diff --git a/man/RMSSS.Rd b/man/RMSSS.Rd index 2c23ee4..9ebcf65 100644 --- a/man/RMSSS.Rd +++ b/man/RMSSS.Rd @@ -30,8 +30,7 @@ be 1.} which the RMSSS are computed. The default value is 'sdate'.} \item{dat_dim}{A character string indicating the name of dataset (nobs/nexp) -dimension. The default value is 'dataset'. If there is no dataset -dimension, set NULL.} +dimension. The default value is 'dataset'.} \item{pval}{A logical value indicating whether to compute or not the p-value of the test Ho: RMSSS = 0. If pval = TRUE, the insignificant RMSSS will -- GitLab From 780e068977cd7473127d7c3d4ba44c2338bbd784 Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Fri, 29 Jul 2022 10:18:59 +0200 Subject: [PATCH 058/140] allow memb_dim=1 for obs --- R/CRPS.R | 9 ++++++--- R/CRPSS.R | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/R/CRPS.R b/R/CRPS.R index c3205ab..942ec9e 100644 --- a/R/CRPS.R +++ b/R/CRPS.R @@ -36,6 +36,7 @@ #' #'@import multiApply #'@importFrom SpecsVerification enscrps_cpp +#'@importFrom ClimProjDiags Subset #'@export CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', Fair = FALSE, ncores = NULL) { @@ -64,12 +65,14 @@ CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', stop("Parameter 'memb_dim' is not found in 'exp' dimension.") } ## exp and obs (2) + if (memb_dim %in% names(dim(obs))) { + if (identical(as.numeric(dim(obs)[memb_dim]),1)){ + obs <- ClimProjDiags::Subset(x = obs, along = memb_dim, indices = 1, drop = 'selected') + } else {stop("Not implemented for observations with members ('obs' can have 'memb_dim', but it should be of length=1).")} + } name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) name_exp <- name_exp[-which(name_exp == memb_dim)] - if (memb_dim %in% name_obs) { - stop('Not implemented for observations with members.') - } if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", diff --git a/R/CRPSS.R b/R/CRPSS.R index 221236e..9f5edbd 100644 --- a/R/CRPSS.R +++ b/R/CRPSS.R @@ -53,6 +53,7 @@ #'res <- CRPSS(exp = exp, obs = obs, ref = ref) ## ref as reference forecast #' #'@import multiApply +#'@importFrom ClimProjDiags Subset #'@export CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', Fair = FALSE, ncores = NULL) { @@ -87,12 +88,14 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', stop("Parameter 'memb_dim' is not found in 'ref' dimension.") } ## exp and obs (2) + if (memb_dim %in% names(dim(obs))) { + if (identical(as.numeric(dim(obs)[memb_dim]),1)){ + obs <- ClimProjDiags::Subset(x = obs, along = memb_dim, indices = 1, drop = 'selected') + } else {stop("Not implemented for observations with members ('obs' can have 'memb_dim', but it should be of length=1).")} + } name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) name_exp <- name_exp[-which(name_exp == memb_dim)] - if (memb_dim %in% name_obs) { - stop('Not implemented for observations with members.') - } if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", -- GitLab From 5a40eb03f58bcc139cdb58f487932ec147dbda1a Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 4 Aug 2022 17:54:54 +0200 Subject: [PATCH 059/140] Develop dat_dim NULL in RPS and RPSS and in tests --- R/RPS.R | 42 ++++++++++++++++++++++++++---------- R/RPSS.R | 39 ++++++++++++++++++++++++++------- man/RPS.Rd | 5 +++++ man/RPSS.Rd | 1 + tests/testthat/test-RPS.R | 26 +++++++++++++++++++++- tests/testthat/test-RPSS.R | 44 +++++++++++++++++++++++++++++++++++++- 6 files changed, 136 insertions(+), 21 deletions(-) diff --git a/R/RPS.R b/R/RPS.R index 200c59c..1c41d11 100644 --- a/R/RPS.R +++ b/R/RPS.R @@ -17,6 +17,9 @@ #' dimension. The dimensions must be the same as 'exp' except 'memb_dim'. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. +#'@param dat_dim A character string indicating the name of dataset (nobs/nexp) +#' dimension. The length of this dimension can be different between ’exp’ and ’obs’. +#' The default value is NULL. #'@param memb_dim A character string indicating the name of the member dimension #' to compute the probabilities of the forecast. The default value is 'member'. #'@param prob_thresholds A numeric vector of the relative thresholds (from 0 to @@ -51,7 +54,7 @@ #'@import multiApply #'@importFrom easyVerification convert2prob #'@export -RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', +RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights = NULL, ncores = NULL) { @@ -71,6 +74,16 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', if (!time_dim %in% names(dim(exp)) | !time_dim %in% names(dim(obs))) { stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } + ## dat_dim + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + " Set it as NULL if there is no dataset dimension.") + } + } ## memb_dim if (!is.character(memb_dim) | length(memb_dim) > 1) { stop("Parameter 'memb_dim' must be a character string.") @@ -85,10 +98,14 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', if (memb_dim %in% name_obs) { name_obs <- name_obs[-which(name_obs == memb_dim)] } + if (!is.null(dat_dim)) { + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] + } if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions except 'memb_dim'.")) + "all dimensions except 'memb_dim' and 'dat_dim'.")) } ## prob_thresholds if (!is.numeric(prob_thresholds) | !is.vector(prob_thresholds) | @@ -135,12 +152,12 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', # Compute RPS if (!memb_dim %in% names(dim(obs))) { - target_dims_obs <- time_dim + target_dims_obs <- c(time_dim, dat_dim) } else { - target_dims_obs <- c(time_dim, memb_dim) + target_dims_obs <- c(time_dim, memb_dim, dat_dim) } rps <- Apply(data = list(exp = exp, obs = obs), - target_dims = list(exp = c(time_dim, memb_dim), + target_dims = list(exp = c(time_dim, memb_dim, dat_dim), obs = target_dims_obs), output_dims = time_dim, fun = .RPS, @@ -183,18 +200,21 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', } .get_probs <- function(data, indices_for_quantiles, prob_thresholds, weights = NULL) { + # if exp: [sdate, memb] # if obs: [sdate, (memb)] - # Add dim [memb = 1] to obs if it doesn't have memb_dim + # Add dim [length = 1] to obs if it doesn't have memb_dim or dat_dim if (length(dim(data)) == 1) dim(data) <- c(dim(data), 1) + # Add dim [length = 1] to obs if it doesn't have memb_dim or dat_dim + if (length(dim(data)) == 2) dim(data) <- c(dim(data), 1) # Absolute thresholds if (is.null(weights)) { - quantiles <- quantile(x = as.vector(data[indices_for_quantiles, ]), probs = prob_thresholds, type = 8, na.rm = TRUE) + quantiles <- quantile(x = as.vector(data[indices_for_quantiles, ,]), probs = prob_thresholds, type = 8, na.rm = TRUE) } else { # weights: [sdate, memb] - sorted_arrays <- .sorted_distributions(data[indices_for_quantiles, ], weights[indices_for_quantiles, ]) + sorted_arrays <- .sorted_distributions(data[indices_for_quantiles, , ], weights[indices_for_quantiles, ]) sorted_data <- sorted_arrays$data cumulative_weights <- sorted_arrays$cumulative_weights quantiles <- approx(cumulative_weights, sorted_data, prob_thresholds, "linear")$y @@ -203,13 +223,13 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', # Probabilities probs <- array(dim = c(bin = length(quantiles) + 1, dim(data)[1])) # [bin, sdate] for (i_time in 1:dim(data)[1]) { - if (anyNA(data[i_time, ])) { + if (anyNA(data[i_time, ,])) { probs[, i_time] <- rep(NA, dim = length(quantiles) + 1) } else { if (is.null(weights)) { - probs[, i_time] <- colMeans(easyVerification::convert2prob(data[i_time, ], threshold = quantiles)) + probs[, i_time] <- colMeans(easyVerification::convert2prob(data[i_time, , ], threshold = quantiles)) } else { - sorted_arrays <- .sorted_distributions(data[i_time, ], weights[i_time, ]) + sorted_arrays <- .sorted_distributions(data[i_time, , ], weights[i_time, ]) sorted_data <- sorted_arrays$data cumulative_weights <- sorted_arrays$cumulative_weights # find any quantiles that are outside the data range diff --git a/R/RPSS.R b/R/RPSS.R index 4552d74..7d4012e 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -69,7 +69,7 @@ #' #'@import multiApply #'@export -RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', +RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights = NULL, weights_exp = NULL, weights_ref = NULL, ncores = NULL) { @@ -92,6 +92,22 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!is.null(ref) & !time_dim %in% names(dim(ref))) { stop("Parameter 'time_dim' is not found in 'ref' dimension.") } + ## dat_dim + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp', 'obs' or in 'ref' dimension.", + " Set it as NULL if there is no dataset dimension.") + } + if (!is.null(ref)) { + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp', 'obs' or in 'ref' dimension.", + " Set it as NULL if there is no dataset dimension.") + } + } + } ## memb_dim if (!is.character(memb_dim) | length(memb_dim) > 1) { stop("Parameter 'memb_dim' must be a character string.") @@ -109,18 +125,25 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (memb_dim %in% name_obs) { name_obs <- name_obs[-which(name_obs == memb_dim)] } + if (!is.null(dat_dim)) { + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] + } if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions except 'memb_dim'.")) + "all dimensions except 'memb_dim' and 'dat_dim'.")) } if (!is.null(ref)) { name_ref <- sort(names(dim(ref))) name_ref <- name_ref[-which(name_ref == memb_dim)] + if (!is.null(dat_dim)) { + name_ref <- name_ref[-which(name_ref == dat_dim)] + } if (!identical(length(name_exp), length(name_ref)) | !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions except 'memb_dim'.")) + "all dimensions except 'memb_dim' and 'dat_dim'.")) } } ## prob_thresholds @@ -188,19 +211,19 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', # Compute RPSS if (!memb_dim %in% names(dim(obs))) { - target_dims_obs <- time_dim + target_dims_obs <- c(time_dim, dat_dim) } else { - target_dims_obs <- c(time_dim, memb_dim) + target_dims_obs <- c(time_dim, memb_dim, dat_dim) } if (!is.null(ref)) { # use "ref" as reference forecast data <- list(exp = exp, obs = obs, ref = ref) - target_dims = list(exp = c(time_dim, memb_dim), + target_dims = list(exp = c(time_dim, memb_dim, dat_dim), obs = target_dims_obs, - ref = c(time_dim, memb_dim)) + ref = c(time_dim, memb_dim, dat_dim)) } else { data <- list(exp = exp, obs = obs) - target_dims = list(exp = c(time_dim, memb_dim), + target_dims = list(exp = c(time_dim, memb_dim, dat_dim), obs = target_dims_obs) } output <- Apply(data, diff --git a/man/RPS.Rd b/man/RPS.Rd index ee5c241..d7fb734 100644 --- a/man/RPS.Rd +++ b/man/RPS.Rd @@ -9,6 +9,7 @@ RPS( obs, time_dim = "sdate", memb_dim = "member", + dat_dim = NULL, prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, @@ -29,6 +30,10 @@ The default value is 'sdate'.} \item{memb_dim}{A character string indicating the name of the member dimension to compute the probabilities of the forecast. The default value is 'member'.} +\item{dat_dim}{A character string indicating the name of dataset (nobs/nexp) + dimension. The length of this dimension can be different between ’exp’ and ’obs’. +The default value is NULL.} + \item{prob_thresholds}{A numeric vector of the relative thresholds (from 0 to 1) between the categories. The default value is c(1/3, 2/3), which corresponds to tercile equiprobable categories.} diff --git a/man/RPSS.Rd b/man/RPSS.Rd index 893169d..e9df825 100644 --- a/man/RPSS.Rd +++ b/man/RPSS.Rd @@ -10,6 +10,7 @@ RPSS( ref = NULL, time_dim = "sdate", memb_dim = "member", + dat_dim = NULL, prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, diff --git a/tests/testthat/test-RPS.R b/tests/testthat/test-RPS.R index 2029fd9..6d6e2d9 100644 --- a/tests/testthat/test-RPS.R +++ b/tests/testthat/test-RPS.R @@ -21,7 +21,11 @@ exp2_1 <- array(rnorm(20), dim = c(member = 2, sdate = 10)) set.seed(2) obs2_1 <- array(rnorm(10), dim = c(member = 1, sdate = 10)) - +# dat3 +set.seed(1) +exp3 <- array(rnorm(40), dim = c(member = 2, sdate = 10, dataset = 2)) +set.seed(2) +obs3 <- array(rnorm(30), dim = c(member = 1, sdate = 10, dataset = 3)) ############################################## test_that("1. Input checks", { @@ -155,3 +159,23 @@ RPS(exp2, obs2), RPS(exp2_1, obs2_1) ) }) + +############################################## +test_that("3. Output checks: dat3", { + +expect_equal( +dim(RPS(exp3, obs3, dat_dim = 'dataset')), +NULL +) +expect_equal( +RPS(exp3, obs3, dat_dim = 'dataset'), +2.3444, +tolerance = 0.0001 +) +expect_equal( +RPS(exp3, obs3, dat_dim = 'dataset', indices_for_clim = 2:5, prob_thresholds = seq(0.1, 0.9, 0.1)), +4.7722, +tolerance = 0.0001 +) + +}) diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index a9edaff..428e970 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -20,7 +20,13 @@ set.seed(2) obs2_1 <- array(rnorm(10), dim = c(member = 1, sdate = 10)) set.seed(3) ref2 <- array(rnorm(20), dim = c(member = 2, sdate = 10)) - +# dat3 +set.seed(1) +exp3 <- array(rnorm(20), dim = c(member = 2, sdate = 10, dataset = 3)) +set.seed(2) +obs3 <- array(rnorm(10), dim = c(member = 1, sdate = 10, dataset = 2)) +set.seed(3) +ref3 <- array(rnorm(20), dim = c(member = 2, sdate = 10, dataset = 3)) ############################################## test_that("1. Input checks", { @@ -293,3 +299,39 @@ RPSS(exp2, obs2_1) ) }) +############################################## +test_that("4. Output checks: dat3", { + +# ref = NULL +expect_equal( +as.vector(RPSS(exp3, obs3, dat_dim = 'dataset')$rpss), +c(-18.2622), +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp3, obs3, dat_dim = 'dataset')$sign), +TRUE, +) +expect_equal( +as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', Fair = T)$rpss), +c(-39.82927), +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', indices_for_clim = 3:5)$rpss), +-21.71094, +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), +c(-14.06061), +tolerance = 0.0001 +) +# ref = ref +expect_equal( +as.vector(RPSS(exp3, obs3, ref3, dat_dim = 'dataset')$rpss), +0.07874016, +tolerance = 0.0001 +) + +}) \ No newline at end of file -- GitLab From 0fd362e8ad592eb902aa7582a78d331879df238d Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Fri, 5 Aug 2022 16:23:06 +0200 Subject: [PATCH 060/140] Development dat_dim NULL corrected --- R/RPS.R | 59 +++-- R/RPSS.R | 50 +++-- man/RPS.Rd | 11 +- man/RPSS.Rd | 11 +- tests/testthat/test-RPS.R | 122 +++++----- tests/testthat/test-RPSS.R | 441 +++++++++++++++++++------------------ 6 files changed, 376 insertions(+), 318 deletions(-) diff --git a/R/RPS.R b/R/RPS.R index 1c41d11..52eb0a4 100644 --- a/R/RPS.R +++ b/R/RPS.R @@ -17,9 +17,9 @@ #' dimension. The dimensions must be the same as 'exp' except 'memb_dim'. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. -#'@param dat_dim A character string indicating the name of dataset (nobs/nexp) -#' dimension. The length of this dimension can be different between ’exp’ and ’obs’. -#' The default value is NULL. +#'@param dat_dim A character string indicating the name of dataset dimension. +#' The length of this dimension can be different between ’exp’ and ’obs’. +#' The default value is NULL. #'@param memb_dim A character string indicating the name of the member dimension #' to compute the probabilities of the forecast. The default value is 'member'. #'@param prob_thresholds A numeric vector of the relative thresholds (from 0 to @@ -31,9 +31,10 @@ #'@param Fair A logical indicating whether to compute the FairRPSS (the #' potential RPSS that the forecast would have with an infinite ensemble size). #' The default value is FALSE. -#'@param weights A named two-dimensional numerical array of the weights for each -#' member and time. The dimension names should include 'memb_dim' and -#' 'time_dim'. The default value is NULL. The ensemble should have at least 70 +#'@param weights A named numerical array of the weights for each +#' member and time. If 'dat_dim' is NULL the dimension names should include 'memb_dim' +#' and 'time_dim'. Else, dimension names should include also dat_dim dimension. +#' The default value is NULL. The ensemble should have at least 70 #' members or span at least 10 time steps and have more than 45 members if #' consistency between the weighted and unweighted methodologies is desired. #'@param ncores An integer indicating the number of cores to use for parallel @@ -132,8 +133,13 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL if (!is.null(weights)) { if (!is.array(weights) | !is.numeric(weights)) stop('Parameter "weights" must be a two-dimensional numeric array.') - if (length(dim(weights)) != 2 | any(!names(dim(weights)) %in% c(memb_dim, time_dim))) - stop("Parameter 'weights' must have two dimensions with the names of memb_dim and time_dim.") + if (is.null(dat_dim)) { + if (length(dim(weights)) != 2 | any(!names(dim(weights)) %in% c(memb_dim, time_dim))) + stop("Parameter 'weights' must have two dimensions with the names of memb_dim and time_dim.") + } else { + if (length(dim(weights)) != 3 | any(!names(dim(weights)) %in% c(memb_dim, time_dim, dat_dim))) + stop("Parameter 'weights' must have three dimensions with the names of memb_dim, time_dim and dat_dim.") + } if (dim(weights)[memb_dim] != dim(exp)[memb_dim] | dim(weights)[time_dim] != dim(exp)[time_dim]) { stop("Parameter 'weights' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") @@ -161,6 +167,7 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL obs = target_dims_obs), output_dims = time_dim, fun = .RPS, + dat_dim = dat_dim, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights = weights, ncores = ncores)$output1 @@ -172,13 +179,14 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL } -.RPS <- function(exp, obs, prob_thresholds = c(1/3, 2/3), +.RPS <- function(exp, obs, dat_dim = NULL, prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights = NULL) { # exp: [sdate, memb] # obs: [sdate, (memb)] - exp_probs <- .get_probs(data = exp, indices_for_quantiles = indices_for_clim, prob_thresholds = prob_thresholds, weights = weights) + exp_probs <- .get_probs(data = exp, dat_dim = dat_dim, indices_for_quantiles = indices_for_clim, + prob_thresholds = prob_thresholds, weights = weights) # exp_probs: [bin, sdate] - obs_probs <- .get_probs(data = obs, indices_for_quantiles = indices_for_clim, + obs_probs <- .get_probs(data = obs, dat_dim = dat_dim, indices_for_quantiles = indices_for_clim, prob_thresholds = prob_thresholds, weights = NULL) # obs_probs: [bin, sdate] @@ -199,22 +207,29 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL return(rps) } -.get_probs <- function(data, indices_for_quantiles, prob_thresholds, weights = NULL) { +.get_probs <- function(data, dat_dim = NULL, indices_for_quantiles, prob_thresholds, weights = NULL) { - # if exp: [sdate, memb] - # if obs: [sdate, (memb)] + # if exp: [sdate, memb, (dat_dim)] + # if obs: [sdate, (memb), (dat_dim)] - # Add dim [length = 1] to obs if it doesn't have memb_dim or dat_dim + # Add dim [memb = 1] to obs if it doesn't have memb_dim if (length(dim(data)) == 1) dim(data) <- c(dim(data), 1) - # Add dim [length = 1] to obs if it doesn't have memb_dim or dat_dim - if (length(dim(data)) == 2) dim(data) <- c(dim(data), 1) + + # Add dim [dat_dim = 1] if dat_dim is NULL + if (is.null(dat_dim)) { + dim(data) <- c(dim(data), 1) + if (!is.null(weights)) { + dim(weights) <- c(dim(weights), 1) + } + } + # Absolute thresholds if (is.null(weights)) { - quantiles <- quantile(x = as.vector(data[indices_for_quantiles, ,]), probs = prob_thresholds, type = 8, na.rm = TRUE) + quantiles <- quantile(x = as.vector(data[indices_for_quantiles, , ]), probs = prob_thresholds, type = 8, na.rm = TRUE) } else { - # weights: [sdate, memb] - sorted_arrays <- .sorted_distributions(data[indices_for_quantiles, , ], weights[indices_for_quantiles, ]) + # weights: [sdate, memb, (dat_dim)] + sorted_arrays <- .sorted_distributions(data[indices_for_quantiles, , ], weights[indices_for_quantiles, , ]) sorted_data <- sorted_arrays$data cumulative_weights <- sorted_arrays$cumulative_weights quantiles <- approx(cumulative_weights, sorted_data, prob_thresholds, "linear")$y @@ -223,13 +238,13 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL # Probabilities probs <- array(dim = c(bin = length(quantiles) + 1, dim(data)[1])) # [bin, sdate] for (i_time in 1:dim(data)[1]) { - if (anyNA(data[i_time, ,])) { + if (anyNA(data[i_time, , ])) { probs[, i_time] <- rep(NA, dim = length(quantiles) + 1) } else { if (is.null(weights)) { probs[, i_time] <- colMeans(easyVerification::convert2prob(data[i_time, , ], threshold = quantiles)) } else { - sorted_arrays <- .sorted_distributions(data[i_time, , ], weights[i_time, ]) + sorted_arrays <- .sorted_distributions(data[i_time, , ], weights[i_time, , ]) sorted_data <- sorted_arrays$data cumulative_weights <- sorted_arrays$cumulative_weights # find any quantiles that are outside the data range diff --git a/R/RPSS.R b/R/RPSS.R index 7d4012e..20eaa0e 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -22,6 +22,9 @@ #' forecast. The default value is NULL. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. +#'@param dat_dim A character string indicating the name of dataset dimension. +#' The length of this dimension can be different between ’exp’ and ’obs’. +#' The default value is NULL. #'@param memb_dim A character string indicating the name of the member dimension #' to compute the probabilities of the forecast and the reference forecast. The #' default value is 'member'. @@ -36,9 +39,10 @@ #' The default value is FALSE. #'@param weights Deprecated and will be removed in the next release. Please use #' 'weights_exp' and 'weights_ref' instead. -#'@param weights_exp A named two-dimensional numerical array of the forecast -#' ensemble weights for each member and time. The dimension names should -#' include 'memb_dim' and 'time_dim'. The default value is NULL. The ensemble +#'@param weights_exp A named numerical array of the forecast +#' ensemble weights for each member and time. If 'dat_dim' is NULL the dimension +#' names should include 'memb_dim' and 'time_dim'. Else, dimension names should +#' include also dat_dim dimension. The default value is NULL. The ensemble #' should have at least 70 members or span at least 10 time steps and have #' more than 45 members if consistency between the weighted and unweighted #' methodologies is desired. @@ -178,26 +182,41 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', ## weights_exp if (!is.null(weights_exp)) { if (!is.array(weights_exp) | !is.numeric(weights_exp)) - stop('Parameter "weights_exp" must be a two-dimensional numeric array.') - if (length(dim(weights_exp)) != 2 | any(!names(dim(weights_exp)) %in% c(memb_dim, time_dim))) - stop("Parameter 'weights_exp' must have two dimensions with the names of memb_dim and time_dim.") + stop('Parameter "weights_exp" must be a numeric array.') + if (is.null(dat_dim)) { + if (length(dim(weights_exp)) != 2 | any(!names(dim(weights_exp)) %in% c(memb_dim, time_dim))) + stop("Parameter 'weights_exp' must have two dimensions with the names of memb_dim and time_dim.") + } else { + if (length(dim(weights_exp)) != 3 | any(!names(dim(weights_exp)) %in% c(memb_dim, time_dim, dat_dim))) + stop("Parameter 'weights_exp' must have three dimensions with the names of memb_dim, time_dim and dat_dim.") + } if (dim(weights_exp)[memb_dim] != dim(exp)[memb_dim] | dim(weights_exp)[time_dim] != dim(exp)[time_dim]) { - stop("Parameter 'weights_exp' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") + if (is.null(dat_dim)) { + stop("Parameter 'weights_exp' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") + } else { + stop("Parameter 'weights_exp' must have the same dimension lengths as memb_dim, time_dim and dat_dim in 'exp'.") + } + } - weights_exp <- Reorder(weights_exp, c(time_dim, memb_dim)) + weights_exp <- Reorder(weights_exp, c(time_dim, memb_dim, dat_dim)) } ## weights_ref if (!is.null(weights_ref)) { if (!is.array(weights_ref) | !is.numeric(weights_ref)) stop('Parameter "weights_ref" must be a two-dimensional numeric array.') - if (length(dim(weights_ref)) != 2 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim))) - stop("Parameter 'weights_ref' must have two dimensions with the names of memb_dim and time_dim.") + if (is.null(dat_dim)) { + if (length(dim(weights_ref)) != 2 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim))) + stop("Parameter 'weights' must have two dimensions with the names of memb_dim and time_dim.") + } else { + if (length(dim(weights_ref)) != 3 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim, dat_dim))) + stop("Parameter 'weights' must have three dimensions with the names of memb_dim, time_dim and dat_dim.") + } if (dim(weights_ref)[memb_dim] != dim(exp)[memb_dim] | dim(weights_ref)[time_dim] != dim(exp)[time_dim]) { stop("Parameter 'weights_ref' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") } - weights_ref <- Reorder(weights_ref, c(time_dim, memb_dim)) + weights_ref <- Reorder(weights_ref, c(time_dim, memb_dim, dat_dim)) } ## ncores if (!is.null(ncores)) { @@ -229,6 +248,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', output <- Apply(data, target_dims = target_dims, fun = .RPSS, + dat_dim = dat_dim, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights_exp = weights_exp, @@ -238,7 +258,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', return(output) } -.RPSS <- function(exp, obs, ref = NULL, prob_thresholds = c(1/3, 2/3), +.RPSS <- function(exp, obs, ref = NULL, dat_dim = NULL, prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights_exp = NULL, weights_ref = NULL) { # exp: [sdate, memb] @@ -246,12 +266,12 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', # ref: [sdate, memb] or NULL # RPS of the forecast - rps_exp <- .RPS(exp = exp, obs = obs, prob_thresholds = prob_thresholds, + rps_exp <- .RPS(exp = exp, obs = obs, dat_dim = dat_dim, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights = weights_exp) # RPS of the reference forecast if (is.null(ref)) { ## using climatology as reference forecast - obs_probs <- .get_probs(data = obs, indices_for_quantiles = indices_for_clim, + obs_probs <- .get_probs(data = obs, dat_dim = dat_dim, indices_for_quantiles = indices_for_clim, prob_thresholds = prob_thresholds, weights = NULL) # obs_probs: [bin, sdate] @@ -275,7 +295,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', # } } else { # use "ref" as reference forecast - rps_ref <- .RPS(exp = ref, obs = obs, prob_thresholds = prob_thresholds, + rps_ref <- .RPS(exp = ref, obs = obs, dat_dim = dat_dim, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights = weights_ref) } diff --git a/man/RPS.Rd b/man/RPS.Rd index d7fb734..cad8fbd 100644 --- a/man/RPS.Rd +++ b/man/RPS.Rd @@ -30,8 +30,8 @@ The default value is 'sdate'.} \item{memb_dim}{A character string indicating the name of the member dimension to compute the probabilities of the forecast. The default value is 'member'.} -\item{dat_dim}{A character string indicating the name of dataset (nobs/nexp) - dimension. The length of this dimension can be different between ’exp’ and ’obs’. +\item{dat_dim}{A character string indicating the name of dataset dimension. +The length of this dimension can be different between ’exp’ and ’obs’. The default value is NULL.} \item{prob_thresholds}{A numeric vector of the relative thresholds (from 0 to @@ -46,9 +46,10 @@ the whole period is used. The default value is NULL.} potential RPSS that the forecast would have with an infinite ensemble size). The default value is FALSE.} -\item{weights}{A named two-dimensional numerical array of the weights for each -member and time. The dimension names should include 'memb_dim' and -'time_dim'. The default value is NULL. The ensemble should have at least 70 +\item{weights}{A named numerical array of the weights for each +member and time. If 'dat_dim' is NULL the dimension names should include 'memb_dim' +and 'time_dim'. Else, dimension names should include also dat_dim dimension. +The default value is NULL. The ensemble should have at least 70 members or span at least 10 time steps and have more than 45 members if consistency between the weighted and unweighted methodologies is desired.} diff --git a/man/RPSS.Rd b/man/RPSS.Rd index e9df825..54257b8 100644 --- a/man/RPSS.Rd +++ b/man/RPSS.Rd @@ -39,6 +39,10 @@ The default value is 'sdate'.} to compute the probabilities of the forecast and the reference forecast. The default value is 'member'.} +\item{dat_dim}{A character string indicating the name of dataset dimension. +The length of this dimension can be different between ’exp’ and ’obs’. +The default value is NULL.} + \item{prob_thresholds}{A numeric vector of the relative thresholds (from 0 to 1) between the categories. The default value is c(1/3, 2/3), which corresponds to tercile equiprobable categories.} @@ -54,9 +58,10 @@ The default value is FALSE.} \item{weights}{Deprecated and will be removed in the next release. Please use 'weights_exp' and 'weights_ref' instead.} -\item{weights_exp}{A named two-dimensional numerical array of the forecast -ensemble weights for each member and time. The dimension names should -include 'memb_dim' and 'time_dim'. The default value is NULL. The ensemble +\item{weights_exp}{A named numerical array of the forecast +ensemble weights for each member and time. If 'dat_dim' is NULL the dimension +names should include 'memb_dim' and 'time_dim'. Else, dimension names should +include also dat_dim dimension. The default value is NULL. The ensemble should have at least 70 members or span at least 10 time steps and have more than 45 members if consistency between the weighted and unweighted methodologies is desired.} diff --git a/tests/testthat/test-RPS.R b/tests/testthat/test-RPS.R index 6d6e2d9..e920c94 100644 --- a/tests/testthat/test-RPS.R +++ b/tests/testthat/test-RPS.R @@ -105,77 +105,77 @@ test_that("1. Input checks", { ############################################## test_that("2. Output checks: dat1", { -expect_equal( -dim(RPS(exp1, obs1)), -c(lat = 2) -) -expect_equal( -as.vector(RPS(exp1, obs1)), -c(0.3555556, 0.4444444), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPS(exp1, obs1, Fair = T)), -c(0.2000000, 0.2666667), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPS(exp1, obs1, indices_for_clim = 3:5)), -c(0.5000000, 0.4666667), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPS(exp1, obs1, prob_thresholds = seq(0.1, 0.9, 0.1))), -c(1.600000, 1.888889), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPS(exp1, obs1, weights = weights1)), -c(0.3692964, 0.5346627), -tolerance = 0.0001 -) + expect_equal( + dim(RPS(exp1, obs1)), + c(lat = 2) + ) + expect_equal( + as.vector(RPS(exp1, obs1)), + c(0.3555556, 0.4444444), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPS(exp1, obs1, Fair = T)), + c(0.2000000, 0.2666667), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPS(exp1, obs1, indices_for_clim = 3:5)), + c(0.5000000, 0.4666667), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPS(exp1, obs1, prob_thresholds = seq(0.1, 0.9, 0.1))), + c(1.600000, 1.888889), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPS(exp1, obs1, weights = weights1)), + c(0.3692964, 0.5346627), + tolerance = 0.0001 + ) }) ############################################## test_that("3. Output checks: dat2", { -expect_equal( -dim(RPS(exp2, obs2)), -NULL -) -expect_equal( -RPS(exp2, obs2), -0.75, -tolerance = 0.0001 -) -expect_equal( -RPS(exp2, obs2, indices_for_clim = 2:5, prob_thresholds = seq(0.1, 0.9, 0.1)), -2.75, -tolerance = 0.0001 -) -expect_equal( -RPS(exp2, obs2), -RPS(exp2_1, obs2_1) -) + expect_equal( + dim(RPS(exp2, obs2)), + NULL + ) + expect_equal( + RPS(exp2, obs2), + 0.75, + tolerance = 0.0001 + ) + expect_equal( + RPS(exp2, obs2, indices_for_clim = 2:5, prob_thresholds = seq(0.1, 0.9, 0.1)), + 2.75, + tolerance = 0.0001 + ) + expect_equal( + RPS(exp2, obs2), + RPS(exp2_1, obs2_1) + ) }) ############################################## test_that("3. Output checks: dat3", { -expect_equal( -dim(RPS(exp3, obs3, dat_dim = 'dataset')), -NULL -) -expect_equal( -RPS(exp3, obs3, dat_dim = 'dataset'), -2.3444, -tolerance = 0.0001 -) -expect_equal( -RPS(exp3, obs3, dat_dim = 'dataset', indices_for_clim = 2:5, prob_thresholds = seq(0.1, 0.9, 0.1)), -4.7722, -tolerance = 0.0001 -) + expect_equal( + dim(RPS(exp3, obs3, dat_dim = 'dataset')), + NULL + ) + expect_equal( + RPS(exp3, obs3, dat_dim = 'dataset'), + 2.3444, + tolerance = 0.0001 + ) + expect_equal( + RPS(exp3, obs3, dat_dim = 'dataset', indices_for_clim = 2:5, prob_thresholds = seq(0.1, 0.9, 0.1)), + 4.7722, + tolerance = 0.0001 + ) }) diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index 428e970..9ada2b7 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -27,6 +27,8 @@ set.seed(2) obs3 <- array(rnorm(10), dim = c(member = 1, sdate = 10, dataset = 2)) set.seed(3) ref3 <- array(rnorm(20), dim = c(member = 2, sdate = 10, dataset = 3)) +set.seed(4) +weights3 <- array(abs(rnorm(60)), dim = c(member = 2, sdate = 10, dataset = 3)) ############################################## test_that("1. Input checks", { @@ -69,11 +71,11 @@ test_that("1. Input checks", { # exp, ref, and obs (2) expect_error( RPSS(exp1, array(1:9, dim = c(sdate = 9))), - "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." ) expect_error( RPSS(exp1, obs1, ref2), - "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." ) # prob_thresholds expect_error( @@ -97,7 +99,7 @@ test_that("1. Input checks", { # weights_exp and weights_ref expect_error( RPSS(exp1, obs1, weights_exp = c(0, 1)), - 'Parameter "weights_exp" must be a two-dimensional numeric array.' + 'Parameter "weights_exp" must be a numeric array.' ) expect_error( RPSS(exp1, obs1, weights_exp = array(1, dim = c(member = 3, time = 10))), @@ -118,220 +120,235 @@ test_that("1. Input checks", { ############################################## test_that("2. Output checks: dat1", { -expect_equal( -names(RPSS(exp1, obs1)), -c("rpss", "sign") -) -expect_equal( -names(RPSS(exp1, obs1, ref1)), -c("rpss", "sign") -) -expect_equal( -dim(RPSS(exp1, obs1)$rpss), -c(lat = 2) -) -expect_equal( -dim(RPSS(exp1, obs1)$sign), -c(lat = 2) -) -expect_equal( -dim(RPSS(exp1, obs1, ref1)$rpss), -c(lat = 2) -) -expect_equal( -dim(RPSS(exp1, obs1, ref1)$sign), -c(lat = 2) -) -# ref = NULL -expect_equal( -as.vector(RPSS(exp1, obs1)$rpss), -c(0.15789474, -0.05263158), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1)$sign), -c(FALSE, FALSE), -) -expect_equal( -as.vector(RPSS(exp1, obs1, Fair = T)$rpss), -c(0.5263158, 0.3684211), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, indices_for_clim = 3:5)$rpss), -c(-0.4062500, -0.1052632), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), -c(0.03030303, -0.14478114), -tolerance = 0.0001 -) -# ref = ref -expect_equal( -as.vector(RPSS(exp1, obs1, ref1)$rpss), -c(0.5259259, 0.4771242), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1, weights_ref = weights1)$rpss), -c(0.6596424, 0.4063579), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1)$sign), -c(FALSE, FALSE) -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1, Fair = T)$rpss), -c(0.6000000, 0.6190476), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$rpss), -c(0.2857143, 0.4166667), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$sign), -c(FALSE, FALSE) -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), -c(0.4754098, 0.3703704), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), -c(T, F) -) + expect_equal( + names(RPSS(exp1, obs1)), + c("rpss", "sign") + ) + expect_equal( + names(RPSS(exp1, obs1, ref1)), + c("rpss", "sign") + ) + expect_equal( + dim(RPSS(exp1, obs1)$rpss), + c(lat = 2) + ) + expect_equal( + dim(RPSS(exp1, obs1)$sign), + c(lat = 2) + ) + expect_equal( + dim(RPSS(exp1, obs1, ref1)$rpss), + c(lat = 2) + ) + expect_equal( + dim(RPSS(exp1, obs1, ref1)$sign), + c(lat = 2) + ) + # ref = NULL + expect_equal( + as.vector(RPSS(exp1, obs1)$rpss), + c(0.15789474, -0.05263158), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1)$sign), + c(FALSE, FALSE), + ) + expect_equal( + as.vector(RPSS(exp1, obs1, Fair = T)$rpss), + c(0.5263158, 0.3684211), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, indices_for_clim = 3:5)$rpss), + c(-0.4062500, -0.1052632), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + c(0.03030303, -0.14478114), + tolerance = 0.0001 + ) + # ref = ref + expect_equal( + as.vector(RPSS(exp1, obs1, ref1)$rpss), + c(0.5259259, 0.4771242), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1, weights_ref = weights1)$rpss), + c(0.6596424, 0.4063579), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1)$sign), + c(FALSE, FALSE) + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1, Fair = T)$rpss), + c(0.6000000, 0.6190476), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$rpss), + c(0.2857143, 0.4166667), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$sign), + c(FALSE, FALSE) + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + c(0.4754098, 0.3703704), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), + c(T, F) + ) }) ############################################## test_that("3. Output checks: dat2", { -expect_equal( -names(RPSS(exp2, obs2)), -c("rpss", "sign") -) -expect_equal( -names(RPSS(exp2, obs2, ref2)), -c("rpss", "sign") -) -expect_equal( -dim(RPSS(exp2, obs2)$rpss), -NULL -) -expect_equal( -dim(RPSS(exp2, obs2)$sign), -NULL -) -expect_equal( -dim(RPSS(exp2, obs2, ref2)$rpss), -NULL -) -expect_equal( -dim(RPSS(exp2, obs2, ref2)$sign), -NULL -) -# ref = NULL -expect_equal( -as.vector(RPSS(exp2, obs2)$rpss), -c(-0.7763158), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2)$sign), -FALSE, -) -expect_equal( -as.vector(RPSS(exp2, obs2, Fair = T)$rpss), -c(-0.1842105), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2, indices_for_clim = 3:5)$rpss), --0.8984375, -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), -c(-0.7272727), -tolerance = 0.0001 -) -# ref = ref -expect_equal( -as.vector(RPSS(exp2, obs2, ref2)$rpss), -0, -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2, ref2)$sign), -FALSE -) -expect_equal( -as.vector(RPSS(exp2, obs2, ref2, Fair = T)$rpss), -0, -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$rpss), -0.03571429, -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$sign), -FALSE -) -expect_equal( -as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), -0.06557377, -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), -FALSE -) -expect_equal( -RPSS(exp2, obs2), -RPSS(exp2, obs2_1) -) - -}) -############################################## -test_that("4. Output checks: dat3", { + expect_equal( + names(RPSS(exp2, obs2)), + c("rpss", "sign") + ) + expect_equal( + names(RPSS(exp2, obs2, ref2)), + c("rpss", "sign") + ) + expect_equal( + dim(RPSS(exp2, obs2)$rpss), + NULL + ) + expect_equal( + dim(RPSS(exp2, obs2)$sign), + NULL + ) + expect_equal( + dim(RPSS(exp2, obs2, ref2)$rpss), + NULL + ) + expect_equal( + dim(RPSS(exp2, obs2, ref2)$sign), + NULL + ) + # ref = NULL + expect_equal( + as.vector(RPSS(exp2, obs2)$rpss), + c(-0.7763158), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2)$sign), + FALSE, + ) + expect_equal( + as.vector(RPSS(exp2, obs2, Fair = T)$rpss), + c(-0.1842105), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2, indices_for_clim = 3:5)$rpss), + -0.8984375, + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + c(-0.7272727), + tolerance = 0.0001 + ) + # ref = ref + expect_equal( + as.vector(RPSS(exp2, obs2, ref2)$rpss), + 0, + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2, ref2)$sign), + FALSE + ) + expect_equal( + as.vector(RPSS(exp2, obs2, ref2, Fair = T)$rpss), + 0, + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$rpss), + 0.03571429, + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$sign), + FALSE + ) + expect_equal( + as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + 0.06557377, + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), + FALSE + ) + expect_equal( + RPSS(exp2, obs2), + RPSS(exp2, obs2_1) + ) -# ref = NULL -expect_equal( -as.vector(RPSS(exp3, obs3, dat_dim = 'dataset')$rpss), -c(-18.2622), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp3, obs3, dat_dim = 'dataset')$sign), -TRUE, -) -expect_equal( -as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', Fair = T)$rpss), -c(-39.82927), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', indices_for_clim = 3:5)$rpss), --21.71094, -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), -c(-14.06061), -tolerance = 0.0001 -) -# ref = ref -expect_equal( -as.vector(RPSS(exp3, obs3, ref3, dat_dim = 'dataset')$rpss), -0.07874016, -tolerance = 0.0001 -) + }) + ############################################## + test_that("4. Output checks: dat3", { -}) \ No newline at end of file + # ref = NULL + expect_equal( + as.vector(RPSS(exp3, obs3, dat_dim = 'dataset')$rpss), + c(-18.2622), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp3, obs3, dat_dim = 'dataset')$sign), + TRUE, + ) + expect_equal( + as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', Fair = T)$rpss), + c(-39.82927), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', indices_for_clim = 3:5)$rpss), + -21.71094, + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + c(-14.06061), + tolerance = 0.0001 + ) + # ref = ref + expect_equal( + as.vector(RPSS(exp3, obs3, ref3, dat_dim = 'dataset')$rpss), + 0.07874016, + tolerance = 0.0001 + ) + # weights_exp and weights_ref + expect_error( + RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = weights1), + "Parameter 'weights_exp' must have three dimensions with the names of memb_dim, time_dim and dat_dim." + ) + expect_error( + RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = array(1, dim = c(member = 3, sdate = 1, dataset = 1))), + "Parameter 'weights_exp' must have the same dimension lengths as memb_dim, time_dim and dat_dim in 'exp'." + ) + suppressWarnings( + expect_equal( + as.vector(RPSS(exp3, obs3, ref3, dat_dim = 'dataset', weights_exp = weights3, weights_ref = weights3)$rpss), + c(-0.06109242), + tolerance = 0.0001 + ) + ) +}) -- GitLab From e459c6dda97fdb64410eff002c6c1eff3b825af8 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Mon, 8 Aug 2022 13:28:46 +0200 Subject: [PATCH 061/140] Corrected text output from test-RPS --- R/RPS.R | 8 ++++---- tests/testthat/test-RPS.R | 12 ++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/R/RPS.R b/R/RPS.R index 52eb0a4..f9ce1e2 100644 --- a/R/RPS.R +++ b/R/RPS.R @@ -132,17 +132,17 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL ## weights if (!is.null(weights)) { if (!is.array(weights) | !is.numeric(weights)) - stop('Parameter "weights" must be a two-dimensional numeric array.') + stop("Parameter 'weights' must be a two-dimensional numeric array.") if (is.null(dat_dim)) { if (length(dim(weights)) != 2 | any(!names(dim(weights)) %in% c(memb_dim, time_dim))) - stop("Parameter 'weights' must have two dimensions with the names of memb_dim and time_dim.") + stop("Parameter 'weights' must have two dimensions with the names of 'memb_dim' and 'time_dim'.") } else { if (length(dim(weights)) != 3 | any(!names(dim(weights)) %in% c(memb_dim, time_dim, dat_dim))) - stop("Parameter 'weights' must have three dimensions with the names of memb_dim, time_dim and dat_dim.") + stop("Parameter 'weights' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'.") } if (dim(weights)[memb_dim] != dim(exp)[memb_dim] | dim(weights)[time_dim] != dim(exp)[time_dim]) { - stop("Parameter 'weights' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") + stop("Parameter 'weights' must have the same dimension lengths as 'memb_dim' and 'time_dim' in 'exp'.") } weights <- Reorder(weights, c(time_dim, memb_dim)) } diff --git a/tests/testthat/test-RPS.R b/tests/testthat/test-RPS.R index e920c94..1e0c971 100644 --- a/tests/testthat/test-RPS.R +++ b/tests/testthat/test-RPS.R @@ -60,7 +60,7 @@ test_that("1. Input checks", { # exp, ref, and obs (2) expect_error( RPS(exp1, array(1:9, dim = c(sdate = 9))), - "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." ) # prob_thresholds expect_error( @@ -84,15 +84,19 @@ test_that("1. Input checks", { # weights expect_error( RPS(exp1, obs1, weights = c(0, 1)), - 'Parameter "weights" must be a two-dimensional numeric array.' + "Parameter 'weights' must be a two-dimensional numeric array." ) expect_error( RPS(exp1, obs1, weights = array(1, dim = c(member = 3, time = 10))), - "Parameter 'weights' must have two dimensions with the names of memb_dim and time_dim." + "Parameter 'weights' must have two dimensions with the names of 'memb_dim' and 'time_dim'." ) expect_error( RPS(exp1, obs1, weights = array(1, dim = c(member = 3, sdate = 1))), - "Parameter 'weights' must have the same dimension lengths as memb_dim and time_dim in 'exp'." + "Parameter 'weights' must have the same dimension lengths as 'memb_dim' and 'time_dim' in 'exp'." + ) + expect_error( + RPS(exp3, obs3, weights = array(1, dim = c(member = 3, time = 10, dataset = 3)), dat_dim = 'dataset'), + "Parameter 'weights' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'." ) # ncores expect_error( -- GitLab From 95079c4fa6dc88ae4bbc6a568a8c34760125128e Mon Sep 17 00:00:00 2001 From: aho Date: Mon, 8 Aug 2022 14:43:14 +0200 Subject: [PATCH 062/140] Improve document and fix some bugs --- R/RPS.R | 77 ++++++++++++++++++++++++++++++++---------------------- man/RPS.Rd | 18 +++++++------ 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/R/RPS.R b/R/RPS.R index 52eb0a4..2794f9b 100644 --- a/R/RPS.R +++ b/R/RPS.R @@ -9,12 +9,14 @@ #'categories. In the case of a forecast divided into two categories (the lowest #'number of categories that a probabilistic forecast can have), the RPS #'corresponds to the Brier Score (BS; Wilks, 2011), therefore, ranges between 0 -#'and 1. +#'and 1. If there is more than one dataset, RPS will be computed for each pair +#'of exp and obs data. #' #'@param exp A named numerical array of the forecast with at least time #' dimension. #'@param obs A named numerical array of the observation with at least time -#' dimension. The dimensions must be the same as 'exp' except 'memb_dim'. +#' dimension. The dimensions must be the same as 'exp' except 'memb_dim' and +#' 'dat_dim'. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. #'@param dat_dim A character string indicating the name of dataset dimension. @@ -31,12 +33,12 @@ #'@param Fair A logical indicating whether to compute the FairRPSS (the #' potential RPSS that the forecast would have with an infinite ensemble size). #' The default value is FALSE. -#'@param weights A named numerical array of the weights for each -#' member and time. If 'dat_dim' is NULL the dimension names should include 'memb_dim' -#' and 'time_dim'. Else, dimension names should include also dat_dim dimension. -#' The default value is NULL. The ensemble should have at least 70 -#' members or span at least 10 time steps and have more than 45 members if -#' consistency between the weighted and unweighted methodologies is desired. +#'@param weights A named numerical array of the weights for 'exp'. If 'dat_dim' +#' is NULL, the dimension should include 'memb_dim' and 'time_dim'. Else, the +#' dimension should also include 'dat_dim'. The default value is NULL. The +#' ensemble should have at least 70 members or span at least 10 time steps and +#' have more than 45 members if consistency between the weighted and unweighted +#' methodologies is desired. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -75,6 +77,13 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL if (!time_dim %in% names(dim(exp)) | !time_dim %in% names(dim(obs))) { stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } + ## memb_dim + if (!is.character(memb_dim) | length(memb_dim) > 1) { + stop("Parameter 'memb_dim' must be a character string.") + } + if (!memb_dim %in% names(dim(exp))) { + stop("Parameter 'memb_dim' is not found in 'exp' dimension.") + } ## dat_dim if (!is.null(dat_dim)) { if (!is.character(dat_dim) | length(dat_dim) > 1) { @@ -85,13 +94,6 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL " Set it as NULL if there is no dataset dimension.") } } - ## memb_dim - if (!is.character(memb_dim) | length(memb_dim) > 1) { - stop("Parameter 'memb_dim' must be a character string.") - } - if (!memb_dim %in% names(dim(exp))) { - stop("Parameter 'memb_dim' is not found in 'exp' dimension.") - } ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) @@ -136,15 +138,25 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL if (is.null(dat_dim)) { if (length(dim(weights)) != 2 | any(!names(dim(weights)) %in% c(memb_dim, time_dim))) stop("Parameter 'weights' must have two dimensions with the names of memb_dim and time_dim.") + if (dim(weights)[memb_dim] != dim(exp)[memb_dim] | + dim(weights)[time_dim] != dim(exp)[time_dim]) { + stop(paste0("Parameter 'weights' must have the same dimension lengths ", + "as memb_dim and time_dim in 'exp'.")) + } + weights <- Reorder(weights, c(time_dim, memb_dim)) + } else { - if (length(dim(weights)) != 3 | any(!names(dim(weights)) %in% c(memb_dim, time_dim, dat_dim))) - stop("Parameter 'weights' must have three dimensions with the names of memb_dim, time_dim and dat_dim.") - } - if (dim(weights)[memb_dim] != dim(exp)[memb_dim] | - dim(weights)[time_dim] != dim(exp)[time_dim]) { - stop("Parameter 'weights' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") + if (length(dim(weights)) != 3 | any(!names(dim(weights)) %in% c(memb_dim, time_dim, dat_dim))) + stop("Parameter 'weights' must have three dimensions with the names of memb_dim, time_dim, and dat_dim.") + if (dim(weights)[memb_dim] != dim(exp)[memb_dim] | + dim(weights)[time_dim] != dim(exp)[time_dim] | + dim(weights)[dat_dim] != dim(exp)[dat_dim]) { + stop(paste0("Parameter 'weights' must have the same dimension lengths ", + "as memb_dim, time_dim, and dat_dim in 'exp'.")) + } + weights <- Reorder(weights, c(time_dim, memb_dim, dat_dim)) + } - weights <- Reorder(weights, c(time_dim, memb_dim)) } ## ncores if (!is.null(ncores)) { @@ -171,7 +183,7 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights = weights, ncores = ncores)$output1 - # browser() + # Return only the mean RPS rps <- MeanDims(rps, time_dim, na.rm = FALSE) @@ -181,12 +193,14 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL .RPS <- function(exp, obs, dat_dim = NULL, prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights = NULL) { - # exp: [sdate, memb] - # obs: [sdate, (memb)] - exp_probs <- .get_probs(data = exp, dat_dim = dat_dim, indices_for_quantiles = indices_for_clim, + # exp: [sdate, memb, (dat)] + # obs: [sdate, (memb), (dat)] + exp_probs <- .get_probs(data = exp, time_dim =time_dim, memb_dim = memb_dim, + dat_dim = dat_dim, indices_for_quantiles = indices_for_clim, prob_thresholds = prob_thresholds, weights = weights) # exp_probs: [bin, sdate] - obs_probs <- .get_probs(data = obs, dat_dim = dat_dim, indices_for_quantiles = indices_for_clim, + obs_probs <- .get_probs(data = obs, time_dim =time_dim, memb_dim = memb_dim, + dat_dim = dat_dim, indices_for_quantiles = indices_for_clim, prob_thresholds = prob_thresholds, weights = NULL) # obs_probs: [bin, sdate] @@ -207,13 +221,14 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL return(rps) } -.get_probs <- function(data, dat_dim = NULL, indices_for_quantiles, prob_thresholds, weights = NULL) { +.get_probs <- function(data, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, + indices_for_quantiles, prob_thresholds, weights = NULL) { - # if exp: [sdate, memb, (dat_dim)] - # if obs: [sdate, (memb), (dat_dim)] + # if exp: [sdate, memb, (dat)] + # if obs: [sdate, (memb), (dat)] # Add dim [memb = 1] to obs if it doesn't have memb_dim - if (length(dim(data)) == 1) dim(data) <- c(dim(data), 1) + if (!memb_dim %in% names(dim(data))) dim(data) <- c(dim(data), 1) # Add dim [dat_dim = 1] if dat_dim is NULL if (is.null(dat_dim)) { diff --git a/man/RPS.Rd b/man/RPS.Rd index cad8fbd..db2996f 100644 --- a/man/RPS.Rd +++ b/man/RPS.Rd @@ -22,7 +22,8 @@ RPS( dimension.} \item{obs}{A named numerical array of the observation with at least time -dimension. The dimensions must be the same as 'exp' except 'memb_dim'.} +dimension. The dimensions must be the same as 'exp' except 'memb_dim' and +'dat_dim'.} \item{time_dim}{A character string indicating the name of the time dimension. The default value is 'sdate'.} @@ -46,12 +47,12 @@ the whole period is used. The default value is NULL.} potential RPSS that the forecast would have with an infinite ensemble size). The default value is FALSE.} -\item{weights}{A named numerical array of the weights for each -member and time. If 'dat_dim' is NULL the dimension names should include 'memb_dim' -and 'time_dim'. Else, dimension names should include also dat_dim dimension. -The default value is NULL. The ensemble should have at least 70 -members or span at least 10 time steps and have more than 45 members if -consistency between the weighted and unweighted methodologies is desired.} +\item{weights}{A named numerical array of the weights for 'exp'. If 'dat_dim' +is NULL, the dimension should include 'memb_dim' and 'time_dim'. Else, the +dimension should also include 'dat_dim'. The default value is NULL. The +ensemble should have at least 70 members or span at least 10 time steps and +have more than 45 members if consistency between the weighted and unweighted +methodologies is desired.} \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} @@ -70,7 +71,8 @@ of multi-categorical probabilistic forecasts. The RPS ranges between 0 categories. In the case of a forecast divided into two categories (the lowest number of categories that a probabilistic forecast can have), the RPS corresponds to the Brier Score (BS; Wilks, 2011), therefore, ranges between 0 -and 1. +and 1. If there is more than one dataset, RPS will be computed for each pair +of exp and obs data. } \examples{ exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) -- GitLab From a874cac86d8992ba9e5022090e55482ebc448bb7 Mon Sep 17 00:00:00 2001 From: aho Date: Mon, 8 Aug 2022 17:16:37 +0200 Subject: [PATCH 063/140] Fix the bug of coastline when lon is not continuous --- R/PlotEquiMap.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/R/PlotEquiMap.R b/R/PlotEquiMap.R index ee10ec3..6405333 100644 --- a/R/PlotEquiMap.R +++ b/R/PlotEquiMap.R @@ -748,9 +748,10 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, # ~~~~~~~~~~~~~~~~~ # latb <- sort(lat, index.return = TRUE) - dlon <- lon[2:dims[1]] - lon[1:(dims[1] - 1)] + dlon <- diff(lon) wher <- which(dlon > (mean(dlon) + 1)) if (length(wher) > 0) { + warning("Detect gap in 'lon' vector, which is considered as crossing the border.") lon[(wher + 1):dims[1]] <- lon[(wher + 1):dims[1]] - 360 } lonb <- sort(lon, index.return = TRUE) @@ -942,7 +943,7 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, # Plotting continents # ~~~~~~~~~~~~~~~~~~~~~ # - wrap_vec <- c(lon[1], lon[1] + 360) + wrap_vec <- c(lonb$x[1], lonb$x[1] + 360) old_lwd <- par('lwd') par(lwd = coast_width) # If [0, 360], use GEOmap; if [-180, 180], use maps::map -- GitLab From acdb270c0598cf6e911416d3681f6d23fd968ab6 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Tue, 9 Aug 2022 18:12:46 +0200 Subject: [PATCH 064/140] Correct development of dat_dim NULL in RPS --- R/RPS.R | 136 ++++++++++++++++++++++++-------------- tests/testthat/test-RPS.R | 11 +-- 2 files changed, 94 insertions(+), 53 deletions(-) diff --git a/R/RPS.R b/R/RPS.R index ff45058..9578132 100644 --- a/R/RPS.R +++ b/R/RPS.R @@ -1,3 +1,4 @@ +#123456789#123456789#123456789#123456789#123456789#123456789#123456789#123456789 #'Compute the Ranked Probability Score #' #'The Ranked Probability Score (RPS; Wilks, 2011) is defined as the sum of the @@ -43,8 +44,10 @@ #' computation. The default value is NULL. #' #'@return -#'A numerical array of RPS with the same dimensions as "exp" except the -#''time_dim' and 'memb_dim' dimensions. +#'A numerical array of RPS with dimensions c(nexp, nobs, the rest dimensions of +#''exp' except 'time_dim' and 'memb_dim' dimensions. nexp is the number of +#'experiment (i.e., dat_dim in exp), and nobs is the number of observation (i.e. +#', dat_dim in obs). If dat_dim is NULL, nexp and nobs are omitted. #' #'@references #'Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 @@ -137,22 +140,22 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL stop("Parameter 'weights' must be a two-dimensional numeric array.") if (is.null(dat_dim)) { if (length(dim(weights)) != 2 | any(!names(dim(weights)) %in% c(memb_dim, time_dim))) - stop("Parameter 'weights' must have two dimensions with the names of memb_dim and time_dim.") + stop("Parameter 'weights' must have two dimensions with the names of 'memb_dim' and 'time_dim'.") if (dim(weights)[memb_dim] != dim(exp)[memb_dim] | dim(weights)[time_dim] != dim(exp)[time_dim]) { stop(paste0("Parameter 'weights' must have the same dimension lengths ", - "as memb_dim and time_dim in 'exp'.")) + "as 'memb_dim' and 'time_dim' in 'exp'.")) } weights <- Reorder(weights, c(time_dim, memb_dim)) } else { if (length(dim(weights)) != 3 | any(!names(dim(weights)) %in% c(memb_dim, time_dim, dat_dim))) - stop("Parameter 'weights' must have three dimensions with the names of memb_dim, time_dim, and dat_dim.") + stop("Parameter 'weights' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'.") if (dim(weights)[memb_dim] != dim(exp)[memb_dim] | dim(weights)[time_dim] != dim(exp)[time_dim] | dim(weights)[dat_dim] != dim(exp)[dat_dim]) { stop(paste0("Parameter 'weights' must have the same dimension lengths ", - "as memb_dim, time_dim, and dat_dim in 'exp'.")) + "as 'memb_dim', 'time_dim' and 'dat_dim' in 'exp'.")) } weights <- Reorder(weights, c(time_dim, memb_dim, dat_dim)) @@ -177,9 +180,9 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL rps <- Apply(data = list(exp = exp, obs = obs), target_dims = list(exp = c(time_dim, memb_dim, dat_dim), obs = target_dims_obs), - output_dims = time_dim, fun = .RPS, - dat_dim = dat_dim, + dat_dim = dat_dim, time_dim = time_dim, + memb_dim = memb_dim, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights = weights, ncores = ncores)$output1 @@ -191,60 +194,97 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL } -.RPS <- function(exp, obs, dat_dim = NULL, prob_thresholds = c(1/3, 2/3), - indices_for_clim = NULL, Fair = FALSE, weights = NULL) { +.RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, + prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights = NULL) { # exp: [sdate, memb, (dat)] # obs: [sdate, (memb), (dat)] - exp_probs <- .get_probs(data = exp, time_dim = time_dim, memb_dim = memb_dim, - dat_dim = dat_dim, indices_for_quantiles = indices_for_clim, - prob_thresholds = prob_thresholds, weights = weights) - # exp_probs: [bin, sdate] - obs_probs <- .get_probs(data = obs, time_dim = time_dim, memb_dim = memb_dim, - dat_dim = dat_dim, indices_for_quantiles = indices_for_clim, - prob_thresholds = prob_thresholds, weights = NULL) - # obs_probs: [bin, sdate] - probs_exp_cumsum <- apply(exp_probs, 2, cumsum) - probs_obs_cumsum <- apply(obs_probs, 2, cumsum) - rps <- apply((probs_exp_cumsum - probs_obs_cumsum)^2, 2, sum) - # rps: [sdate] + # Add dim [memb = 1] to obs if it doesn't have memb_dim + if (!is.null(memb_dim)) { + if (!memb_dim %in% names(dim(obs))) obs <- InsertDim(obs, posdim = 2, lendim = 1, name = memb_dim) + } - if (Fair) { # FairRPS - ## adjustment <- rowSums(-1 * (1/R - 1/R.new) * ens.cum * (R - ens.cum)/R/(R - 1)) [formula taken from SpecsVerification::EnsRps] - R <- dim(exp)[2] #memb - R_new <- Inf - adjustment <- (-1) / (R - 1) * probs_exp_cumsum * (1 - probs_exp_cumsum) - adjustment <- apply(adjustment, 2, sum) - rps <- rps + adjustment + if (is.null(memb_dim)) { + exp_memb <- 1 + obs_memb <- 1 + dim(exp) <- c(dim(exp), member = exp_memb) + dim(obs) <- c(dim(obs), member = obs_memb) + if (!is.null(weights)) dim(weights) <- c(dim(weights), member = exp_memb) + } else { + obs_memb <- 1 + if (!memb_dim %in% names(dim(obs))) dim(obs) <- c(dim(obs), member = obs_memb) + } + if (is.null(dat_dim)) { + nexp <- 1 + nobs <- 1 + dim(exp) <- c(dim(exp), nexp = nexp) + dim(obs) <- c(dim(obs), nobs = nobs) + if (!is.null(weights)) dim(weights) <- c(dim(weights), nexp = nexp) + } else { + nexp <- as.numeric(dim(exp)[dat_dim]) + nobs <- as.numeric(dim(obs)[dat_dim]) } - return(rps) -} + rps <- array(dim = c(dim(exp)[time_dim], nexp = nexp, nobs = nobs)) -.get_probs <- function(data, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, - indices_for_quantiles, prob_thresholds, weights = NULL) { + for (i in 1:nexp) { + for (j in 1:nobs) { + exp_data <- exp[ , , i] + obs_data <- obs[ , , j] - # if exp: [sdate, memb, (dat)] - # if obs: [sdate, (memb), (dat)] + if (is.null(dim(exp_data))) dim(exp_data) <- c(dim(exp)[time_dim], member = 1) + if (is.null(dim(obs_data))) dim(obs_data) <- c(dim(obs)[time_dim], member = 1) - # Add dim [memb = 1] to obs if it doesn't have memb_dim - if (!memb_dim %in% names(dim(data))) dim(data) <- c(dim(data), 1) + if (!is.null(weights)) { + weights_data <- weights[ , , i] + if (is.null(dim(weights_data))) dim(weights_data) <- c(dim(weights)[time_dim], member = 1) + } else { + weights_data <- weights + } + # exp_probs: [bin, sdate] + exp_probs <- .get_probs(data = exp_data, indices_for_quantiles = indices_for_clim, + prob_thresholds = prob_thresholds, weights = weights_data) + + obs_probs <- .get_probs(data = obs_data, indices_for_quantiles = indices_for_clim, + prob_thresholds = prob_thresholds, weights = NULL) + # obs_probs: [bin, sdate] + probs_exp_cumsum <- apply(exp_probs, 2, cumsum) + probs_obs_cumsum <- apply(obs_probs, 2, cumsum) - # Add dim [dat_dim = 1] if dat_dim is NULL - if (is.null(dat_dim)) { - dim(data) <- c(dim(data), 1) - if (!is.null(weights)) { - dim(weights) <- c(dim(weights), 1) + # rps: [sdate, (nexp), (nobs)] + rps[ , i, j] <- apply((probs_exp_cumsum - probs_obs_cumsum)^2, 2, sum) + + if (Fair) { # FairRPS + ## adjustment <- rowSums(-1 * (1/R - 1/R.new) * ens.cum * (R - ens.cum)/R/(R - 1)) [formula taken from SpecsVerification::EnsRps] + R <- dim(exp)[2] #memb + R_new <- Inf + adjustment <- (-1) / (R - 1) * probs_exp_cumsum * (1 - probs_exp_cumsum) + adjustment <- apply(adjustment, 2, sum) + rps[ , i, j] <- rps[ , i, j] + adjustment + } } } + if (is.null(dat_dim)) { + dim(rps) <- dim(exp)[time_dim] + } + + return(rps) +} + +.get_probs <- function(data, indices_for_quantiles, prob_thresholds, weights = NULL) { + # if exp: [sdate, memb] + # if obs: [sdate, memb] + + # Add dim [memb = 1] to obs if it doesn't have memb_dim + if (length(dim(data)) == 1) dim(data) <- c(dim(data), 1) # Absolute thresholds if (is.null(weights)) { - quantiles <- quantile(x = as.vector(data[indices_for_quantiles, , ]), probs = prob_thresholds, type = 8, na.rm = TRUE) + quantiles <- quantile(x = as.vector(data[indices_for_quantiles, ]), probs = prob_thresholds, type = 8, na.rm = TRUE) } else { - # weights: [sdate, memb, (dat_dim)] - sorted_arrays <- .sorted_distributions(data[indices_for_quantiles, , ], weights[indices_for_quantiles, , ]) + # weights: [sdate, memb] + sorted_arrays <- .sorted_distributions(data[indices_for_quantiles, ], weights[indices_for_quantiles, ]) sorted_data <- sorted_arrays$data cumulative_weights <- sorted_arrays$cumulative_weights quantiles <- approx(cumulative_weights, sorted_data, prob_thresholds, "linear")$y @@ -253,13 +293,13 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL # Probabilities probs <- array(dim = c(bin = length(quantiles) + 1, dim(data)[1])) # [bin, sdate] for (i_time in 1:dim(data)[1]) { - if (anyNA(data[i_time, , ])) { + if (anyNA(data[i_time, ])) { probs[, i_time] <- rep(NA, dim = length(quantiles) + 1) } else { if (is.null(weights)) { - probs[, i_time] <- colMeans(easyVerification::convert2prob(data[i_time, , ], threshold = quantiles)) + probs[, i_time] <- colMeans(easyVerification::convert2prob(data[i_time, ], threshold = quantiles)) } else { - sorted_arrays <- .sorted_distributions(data[i_time, , ], weights[i_time, , ]) + sorted_arrays <- .sorted_distributions(data[i_time, ], weights[i_time, ]) sorted_data <- sorted_arrays$data cumulative_weights <- sorted_arrays$cumulative_weights # find any quantiles that are outside the data range diff --git a/tests/testthat/test-RPS.R b/tests/testthat/test-RPS.R index 1e0c971..dd3b29b 100644 --- a/tests/testthat/test-RPS.R +++ b/tests/testthat/test-RPS.R @@ -9,6 +9,7 @@ set.seed(2) obs1 <- array(rnorm(20), dim = c(sdate = 10, lat = 2)) set.seed(3) weights1 <- array(abs(rnorm(30)), dim = c(member = 3, sdate = 10)) + # dat2 set.seed(1) exp2 <- array(rnorm(20), dim = c(member = 2, sdate = 10)) @@ -169,16 +170,16 @@ test_that("3. Output checks: dat3", { expect_equal( dim(RPS(exp3, obs3, dat_dim = 'dataset')), - NULL + c(nexp = 2, nobs = 3) ) expect_equal( - RPS(exp3, obs3, dat_dim = 'dataset'), - 2.3444, + as.vector(RPS(exp3, obs3, dat_dim = 'dataset')), + c(0.75, 0.65, 0.75, 0.85, 0.75, 0.55), tolerance = 0.0001 ) expect_equal( - RPS(exp3, obs3, dat_dim = 'dataset', indices_for_clim = 2:5, prob_thresholds = seq(0.1, 0.9, 0.1)), - 4.7722, + as.vector(RPS(exp3, obs3, dat_dim = 'dataset', indices_for_clim = 2:5, prob_thresholds = seq(0.1, 0.9, 0.1))), + c(2.75, 2.45, 2.55, 2.55, 2.65, 2.15), tolerance = 0.0001 ) -- GitLab From a63ce2ea735747b1eaf99db72688c6ecd9889b47 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Tue, 9 Aug 2022 18:23:39 +0200 Subject: [PATCH 065/140] Revert changes of RPSS and test-RPSS --- R/RPSS.R | 89 +++----- tests/testthat/test-RPSS.R | 413 ++++++++++++++++--------------------- 2 files changed, 200 insertions(+), 302 deletions(-) diff --git a/R/RPSS.R b/R/RPSS.R index 20eaa0e..4552d74 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -22,9 +22,6 @@ #' forecast. The default value is NULL. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. -#'@param dat_dim A character string indicating the name of dataset dimension. -#' The length of this dimension can be different between ’exp’ and ’obs’. -#' The default value is NULL. #'@param memb_dim A character string indicating the name of the member dimension #' to compute the probabilities of the forecast and the reference forecast. The #' default value is 'member'. @@ -39,10 +36,9 @@ #' The default value is FALSE. #'@param weights Deprecated and will be removed in the next release. Please use #' 'weights_exp' and 'weights_ref' instead. -#'@param weights_exp A named numerical array of the forecast -#' ensemble weights for each member and time. If 'dat_dim' is NULL the dimension -#' names should include 'memb_dim' and 'time_dim'. Else, dimension names should -#' include also dat_dim dimension. The default value is NULL. The ensemble +#'@param weights_exp A named two-dimensional numerical array of the forecast +#' ensemble weights for each member and time. The dimension names should +#' include 'memb_dim' and 'time_dim'. The default value is NULL. The ensemble #' should have at least 70 members or span at least 10 time steps and have #' more than 45 members if consistency between the weighted and unweighted #' methodologies is desired. @@ -73,7 +69,7 @@ #' #'@import multiApply #'@export -RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, +RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights = NULL, weights_exp = NULL, weights_ref = NULL, ncores = NULL) { @@ -96,22 +92,6 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!is.null(ref) & !time_dim %in% names(dim(ref))) { stop("Parameter 'time_dim' is not found in 'ref' dimension.") } - ## dat_dim - if (!is.null(dat_dim)) { - if (!is.character(dat_dim) | length(dat_dim) > 1) { - stop("Parameter 'dat_dim' must be a character string.") - } - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp', 'obs' or in 'ref' dimension.", - " Set it as NULL if there is no dataset dimension.") - } - if (!is.null(ref)) { - if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - stop("Parameter 'dat_dim' is not found in 'exp', 'obs' or in 'ref' dimension.", - " Set it as NULL if there is no dataset dimension.") - } - } - } ## memb_dim if (!is.character(memb_dim) | length(memb_dim) > 1) { stop("Parameter 'memb_dim' must be a character string.") @@ -129,25 +109,18 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (memb_dim %in% name_obs) { name_obs <- name_obs[-which(name_obs == memb_dim)] } - if (!is.null(dat_dim)) { - name_exp <- name_exp[-which(name_exp == dat_dim)] - name_obs <- name_obs[-which(name_obs == dat_dim)] - } if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions except 'memb_dim' and 'dat_dim'.")) + "all dimensions except 'memb_dim'.")) } if (!is.null(ref)) { name_ref <- sort(names(dim(ref))) name_ref <- name_ref[-which(name_ref == memb_dim)] - if (!is.null(dat_dim)) { - name_ref <- name_ref[-which(name_ref == dat_dim)] - } if (!identical(length(name_exp), length(name_ref)) | !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions except 'memb_dim' and 'dat_dim'.")) + "all dimensions except 'memb_dim'.")) } } ## prob_thresholds @@ -182,41 +155,26 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', ## weights_exp if (!is.null(weights_exp)) { if (!is.array(weights_exp) | !is.numeric(weights_exp)) - stop('Parameter "weights_exp" must be a numeric array.') - if (is.null(dat_dim)) { - if (length(dim(weights_exp)) != 2 | any(!names(dim(weights_exp)) %in% c(memb_dim, time_dim))) - stop("Parameter 'weights_exp' must have two dimensions with the names of memb_dim and time_dim.") - } else { - if (length(dim(weights_exp)) != 3 | any(!names(dim(weights_exp)) %in% c(memb_dim, time_dim, dat_dim))) - stop("Parameter 'weights_exp' must have three dimensions with the names of memb_dim, time_dim and dat_dim.") - } + stop('Parameter "weights_exp" must be a two-dimensional numeric array.') + if (length(dim(weights_exp)) != 2 | any(!names(dim(weights_exp)) %in% c(memb_dim, time_dim))) + stop("Parameter 'weights_exp' must have two dimensions with the names of memb_dim and time_dim.") if (dim(weights_exp)[memb_dim] != dim(exp)[memb_dim] | dim(weights_exp)[time_dim] != dim(exp)[time_dim]) { - if (is.null(dat_dim)) { - stop("Parameter 'weights_exp' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") - } else { - stop("Parameter 'weights_exp' must have the same dimension lengths as memb_dim, time_dim and dat_dim in 'exp'.") - } - + stop("Parameter 'weights_exp' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") } - weights_exp <- Reorder(weights_exp, c(time_dim, memb_dim, dat_dim)) + weights_exp <- Reorder(weights_exp, c(time_dim, memb_dim)) } ## weights_ref if (!is.null(weights_ref)) { if (!is.array(weights_ref) | !is.numeric(weights_ref)) stop('Parameter "weights_ref" must be a two-dimensional numeric array.') - if (is.null(dat_dim)) { - if (length(dim(weights_ref)) != 2 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim))) - stop("Parameter 'weights' must have two dimensions with the names of memb_dim and time_dim.") - } else { - if (length(dim(weights_ref)) != 3 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim, dat_dim))) - stop("Parameter 'weights' must have three dimensions with the names of memb_dim, time_dim and dat_dim.") - } + if (length(dim(weights_ref)) != 2 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim))) + stop("Parameter 'weights_ref' must have two dimensions with the names of memb_dim and time_dim.") if (dim(weights_ref)[memb_dim] != dim(exp)[memb_dim] | dim(weights_ref)[time_dim] != dim(exp)[time_dim]) { stop("Parameter 'weights_ref' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") } - weights_ref <- Reorder(weights_ref, c(time_dim, memb_dim, dat_dim)) + weights_ref <- Reorder(weights_ref, c(time_dim, memb_dim)) } ## ncores if (!is.null(ncores)) { @@ -230,25 +188,24 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', # Compute RPSS if (!memb_dim %in% names(dim(obs))) { - target_dims_obs <- c(time_dim, dat_dim) + target_dims_obs <- time_dim } else { - target_dims_obs <- c(time_dim, memb_dim, dat_dim) + target_dims_obs <- c(time_dim, memb_dim) } if (!is.null(ref)) { # use "ref" as reference forecast data <- list(exp = exp, obs = obs, ref = ref) - target_dims = list(exp = c(time_dim, memb_dim, dat_dim), + target_dims = list(exp = c(time_dim, memb_dim), obs = target_dims_obs, - ref = c(time_dim, memb_dim, dat_dim)) + ref = c(time_dim, memb_dim)) } else { data <- list(exp = exp, obs = obs) - target_dims = list(exp = c(time_dim, memb_dim, dat_dim), + target_dims = list(exp = c(time_dim, memb_dim), obs = target_dims_obs) } output <- Apply(data, target_dims = target_dims, fun = .RPSS, - dat_dim = dat_dim, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights_exp = weights_exp, @@ -258,7 +215,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', return(output) } -.RPSS <- function(exp, obs, ref = NULL, dat_dim = NULL, prob_thresholds = c(1/3, 2/3), +.RPSS <- function(exp, obs, ref = NULL, prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights_exp = NULL, weights_ref = NULL) { # exp: [sdate, memb] @@ -266,12 +223,12 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', # ref: [sdate, memb] or NULL # RPS of the forecast - rps_exp <- .RPS(exp = exp, obs = obs, dat_dim = dat_dim, prob_thresholds = prob_thresholds, + rps_exp <- .RPS(exp = exp, obs = obs, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights = weights_exp) # RPS of the reference forecast if (is.null(ref)) { ## using climatology as reference forecast - obs_probs <- .get_probs(data = obs, dat_dim = dat_dim, indices_for_quantiles = indices_for_clim, + obs_probs <- .get_probs(data = obs, indices_for_quantiles = indices_for_clim, prob_thresholds = prob_thresholds, weights = NULL) # obs_probs: [bin, sdate] @@ -295,7 +252,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', # } } else { # use "ref" as reference forecast - rps_ref <- .RPS(exp = ref, obs = obs, dat_dim = dat_dim, prob_thresholds = prob_thresholds, + rps_ref <- .RPS(exp = ref, obs = obs, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights = weights_ref) } diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index 9ada2b7..a9edaff 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -20,15 +20,7 @@ set.seed(2) obs2_1 <- array(rnorm(10), dim = c(member = 1, sdate = 10)) set.seed(3) ref2 <- array(rnorm(20), dim = c(member = 2, sdate = 10)) -# dat3 -set.seed(1) -exp3 <- array(rnorm(20), dim = c(member = 2, sdate = 10, dataset = 3)) -set.seed(2) -obs3 <- array(rnorm(10), dim = c(member = 1, sdate = 10, dataset = 2)) -set.seed(3) -ref3 <- array(rnorm(20), dim = c(member = 2, sdate = 10, dataset = 3)) -set.seed(4) -weights3 <- array(abs(rnorm(60)), dim = c(member = 2, sdate = 10, dataset = 3)) + ############################################## test_that("1. Input checks", { @@ -71,11 +63,11 @@ test_that("1. Input checks", { # exp, ref, and obs (2) expect_error( RPSS(exp1, array(1:9, dim = c(sdate = 9))), - "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim'." ) expect_error( RPSS(exp1, obs1, ref2), - "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim'." ) # prob_thresholds expect_error( @@ -99,7 +91,7 @@ test_that("1. Input checks", { # weights_exp and weights_ref expect_error( RPSS(exp1, obs1, weights_exp = c(0, 1)), - 'Parameter "weights_exp" must be a numeric array.' + 'Parameter "weights_exp" must be a two-dimensional numeric array.' ) expect_error( RPSS(exp1, obs1, weights_exp = array(1, dim = c(member = 3, time = 10))), @@ -120,235 +112,184 @@ test_that("1. Input checks", { ############################################## test_that("2. Output checks: dat1", { - expect_equal( - names(RPSS(exp1, obs1)), - c("rpss", "sign") - ) - expect_equal( - names(RPSS(exp1, obs1, ref1)), - c("rpss", "sign") - ) - expect_equal( - dim(RPSS(exp1, obs1)$rpss), - c(lat = 2) - ) - expect_equal( - dim(RPSS(exp1, obs1)$sign), - c(lat = 2) - ) - expect_equal( - dim(RPSS(exp1, obs1, ref1)$rpss), - c(lat = 2) - ) - expect_equal( - dim(RPSS(exp1, obs1, ref1)$sign), - c(lat = 2) - ) - # ref = NULL - expect_equal( - as.vector(RPSS(exp1, obs1)$rpss), - c(0.15789474, -0.05263158), - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp1, obs1)$sign), - c(FALSE, FALSE), - ) - expect_equal( - as.vector(RPSS(exp1, obs1, Fair = T)$rpss), - c(0.5263158, 0.3684211), - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp1, obs1, indices_for_clim = 3:5)$rpss), - c(-0.4062500, -0.1052632), - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp1, obs1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), - c(0.03030303, -0.14478114), - tolerance = 0.0001 - ) - # ref = ref - expect_equal( - as.vector(RPSS(exp1, obs1, ref1)$rpss), - c(0.5259259, 0.4771242), - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1, weights_ref = weights1)$rpss), - c(0.6596424, 0.4063579), - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp1, obs1, ref1)$sign), - c(FALSE, FALSE) - ) - expect_equal( - as.vector(RPSS(exp1, obs1, ref1, Fair = T)$rpss), - c(0.6000000, 0.6190476), - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$rpss), - c(0.2857143, 0.4166667), - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$sign), - c(FALSE, FALSE) - ) - expect_equal( - as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), - c(0.4754098, 0.3703704), - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), - c(T, F) - ) +expect_equal( +names(RPSS(exp1, obs1)), +c("rpss", "sign") +) +expect_equal( +names(RPSS(exp1, obs1, ref1)), +c("rpss", "sign") +) +expect_equal( +dim(RPSS(exp1, obs1)$rpss), +c(lat = 2) +) +expect_equal( +dim(RPSS(exp1, obs1)$sign), +c(lat = 2) +) +expect_equal( +dim(RPSS(exp1, obs1, ref1)$rpss), +c(lat = 2) +) +expect_equal( +dim(RPSS(exp1, obs1, ref1)$sign), +c(lat = 2) +) +# ref = NULL +expect_equal( +as.vector(RPSS(exp1, obs1)$rpss), +c(0.15789474, -0.05263158), +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp1, obs1)$sign), +c(FALSE, FALSE), +) +expect_equal( +as.vector(RPSS(exp1, obs1, Fair = T)$rpss), +c(0.5263158, 0.3684211), +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp1, obs1, indices_for_clim = 3:5)$rpss), +c(-0.4062500, -0.1052632), +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp1, obs1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), +c(0.03030303, -0.14478114), +tolerance = 0.0001 +) +# ref = ref +expect_equal( +as.vector(RPSS(exp1, obs1, ref1)$rpss), +c(0.5259259, 0.4771242), +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1, weights_ref = weights1)$rpss), +c(0.6596424, 0.4063579), +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp1, obs1, ref1)$sign), +c(FALSE, FALSE) +) +expect_equal( +as.vector(RPSS(exp1, obs1, ref1, Fair = T)$rpss), +c(0.6000000, 0.6190476), +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$rpss), +c(0.2857143, 0.4166667), +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$sign), +c(FALSE, FALSE) +) +expect_equal( +as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), +c(0.4754098, 0.3703704), +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), +c(T, F) +) }) ############################################## test_that("3. Output checks: dat2", { - expect_equal( - names(RPSS(exp2, obs2)), - c("rpss", "sign") - ) - expect_equal( - names(RPSS(exp2, obs2, ref2)), - c("rpss", "sign") - ) - expect_equal( - dim(RPSS(exp2, obs2)$rpss), - NULL - ) - expect_equal( - dim(RPSS(exp2, obs2)$sign), - NULL - ) - expect_equal( - dim(RPSS(exp2, obs2, ref2)$rpss), - NULL - ) - expect_equal( - dim(RPSS(exp2, obs2, ref2)$sign), - NULL - ) - # ref = NULL - expect_equal( - as.vector(RPSS(exp2, obs2)$rpss), - c(-0.7763158), - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp2, obs2)$sign), - FALSE, - ) - expect_equal( - as.vector(RPSS(exp2, obs2, Fair = T)$rpss), - c(-0.1842105), - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp2, obs2, indices_for_clim = 3:5)$rpss), - -0.8984375, - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp2, obs2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), - c(-0.7272727), - tolerance = 0.0001 - ) - # ref = ref - expect_equal( - as.vector(RPSS(exp2, obs2, ref2)$rpss), - 0, - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp2, obs2, ref2)$sign), - FALSE - ) - expect_equal( - as.vector(RPSS(exp2, obs2, ref2, Fair = T)$rpss), - 0, - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$rpss), - 0.03571429, - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$sign), - FALSE - ) - expect_equal( - as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), - 0.06557377, - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), - FALSE - ) - expect_equal( - RPSS(exp2, obs2), - RPSS(exp2, obs2_1) - ) - - }) - ############################################## - test_that("4. Output checks: dat3", { +expect_equal( +names(RPSS(exp2, obs2)), +c("rpss", "sign") +) +expect_equal( +names(RPSS(exp2, obs2, ref2)), +c("rpss", "sign") +) +expect_equal( +dim(RPSS(exp2, obs2)$rpss), +NULL +) +expect_equal( +dim(RPSS(exp2, obs2)$sign), +NULL +) +expect_equal( +dim(RPSS(exp2, obs2, ref2)$rpss), +NULL +) +expect_equal( +dim(RPSS(exp2, obs2, ref2)$sign), +NULL +) +# ref = NULL +expect_equal( +as.vector(RPSS(exp2, obs2)$rpss), +c(-0.7763158), +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp2, obs2)$sign), +FALSE, +) +expect_equal( +as.vector(RPSS(exp2, obs2, Fair = T)$rpss), +c(-0.1842105), +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp2, obs2, indices_for_clim = 3:5)$rpss), +-0.8984375, +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp2, obs2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), +c(-0.7272727), +tolerance = 0.0001 +) +# ref = ref +expect_equal( +as.vector(RPSS(exp2, obs2, ref2)$rpss), +0, +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp2, obs2, ref2)$sign), +FALSE +) +expect_equal( +as.vector(RPSS(exp2, obs2, ref2, Fair = T)$rpss), +0, +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$rpss), +0.03571429, +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$sign), +FALSE +) +expect_equal( +as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), +0.06557377, +tolerance = 0.0001 +) +expect_equal( +as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), +FALSE +) +expect_equal( +RPSS(exp2, obs2), +RPSS(exp2, obs2_1) +) - # ref = NULL - expect_equal( - as.vector(RPSS(exp3, obs3, dat_dim = 'dataset')$rpss), - c(-18.2622), - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp3, obs3, dat_dim = 'dataset')$sign), - TRUE, - ) - expect_equal( - as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', Fair = T)$rpss), - c(-39.82927), - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', indices_for_clim = 3:5)$rpss), - -21.71094, - tolerance = 0.0001 - ) - expect_equal( - as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), - c(-14.06061), - tolerance = 0.0001 - ) - # ref = ref - expect_equal( - as.vector(RPSS(exp3, obs3, ref3, dat_dim = 'dataset')$rpss), - 0.07874016, - tolerance = 0.0001 - ) - # weights_exp and weights_ref - expect_error( - RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = weights1), - "Parameter 'weights_exp' must have three dimensions with the names of memb_dim, time_dim and dat_dim." - ) - expect_error( - RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = array(1, dim = c(member = 3, sdate = 1, dataset = 1))), - "Parameter 'weights_exp' must have the same dimension lengths as memb_dim, time_dim and dat_dim in 'exp'." - ) - suppressWarnings( - expect_equal( - as.vector(RPSS(exp3, obs3, ref3, dat_dim = 'dataset', weights_exp = weights3, weights_ref = weights3)$rpss), - c(-0.06109242), - tolerance = 0.0001 - ) - ) }) -- GitLab From f6a9ea90c52fd06f6d297e15b46eb23d2a4dade4 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 10 Aug 2022 10:32:01 +0200 Subject: [PATCH 066/140] Correct insertDim member of function RPS.R --- R/RPS.R | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/R/RPS.R b/R/RPS.R index 9578132..6efd7f9 100644 --- a/R/RPS.R +++ b/R/RPS.R @@ -196,23 +196,16 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL .RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights = NULL) { + # exp: [sdate, memb, (dat)] # obs: [sdate, (memb), (dat)] - # Add dim [memb = 1] to obs if it doesn't have memb_dim - if (!is.null(memb_dim)) { - if (!memb_dim %in% names(dim(obs))) obs <- InsertDim(obs, posdim = 2, lendim = 1, name = memb_dim) - } - if (is.null(memb_dim)) { - exp_memb <- 1 - obs_memb <- 1 - dim(exp) <- c(dim(exp), member = exp_memb) - dim(obs) <- c(dim(obs), member = obs_memb) - if (!is.null(weights)) dim(weights) <- c(dim(weights), member = exp_memb) + exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'member') + obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'member') + if (!is.null(weights)) weights <- InsertDim(weights, posdim = 2, lendim = 1, name = 'member') } else { - obs_memb <- 1 - if (!memb_dim %in% names(dim(obs))) dim(obs) <- c(dim(obs), member = obs_memb) + if (!memb_dim %in% names(dim(obs))) obs <- InsertDim(obs, posdim = 2, lendim = 1, name = memb_dim) } if (is.null(dat_dim)) { nexp <- 1 -- GitLab From 5d48179cd98af28d8ffcdf3cfd90e0e2dccc6057 Mon Sep 17 00:00:00 2001 From: aho Date: Wed, 10 Aug 2022 12:09:15 +0200 Subject: [PATCH 067/140] Update doc --- man/RPSS.Rd | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/man/RPSS.Rd b/man/RPSS.Rd index 54257b8..893169d 100644 --- a/man/RPSS.Rd +++ b/man/RPSS.Rd @@ -10,7 +10,6 @@ RPSS( ref = NULL, time_dim = "sdate", memb_dim = "member", - dat_dim = NULL, prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, @@ -39,10 +38,6 @@ The default value is 'sdate'.} to compute the probabilities of the forecast and the reference forecast. The default value is 'member'.} -\item{dat_dim}{A character string indicating the name of dataset dimension. -The length of this dimension can be different between ’exp’ and ’obs’. -The default value is NULL.} - \item{prob_thresholds}{A numeric vector of the relative thresholds (from 0 to 1) between the categories. The default value is c(1/3, 2/3), which corresponds to tercile equiprobable categories.} @@ -58,10 +53,9 @@ The default value is FALSE.} \item{weights}{Deprecated and will be removed in the next release. Please use 'weights_exp' and 'weights_ref' instead.} -\item{weights_exp}{A named numerical array of the forecast -ensemble weights for each member and time. If 'dat_dim' is NULL the dimension -names should include 'memb_dim' and 'time_dim'. Else, dimension names should -include also dat_dim dimension. The default value is NULL. The ensemble +\item{weights_exp}{A named two-dimensional numerical array of the forecast +ensemble weights for each member and time. The dimension names should +include 'memb_dim' and 'time_dim'. The default value is NULL. The ensemble should have at least 70 members or span at least 10 time steps and have more than 45 members if consistency between the weighted and unweighted methodologies is desired.} -- GitLab From 9c8b3e61db4f5c649529929aa4206cf9b1a059b9 Mon Sep 17 00:00:00 2001 From: aho Date: Wed, 10 Aug 2022 12:09:37 +0200 Subject: [PATCH 068/140] Final revision of RPS.R for dat_dim development --- R/RPS.R | 32 ++++++++++++++------------------ man/RPS.Rd | 6 ++++-- tests/testthat/test-RPS.R | 12 ++++++++++++ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/R/RPS.R b/R/RPS.R index 6efd7f9..369f8dd 100644 --- a/R/RPS.R +++ b/R/RPS.R @@ -1,4 +1,3 @@ -#123456789#123456789#123456789#123456789#123456789#123456789#123456789#123456789 #'Compute the Ranked Probability Score #' #'The Ranked Probability Score (RPS; Wilks, 2011) is defined as the sum of the @@ -45,7 +44,7 @@ #' #'@return #'A numerical array of RPS with dimensions c(nexp, nobs, the rest dimensions of -#''exp' except 'time_dim' and 'memb_dim' dimensions. nexp is the number of +#''exp' except 'time_dim' and 'memb_dim' dimensions). nexp is the number of #'experiment (i.e., dat_dim in exp), and nobs is the number of observation (i.e. #', dat_dim in obs). If dat_dim is NULL, nexp and nobs are omitted. #' @@ -195,18 +194,15 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL .RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, - prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights = NULL) { + prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights = NULL) { # exp: [sdate, memb, (dat)] # obs: [sdate, (memb), (dat)] + # weights: NULL or same as exp + + # Adjust dimensions to be [sdate, memb, dat] for both exp and obs + if (!memb_dim %in% names(dim(obs))) obs <- InsertDim(obs, posdim = 2, lendim = 1, name = memb_dim) - if (is.null(memb_dim)) { - exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'member') - obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'member') - if (!is.null(weights)) weights <- InsertDim(weights, posdim = 2, lendim = 1, name = 'member') - } else { - if (!memb_dim %in% names(dim(obs))) obs <- InsertDim(obs, posdim = 2, lendim = 1, name = memb_dim) - } if (is.null(dat_dim)) { nexp <- 1 nobs <- 1 @@ -225,26 +221,26 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL exp_data <- exp[ , , i] obs_data <- obs[ , , j] - if (is.null(dim(exp_data))) dim(exp_data) <- c(dim(exp)[time_dim], member = 1) - if (is.null(dim(obs_data))) dim(obs_data) <- c(dim(obs)[time_dim], member = 1) + if (is.null(dim(exp_data))) dim(exp_data) <- c(dim(exp)[1:2]) + if (is.null(dim(obs_data))) dim(obs_data) <- c(dim(obs)[1:2]) if (!is.null(weights)) { weights_data <- weights[ , , i] - if (is.null(dim(weights_data))) dim(weights_data) <- c(dim(weights)[time_dim], member = 1) + if (is.null(dim(weights_data))) dim(weights_data) <- c(dim(weights)[1:2]) } else { weights_data <- weights } - # exp_probs: [bin, sdate] + exp_probs <- .get_probs(data = exp_data, indices_for_quantiles = indices_for_clim, - prob_thresholds = prob_thresholds, weights = weights_data) - + prob_thresholds = prob_thresholds, weights = weights_data) + # exp_probs: [bin, sdate] obs_probs <- .get_probs(data = obs_data, indices_for_quantiles = indices_for_clim, prob_thresholds = prob_thresholds, weights = NULL) # obs_probs: [bin, sdate] probs_exp_cumsum <- apply(exp_probs, 2, cumsum) probs_obs_cumsum <- apply(obs_probs, 2, cumsum) - # rps: [sdate, (nexp), (nobs)] + # rps: [sdate, nexp, nobs] rps[ , i, j] <- apply((probs_exp_cumsum - probs_obs_cumsum)^2, 2, sum) if (Fair) { # FairRPS @@ -267,7 +263,7 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL .get_probs <- function(data, indices_for_quantiles, prob_thresholds, weights = NULL) { # if exp: [sdate, memb] - # if obs: [sdate, memb] + # if obs: [sdate, (memb)] # Add dim [memb = 1] to obs if it doesn't have memb_dim if (length(dim(data)) == 1) dim(data) <- c(dim(data), 1) diff --git a/man/RPS.Rd b/man/RPS.Rd index db2996f..21b1e02 100644 --- a/man/RPS.Rd +++ b/man/RPS.Rd @@ -58,8 +58,10 @@ methodologies is desired.} computation. The default value is NULL.} } \value{ -A numerical array of RPS with the same dimensions as "exp" except the -'time_dim' and 'memb_dim' dimensions. +A numerical array of RPS with dimensions c(nexp, nobs, the rest dimensions of +'exp' except 'time_dim' and 'memb_dim' dimensions). nexp is the number of +experiment (i.e., dat_dim in exp), and nobs is the number of observation (i.e. +, dat_dim in obs). If dat_dim is NULL, nexp and nobs are omitted. } \description{ The Ranked Probability Score (RPS; Wilks, 2011) is defined as the sum of the diff --git a/tests/testthat/test-RPS.R b/tests/testthat/test-RPS.R index dd3b29b..b7d7f32 100644 --- a/tests/testthat/test-RPS.R +++ b/tests/testthat/test-RPS.R @@ -27,6 +27,8 @@ set.seed(1) exp3 <- array(rnorm(40), dim = c(member = 2, sdate = 10, dataset = 2)) set.seed(2) obs3 <- array(rnorm(30), dim = c(member = 1, sdate = 10, dataset = 3)) +set.seed(3) +weights3 <- array(abs(rnorm(30)), dim = c(member = 2, sdate = 10, dataset = 2)) ############################################## test_that("1. Input checks", { @@ -182,5 +184,15 @@ test_that("3. Output checks: dat3", { c(2.75, 2.45, 2.55, 2.55, 2.65, 2.15), tolerance = 0.0001 ) + # weights + expect_equal( + dim(RPS(exp3, obs3, weights = weights3, dat_dim = 'dataset')), + c(nexp = 2, nobs = 3) + ) + expect_equal( + as.vector(RPS(exp3, obs3, weights = weights3, dat_dim = 'dataset')), + c(0.7365024, 0.8316852, 0.6778686, 1.0256509, 0.8406320, 0.6385640), + tolerance = 0.0001 + ) }) -- GitLab From 448770e115cda8dc502556ad600b8f5cba7b3d89 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 10 Aug 2022 17:23:19 +0200 Subject: [PATCH 069/140] Allow dat_dim to be NULL in RPSS and added tests --- R/RPSS.R | 247 ++++++++++++++++------- man/RPSS.Rd | 21 +- tests/testthat/test-RPSS.R | 399 ++++++++++++++++++++----------------- 3 files changed, 411 insertions(+), 256 deletions(-) diff --git a/R/RPSS.R b/R/RPSS.R index 4552d74..a6e710b 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -8,23 +8,26 @@ #'reference forecast, while a negative value means that it has a lower skill. #'Examples of reference forecasts are the climatological forecast (same #'probabilities for all categories for all time steps), persistence, a previous -#'model version, and another model. It is computed as RPSS = 1 - RPS_exp / RPS_ref. -#'The statistical significance is obtained based on a Random Walk test at the -#'95% confidence level (DelSole and Tippett, 2016). +#'model version, and another model. It is computed as +#'RPSS = 1 - RPS_exp / RPS_ref. The statistical significance is obtained based +#'on a Random Walk test at the 95% confidence level (DelSole and Tippett, 2016). #' -#'@param exp A named numerical array of the forecast with at least time -#' dimension. +#'@param exp A named numerical array of the forecast with at least time and +#' member dimension. #'@param obs A named numerical array of the observation with at least time #' dimension. The dimensions must be the same as 'exp' except 'memb_dim'. #'@param ref A named numerical array of the reference forecast data with at -#' least time dimension. The dimensions must be the same as 'exp' except -#' 'memb_dim'. If it is NULL, the climatological forecast is used as reference -#' forecast. The default value is NULL. +#' least time and member dimension. The dimensions must be the same as 'exp' +#' except 'memb_dim' and 'dat_dim'. If it is NULL, the climatological forecast +#' is used as reference forecast. The default value is NULL. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. #'@param memb_dim A character string indicating the name of the member dimension #' to compute the probabilities of the forecast and the reference forecast. The #' default value is 'member'. +#'@param dat_dim A character string indicating the name of dataset dimension. +#' The length of this dimension can be different between ’exp’ and ’obs’. +#' The default value is NULL. #'@param prob_thresholds A numeric vector of the relative thresholds (from 0 to #' 1) between the categories. The default value is c(1/3, 2/3), which #' corresponds to tercile equiprobable categories. @@ -70,22 +73,29 @@ #'@import multiApply #'@export RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', - prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights = NULL, - weights_exp = NULL, weights_ref = NULL, ncores = NULL) { + dat_dim = NULL, prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, + Fair = FALSE, weights = NULL, weights_exp = NULL, weights_ref = NULL, ncores = NULL) { # Check inputs ## exp, obs, and ref (1) if (!is.array(exp) | !is.numeric(exp)) - stop('Parameter "exp" must be a numeric array.') + stop("Parameter 'exp' must be a numeric array.") if (!is.array(obs) | !is.numeric(obs)) - stop('Parameter "obs" must be a numeric array.') + stop("Parameter 'obs' must be a numeric array.") if (!is.null(ref)) { if (!is.array(ref) | !is.numeric(ref)) - stop('Parameter "ref" must be a numeric array.') + stop("Parameter 'ref' must be a numeric array.") + if (any(is.null(names(dim(ref))))| any(nchar(names(dim(ref))) == 0)) { + stop("Parameter 'ref' must have dimension names.") + } + } + if(any(is.null(names(dim(exp))))| any(nchar(names(dim(exp))) == 0) | + any(is.null(names(dim(obs))))| any(nchar(names(dim(obs))) == 0)) { + stop("Parameter 'exp' and 'obs' must have dimension names.") } ## time_dim if (!is.character(time_dim) | length(time_dim) != 1) - stop('Parameter "time_dim" must be a character string.') + stop("Parameter 'time_dim' must be a character string.") if (!time_dim %in% names(dim(exp)) | !time_dim %in% names(dim(obs))) { stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } @@ -102,6 +112,16 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!is.null(ref) & !memb_dim %in% names(dim(ref))) { stop("Parameter 'memb_dim' is not found in 'ref' dimension.") } + ## dat_dim + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + " Set it as NULL if there is no dataset dimension.") + } + } ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) @@ -109,18 +129,27 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (memb_dim %in% name_obs) { name_obs <- name_obs[-which(name_obs == memb_dim)] } + if (!is.null(dat_dim)) { + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] + } if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions except 'memb_dim'.")) + "all dimensions except 'memb_dim' and 'dat_dim'.")) } if (!is.null(ref)) { name_ref <- sort(names(dim(ref))) name_ref <- name_ref[-which(name_ref == memb_dim)] + if (!is.null(dat_dim)) { + if (dat_dim %in% name_obs) { + name_ref <- name_ref[-which(name_ref == dat_dim)] + } + } if (!identical(length(name_exp), length(name_ref)) | !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { - stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions except 'memb_dim'.")) + stop(paste0("Parameter 'exp' and 'ref' must have same length of ", + "all dimensions except 'memb_dim' and 'dat_dim'.")) } } ## prob_thresholds @@ -155,26 +184,52 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', ## weights_exp if (!is.null(weights_exp)) { if (!is.array(weights_exp) | !is.numeric(weights_exp)) - stop('Parameter "weights_exp" must be a two-dimensional numeric array.') - if (length(dim(weights_exp)) != 2 | any(!names(dim(weights_exp)) %in% c(memb_dim, time_dim))) - stop("Parameter 'weights_exp' must have two dimensions with the names of memb_dim and time_dim.") - if (dim(weights_exp)[memb_dim] != dim(exp)[memb_dim] | - dim(weights_exp)[time_dim] != dim(exp)[time_dim]) { - stop("Parameter 'weights_exp' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") + stop("Parameter 'weights_exp' must be a two-dimensional numeric array.") + if (is.null(dat_dim)) { + if (length(dim(weights_exp)) != 2 | any(!names(dim(weights_exp)) %in% c(memb_dim, time_dim))) + stop("Parameter 'weights_exp' must have two dimensions with the names of memb_dim and time_dim.") + if (dim(weights_exp)[memb_dim] != dim(exp)[memb_dim] | + dim(weights_exp)[time_dim] != dim(exp)[time_dim]) { + stop("Parameter 'weights_exp' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") + } + weights_exp <- Reorder(weights_exp, c(time_dim, memb_dim)) + } else { + if (length(dim(weights_exp)) != 3 | any(!names(dim(weights_exp)) %in% c(memb_dim, time_dim, dat_dim))) + stop("Parameter 'weights_exp' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'.") + if (dim(weights_exp)[memb_dim] != dim(exp)[memb_dim] | + dim(weights_exp)[time_dim] != dim(exp)[time_dim] | + dim(weights_exp)[dat_dim] != dim(exp)[dat_dim]) { + stop(paste0("Parameter 'weights_exp' must have the same dimension lengths ", + "as 'memb_dim', 'time_dim' and 'dat_dim' in 'exp'.")) + } + weights <- Reorder(weights_exp, c(time_dim, memb_dim, dat_dim)) } - weights_exp <- Reorder(weights_exp, c(time_dim, memb_dim)) + } ## weights_ref if (!is.null(weights_ref)) { if (!is.array(weights_ref) | !is.numeric(weights_ref)) stop('Parameter "weights_ref" must be a two-dimensional numeric array.') - if (length(dim(weights_ref)) != 2 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim))) - stop("Parameter 'weights_ref' must have two dimensions with the names of memb_dim and time_dim.") - if (dim(weights_ref)[memb_dim] != dim(exp)[memb_dim] | - dim(weights_ref)[time_dim] != dim(exp)[time_dim]) { - stop("Parameter 'weights_ref' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") + if (is.null(dat_dim)) { + if (length(dim(weights_ref)) != 2 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim))) + stop("Parameter 'weights_ref' must have two dimensions with the names of memb_dim and time_dim.") + if (dim(weights_ref)[memb_dim] != dim(exp)[memb_dim] | + dim(weights_ref)[time_dim] != dim(exp)[time_dim]) { + stop("Parameter 'weights_ref' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") + } + weights_ref <- Reorder(weights_ref, c(time_dim, memb_dim)) + } else { + if (length(dim(weights_ref)) != 3 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim, dat_dim))) + stop("Parameter 'weights_ref' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'.") + if (dim(weights_ref)[memb_dim] != dim(ref)[memb_dim] | + dim(weights_ref)[time_dim] != dim(ref)[time_dim] | + dim(weights_ref)[dat_dim] != dim(ref)[dat_dim]) { + stop(paste0("Parameter 'weights_ref' must have the same dimension lengths ", + "as 'memb_dim', 'time_dim' and 'dat_dim' in 'ref'.")) + } + weights <- Reorder(weights_ref, c(time_dim, memb_dim, dat_dim)) } - weights_ref <- Reorder(weights_ref, c(time_dim, memb_dim)) + } ## ncores if (!is.null(ncores)) { @@ -188,24 +243,31 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', # Compute RPSS if (!memb_dim %in% names(dim(obs))) { - target_dims_obs <- time_dim + target_dims_obs <- c(time_dim, dat_dim) } else { - target_dims_obs <- c(time_dim, memb_dim) + target_dims_obs <- c(time_dim, memb_dim, dat_dim) } if (!is.null(ref)) { # use "ref" as reference forecast + if ((!is.null(dat_dim)) && (!dat_dim %in% names(dim(ref)))) { + target_dims_ref <- c(time_dim, memb_dim) + } else { + target_dims_ref <- c(time_dim, memb_dim, dat_dim) + } data <- list(exp = exp, obs = obs, ref = ref) - target_dims = list(exp = c(time_dim, memb_dim), + target_dims = list(exp = c(time_dim, memb_dim, dat_dim), obs = target_dims_obs, - ref = c(time_dim, memb_dim)) + ref = target_dims_ref) } else { data <- list(exp = exp, obs = obs) - target_dims = list(exp = c(time_dim, memb_dim), + target_dims = list(exp = c(time_dim, memb_dim, dat_dim), obs = target_dims_obs) } output <- Apply(data, target_dims = target_dims, fun = .RPSS, + time_dim = time_dim, memb_dim = memb_dim, + dat_dim = dat_dim, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights_exp = weights_exp, @@ -213,54 +275,103 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', ncores = ncores) return(output) + } -.RPSS <- function(exp, obs, ref = NULL, prob_thresholds = c(1/3, 2/3), - indices_for_clim = NULL, Fair = FALSE, +.RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, + prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, weights_exp = NULL, weights_ref = NULL) { - # exp: [sdate, memb] - # obs: [sdate, (memb)] - # ref: [sdate, memb] or NULL + + # exp: [sdate, memb, (dat)] + # obs: [sdate, (memb), (dat)] + # ref: [sdate, memb, (dat)] or NULL + + if (is.null(dat_dim)) { + nexp <- 1 + nobs <- 1 + } else { + nexp <- as.numeric(dim(exp)[dat_dim]) + nobs <- as.numeric(dim(obs)[dat_dim]) + } # RPS of the forecast - rps_exp <- .RPS(exp = exp, obs = obs, prob_thresholds = prob_thresholds, - indices_for_clim = indices_for_clim, Fair = Fair, weights = weights_exp) + rps_exp <- .RPS(exp = exp, obs = obs, time_dim = time_dim, memb_dim = memb_dim, dat_dim = dat_dim, + prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, + Fair = Fair, weights = weights_exp) # RPS of the reference forecast if (is.null(ref)) { ## using climatology as reference forecast - obs_probs <- .get_probs(data = obs, indices_for_quantiles = indices_for_clim, - prob_thresholds = prob_thresholds, weights = NULL) - # obs_probs: [bin, sdate] + if (!memb_dim %in% names(dim(obs))) obs <- InsertDim(obs, posdim = 2, lendim = 1, name = memb_dim) + if (is.null(dat_dim)) dim(obs) <- c(dim(obs), nobs = nobs) + rps_ref <- array(dim = c(dim(obs)[time_dim], nobs = nobs)) + + for (j in 1:nobs) { + obs_data <- obs[ , , j] + if (is.null(dim(obs_data))) dim(obs_data) <- c(dim(obs)[1:2]) + obs_probs <- .get_probs(data = obs_data, indices_for_quantiles = indices_for_clim, + prob_thresholds = prob_thresholds, weights = NULL) + # obs_probs: [bin, sdate] - clim_probs <- c(prob_thresholds[1], diff(prob_thresholds), 1 - prob_thresholds[length(prob_thresholds)]) - clim_probs <- array(clim_probs, dim = dim(obs_probs)) - # clim_probs: [bin, sdate] + clim_probs <- c(prob_thresholds[1], diff(prob_thresholds), 1 - prob_thresholds[length(prob_thresholds)]) + clim_probs <- array(clim_probs, dim = dim(obs_probs)) + # clim_probs: [bin, sdate] - # Calculate RPS for each time step - probs_clim_cumsum <- apply(clim_probs, 2, cumsum) - probs_obs_cumsum <- apply(obs_probs, 2, cumsum) - rps_ref <- apply((probs_clim_cumsum - probs_obs_cumsum)^2, 2, sum) - # rps_ref: [sdate] + # Calculate RPS for each time step + probs_clim_cumsum <- apply(clim_probs, 2, cumsum) + probs_obs_cumsum <- apply(obs_probs, 2, cumsum) + rps_ref[ , j] <- apply((probs_clim_cumsum - probs_obs_cumsum)^2, 2, sum) + + # rps_ref: [sdate, (nobs)] -# if (Fair) { # FairRPS -# ## adjustment <- rowSums(-1 * (1/R - 1/R.new) * ens.cum * (R - ens.cum)/R/(R - 1)) [formula taken from SpecsVerification::EnsRps] -# R <- dim(exp)[2] #memb -# R_new <- Inf -# adjustment <- (-1) / (R - 1) * probs_clim_cumsum * (1 - probs_clim_cumsum) -# adjustment <- apply(adjustment, 2, sum) -# rps_ref <- rps_ref + adjustment -# } + # if (Fair) { # FairRPS + # ## adjustment <- rowSums(-1 * (1/R - 1/R.new) * ens.cum * (R - ens.cum)/R/(R - 1)) [formula taken from SpecsVerification::EnsRps] + # R <- dim(exp)[2] #memb + # R_new <- Inf + # adjustment <- (-1) / (R - 1) * probs_clim_cumsum * (1 - probs_clim_cumsum) + # adjustment <- apply(adjustment, 2, sum) + # rps_ref <- rps_ref + adjustment + # } + } + if (is.null(dat_dim)) { + dim(rps_ref) <- dim(exp)[time_dim] + } } else { # use "ref" as reference forecast - rps_ref <- .RPS(exp = ref, obs = obs, prob_thresholds = prob_thresholds, - indices_for_clim = indices_for_clim, Fair = Fair, weights = weights_ref) + if (!is.null(dat_dim)) { + if (!dat_dim %in% names(dim(ref))) { + ref <- InsertDim(ref, posdim = 3, lendim = 1, name = dat_dim) + } + } + rps_ref <- .RPS(exp = ref, obs = obs, time_dim = time_dim, memb_dim = memb_dim, dat_dim = dat_dim, + prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, + Fair = Fair, weights = weights_ref) } - # RPSS - rpss <- 1 - mean(rps_exp) / mean(rps_ref) - - # Significance - sign <- .RandomWalkTest(skill_A = rps_exp, skill_B = rps_ref)$signif + if (!is.null(dat_dim)) { + rpss <- array(dim = c(dim(exp)[time_dim], nexp = nexp, nobs = nobs)) + sign <- array(dim = c(dim(exp)[time_dim], nexp = nexp, nobs = nobs)) + + for (i in 1:nexp) { + for (j in 1:nobs) { + rps_exp_data <- rps_exp[ , i , j] + if (!is.null(ref)) { + if (dim(rps_ref)['nexp'] == 1) { + rps_ref_data <- rps_ref[ , 1, j] + } else { + rps_ref_data <- rps_ref[ , i, j] + } + } else { + rps_ref_data <- rps_ref[ , j] + } + rpss[ , i , j] <- 1 - mean(rps_exp_data) / mean(rps_ref_data) + sign[ , i , j] <- .RandomWalkTest(skill_A = rps_exp_data, skill_B = rps_ref_data)$signif + } + } + } else { + rpss <- 1 - mean(rps_exp) / mean(rps_ref) + # Significance + sign <- .RandomWalkTest(skill_A = rps_exp, skill_B = rps_ref)$signif + } return(list(rpss = rpss, sign = sign)) } diff --git a/man/RPSS.Rd b/man/RPSS.Rd index 893169d..90fe0ba 100644 --- a/man/RPSS.Rd +++ b/man/RPSS.Rd @@ -10,6 +10,7 @@ RPSS( ref = NULL, time_dim = "sdate", memb_dim = "member", + dat_dim = NULL, prob_thresholds = c(1/3, 2/3), indices_for_clim = NULL, Fair = FALSE, @@ -20,16 +21,16 @@ RPSS( ) } \arguments{ -\item{exp}{A named numerical array of the forecast with at least time -dimension.} +\item{exp}{A named numerical array of the forecast with at least time and +member dimension.} \item{obs}{A named numerical array of the observation with at least time dimension. The dimensions must be the same as 'exp' except 'memb_dim'.} \item{ref}{A named numerical array of the reference forecast data with at -least time dimension. The dimensions must be the same as 'exp' except -'memb_dim'. If it is NULL, the climatological forecast is used as reference -forecast. The default value is NULL.} +least time and member dimension. The dimensions must be the same as 'exp' +except 'memb_dim' and 'dat_dim'. If it is NULL, the climatological forecast +is used as reference forecast. The default value is NULL.} \item{time_dim}{A character string indicating the name of the time dimension. The default value is 'sdate'.} @@ -38,6 +39,10 @@ The default value is 'sdate'.} to compute the probabilities of the forecast and the reference forecast. The default value is 'member'.} +\item{dat_dim}{A character string indicating the name of dataset dimension. +The length of this dimension can be different between ’exp’ and ’obs’. +The default value is NULL.} + \item{prob_thresholds}{A numeric vector of the relative thresholds (from 0 to 1) between the categories. The default value is c(1/3, 2/3), which corresponds to tercile equiprobable categories.} @@ -84,9 +89,9 @@ RPSS is positive, it indicates that the forecast has higher skill than the reference forecast, while a negative value means that it has a lower skill. Examples of reference forecasts are the climatological forecast (same probabilities for all categories for all time steps), persistence, a previous -model version, and another model. It is computed as RPSS = 1 - RPS_exp / RPS_ref. -The statistical significance is obtained based on a Random Walk test at the -95% confidence level (DelSole and Tippett, 2016). +model version, and another model. It is computed as +RPSS = 1 - RPS_exp / RPS_ref. The statistical significance is obtained based +on a Random Walk test at the 95% confidence level (DelSole and Tippett, 2016). } \examples{ exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index a9edaff..582e5ff 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -20,24 +20,32 @@ set.seed(2) obs2_1 <- array(rnorm(10), dim = c(member = 1, sdate = 10)) set.seed(3) ref2 <- array(rnorm(20), dim = c(member = 2, sdate = 10)) - +# dat3 +set.seed(1) +exp3 <- array(rnorm(20), dim = c(member = 2, sdate = 10, dataset = 3)) +set.seed(2) +obs3 <- array(rnorm(10), dim = c(member = 1, sdate = 10, dataset = 2)) +set.seed(3) +ref3 <- array(rnorm(20), dim = c(member = 2, sdate = 10, dataset = 3)) +set.seed(4) +weights3 <- array(abs(rnorm(60)), dim = c(member = 2, sdate = 10, dataset = 3)) ############################################## test_that("1. Input checks", { # exp and obs (1) expect_error( RPSS(c()), - 'Parameter "exp" must be a numeric array.' + "Parameter 'exp' must be a numeric array." ) expect_error( RPSS(exp1, c()), - 'Parameter "obs" must be a numeric array.' + "Parameter 'obs' must be a numeric array." ) # time_dim expect_error( RPSS(exp1, obs1, time_dim = 1), - 'Parameter "time_dim" must be a character string.' + "Parameter 'time_dim' must be a character string." ) expect_error( RPSS(exp1, obs1, time_dim = 'time'), @@ -63,11 +71,11 @@ test_that("1. Input checks", { # exp, ref, and obs (2) expect_error( RPSS(exp1, array(1:9, dim = c(sdate = 9))), - "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim'." + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." ) expect_error( RPSS(exp1, obs1, ref2), - "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim'." + "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." ) # prob_thresholds expect_error( @@ -91,7 +99,7 @@ test_that("1. Input checks", { # weights_exp and weights_ref expect_error( RPSS(exp1, obs1, weights_exp = c(0, 1)), - 'Parameter "weights_exp" must be a two-dimensional numeric array.' + "Parameter 'weights_exp' must be a two-dimensional numeric array." ) expect_error( RPSS(exp1, obs1, weights_exp = array(1, dim = c(member = 3, time = 10))), @@ -112,184 +120,215 @@ test_that("1. Input checks", { ############################################## test_that("2. Output checks: dat1", { -expect_equal( -names(RPSS(exp1, obs1)), -c("rpss", "sign") -) -expect_equal( -names(RPSS(exp1, obs1, ref1)), -c("rpss", "sign") -) -expect_equal( -dim(RPSS(exp1, obs1)$rpss), -c(lat = 2) -) -expect_equal( -dim(RPSS(exp1, obs1)$sign), -c(lat = 2) -) -expect_equal( -dim(RPSS(exp1, obs1, ref1)$rpss), -c(lat = 2) -) -expect_equal( -dim(RPSS(exp1, obs1, ref1)$sign), -c(lat = 2) -) -# ref = NULL -expect_equal( -as.vector(RPSS(exp1, obs1)$rpss), -c(0.15789474, -0.05263158), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1)$sign), -c(FALSE, FALSE), -) -expect_equal( -as.vector(RPSS(exp1, obs1, Fair = T)$rpss), -c(0.5263158, 0.3684211), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, indices_for_clim = 3:5)$rpss), -c(-0.4062500, -0.1052632), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), -c(0.03030303, -0.14478114), -tolerance = 0.0001 -) -# ref = ref -expect_equal( -as.vector(RPSS(exp1, obs1, ref1)$rpss), -c(0.5259259, 0.4771242), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1, weights_ref = weights1)$rpss), -c(0.6596424, 0.4063579), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1)$sign), -c(FALSE, FALSE) -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1, Fair = T)$rpss), -c(0.6000000, 0.6190476), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$rpss), -c(0.2857143, 0.4166667), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$sign), -c(FALSE, FALSE) -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), -c(0.4754098, 0.3703704), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), -c(T, F) -) + expect_equal( + names(RPSS(exp1, obs1)), + c("rpss", "sign") + ) + expect_equal( + names(RPSS(exp1, obs1, ref1)), + c("rpss", "sign") + ) + expect_equal( + dim(RPSS(exp1, obs1)$rpss), + c(lat = 2) + ) + expect_equal( + dim(RPSS(exp1, obs1)$sign), + c(lat = 2) + ) + expect_equal( + dim(RPSS(exp1, obs1, ref1)$rpss), + c(lat = 2) + ) + expect_equal( + dim(RPSS(exp1, obs1, ref1)$sign), + c(lat = 2) + ) + # ref = NULL + expect_equal( + as.vector(RPSS(exp1, obs1)$rpss), + c(0.15789474, -0.05263158), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1)$sign), + c(FALSE, FALSE), + ) + expect_equal( + as.vector(RPSS(exp1, obs1, Fair = T)$rpss), + c(0.5263158, 0.3684211), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, indices_for_clim = 3:5)$rpss), + c(-0.4062500, -0.1052632), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + c(0.03030303, -0.14478114), + tolerance = 0.0001 + ) + # ref = ref + expect_equal( + as.vector(RPSS(exp1, obs1, ref1)$rpss), + c(0.5259259, 0.4771242), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1, weights_ref = weights1)$rpss), + c(0.6596424, 0.4063579), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1)$sign), + c(FALSE, FALSE) + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1, Fair = T)$rpss), + c(0.6000000, 0.6190476), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$rpss), + c(0.2857143, 0.4166667), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$sign), + c(FALSE, FALSE) + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + c(0.4754098, 0.3703704), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), + c(T, F) + ) }) ############################################## test_that("3. Output checks: dat2", { -expect_equal( -names(RPSS(exp2, obs2)), -c("rpss", "sign") -) -expect_equal( -names(RPSS(exp2, obs2, ref2)), -c("rpss", "sign") -) -expect_equal( -dim(RPSS(exp2, obs2)$rpss), -NULL -) -expect_equal( -dim(RPSS(exp2, obs2)$sign), -NULL -) -expect_equal( -dim(RPSS(exp2, obs2, ref2)$rpss), -NULL -) -expect_equal( -dim(RPSS(exp2, obs2, ref2)$sign), -NULL -) -# ref = NULL -expect_equal( -as.vector(RPSS(exp2, obs2)$rpss), -c(-0.7763158), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2)$sign), -FALSE, -) -expect_equal( -as.vector(RPSS(exp2, obs2, Fair = T)$rpss), -c(-0.1842105), -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2, indices_for_clim = 3:5)$rpss), --0.8984375, -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), -c(-0.7272727), -tolerance = 0.0001 -) -# ref = ref -expect_equal( -as.vector(RPSS(exp2, obs2, ref2)$rpss), -0, -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2, ref2)$sign), -FALSE -) -expect_equal( -as.vector(RPSS(exp2, obs2, ref2, Fair = T)$rpss), -0, -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$rpss), -0.03571429, -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$sign), -FALSE -) -expect_equal( -as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), -0.06557377, -tolerance = 0.0001 -) -expect_equal( -as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), -FALSE -) -expect_equal( -RPSS(exp2, obs2), -RPSS(exp2, obs2_1) -) + expect_equal( + names(RPSS(exp2, obs2)), + c("rpss", "sign") + ) + expect_equal( + names(RPSS(exp2, obs2, ref2)), + c("rpss", "sign") + ) + expect_equal( + dim(RPSS(exp2, obs2)$rpss), + NULL + ) + expect_equal( + dim(RPSS(exp2, obs2)$sign), + NULL + ) + expect_equal( + dim(RPSS(exp2, obs2, ref2)$rpss), + NULL + ) + expect_equal( + dim(RPSS(exp2, obs2, ref2)$sign), + NULL + ) + # ref = NULL + expect_equal( + as.vector(RPSS(exp2, obs2)$rpss), + c(-0.7763158), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2)$sign), + FALSE, + ) + expect_equal( + as.vector(RPSS(exp2, obs2, Fair = T)$rpss), + c(-0.1842105), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2, indices_for_clim = 3:5)$rpss), + -0.8984375, + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + c(-0.7272727), + tolerance = 0.0001 + ) + # ref = ref + expect_equal( + as.vector(RPSS(exp2, obs2, ref2)$rpss), + 0, + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2, ref2)$sign), + FALSE + ) + expect_equal( + as.vector(RPSS(exp2, obs2, ref2, Fair = T)$rpss), + 0, + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$rpss), + 0.03571429, + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$sign), + FALSE + ) + expect_equal( + as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + 0.06557377, + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), + FALSE + ) + expect_equal( + RPSS(exp2, obs2), + RPSS(exp2, obs2_1) + ) + + }) + ############################################## +test_that("4. Output checks: dat3", { + expect_equal( + dim(RPSS(exp3, obs3, dat_dim = 'dataset')$rpss), + c('sdate' = 10, 'nexp' = 3, 'nobs' = 2) + ) + expect_equal( + mean(RPSS(exp3, obs3, dat_dim = 'dataset')$rpss), + c(-0.7763158), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp3, obs3, dat_dim = 'dataset')$sign)[1:3], + c(FALSE,FALSE, FALSE), + ) + expect_equal( + mean(RPSS(exp3, obs3, dat_dim = 'dataset', Fair = T)$rpss), + c(-0.1842105), + tolerance = 0.0001 + ) + # weights_exp and weights_ref + expect_error( + RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = weights1), + "Parameter 'weights_exp' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'." + ) + expect_error( + RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = array(1, dim = c(member = 3, sdate = 1, dataset = 1))), + "Parameter 'weights_exp' must have the same dimension lengths as 'memb_dim', 'time_dim' and 'dat_dim' in 'exp'." + ) }) -- GitLab From 67af5d0dbbdb86e64b87960b81eabc2ccc8fe49a Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 10 Aug 2022 17:36:03 +0200 Subject: [PATCH 070/140] Allow dataset dimension to be NULL in CRPS and also added test file --- NAMESPACE | 3 + R/CRPS.R | 99 +++++++++++++++++++------- man/CRPS.Rd | 63 +++++++++++++++++ tests/testthat/test-CRPS.R | 138 +++++++++++++++++++++++++++++++++++++ 4 files changed, 278 insertions(+), 25 deletions(-) create mode 100644 man/CRPS.Rd create mode 100644 tests/testthat/test-CRPS.R diff --git a/NAMESPACE b/NAMESPACE index 032ebf9..a5229e3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,6 +7,8 @@ export(Ano) export(Ano_CrossValid) export(BrierScore) export(CDORemap) +export(CRPS) +export(CRPSS) export(Clim) export(Cluster) export(ColorBar) @@ -92,6 +94,7 @@ import(stats) importFrom(ClimProjDiags,CombineIndices) importFrom(ClimProjDiags,Subset) importFrom(ClimProjDiags,WeightedMean) +importFrom(SpecsVerification,enscrps_cpp) importFrom(abind,abind) importFrom(abind,adrop) importFrom(easyNCDF,ArrayToNc) diff --git a/R/CRPS.R b/R/CRPS.R index 942ec9e..0635b66 100644 --- a/R/CRPS.R +++ b/R/CRPS.R @@ -1,12 +1,14 @@ +#2345678901234567890123456789012345678901234567890123456789012345678901234567890 #'Compute the Continuous Ranked Probability Score #' #'The Continuous Ranked Probability Score (CRPS; Wilks, 2011) is the continuous -#'version of the Ranked Probability Score (RPS; Wilks, 2011). It is a skill metric -#'to evaluate the full distribution of probabilistic forecasts. It has a negative -#'orientation (i.e., the higher-quality forecast the smaller CRPS) and it rewards -#'the forecast that has probability concentration around the observed value. In case -#'of a deterministic forecast, the CRPS is reduced to the mean absolute error. It has -#'the same units as the data. The function is based on enscrps_cpp from SpecsVerification. +#'version of the Ranked Probability Score (RPS; Wilks, 2011). It is a skill +#'metric to evaluate the full distribution of probabilistic forecasts. It has a +#'negative orientation (i.e., the higher-quality forecast the smaller CRPS) and +#'it rewards the forecast that has probability concentration around the observed +#'value. In case of a deterministic forecast, the CRPS is reduced to the mean +#'absolute error. It has the same units as the data. The function is based on +#'enscrps_cpp from SpecsVerification. #' #'@param exp A named numerical array of the forecast with at least time #' dimension. @@ -16,6 +18,9 @@ #' The default value is 'sdate'. #'@param memb_dim A character string indicating the name of the member dimension #' to compute the probabilities of the forecast. The default value is 'member'. +#'@param dat_dim A character string indicating the name of dataset dimension. +#' The length of this dimension can be different between ’exp’ and ’obs’. +#' The default value is NULL. #'@param Fair A logical indicating whether to compute the FairCRPS (the #' potential RPSS that the forecast would have with an infinite ensemble size). #' The default value is FALSE. @@ -38,22 +43,21 @@ #'@importFrom SpecsVerification enscrps_cpp #'@importFrom ClimProjDiags Subset #'@export -CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', +CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, Fair = FALSE, ncores = NULL) { - # Check inputs ## exp and obs (1) if (!is.array(exp) | !is.numeric(exp)) - stop('Parameter "exp" must be a numeric array.') + stop("Parameter 'exp' must be a numeric array.") if (!is.array(obs) | !is.numeric(obs)) - stop('Parameter "obs" must be a numeric array.') + stop("Parameter 'obs' must be a numeric array.") if(any(is.null(names(dim(exp))))| any(nchar(names(dim(exp))) == 0) | any(is.null(names(dim(obs))))| any(nchar(names(dim(obs))) == 0)) { stop("Parameter 'exp' and 'obs' must have dimension names.") } ## time_dim if (!is.character(time_dim) | length(time_dim) != 1) - stop('Parameter "time_dim" must be a character string.') + stop("Parameter 'time_dim' must be a character string.") if (!time_dim %in% names(dim(exp)) | !time_dim %in% names(dim(obs))) { stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } @@ -64,19 +68,35 @@ CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', if (!memb_dim %in% names(dim(exp))) { stop("Parameter 'memb_dim' is not found in 'exp' dimension.") } + ## dat_dim + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + " Set it as NULL if there is no dataset dimension.") + } + } ## exp and obs (2) if (memb_dim %in% names(dim(obs))) { if (identical(as.numeric(dim(obs)[memb_dim]),1)){ obs <- ClimProjDiags::Subset(x = obs, along = memb_dim, indices = 1, drop = 'selected') - } else {stop("Not implemented for observations with members ('obs' can have 'memb_dim', but it should be of length=1).")} + } else { + stop("Not implemented for observations with members ('obs' can have 'memb_dim', but it should be of length = 1).") + } } name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) name_exp <- name_exp[-which(name_exp == memb_dim)] + if (!is.null(dat_dim)) { + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] + } if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions except 'memb_dim'.")) + "all dimensions except 'memb_dim' and 'dat_dim'.")) } ## Fair if (!is.logical(Fair) | length(Fair) > 1) { @@ -93,10 +113,11 @@ CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', ############################### crps <- Apply(data = list(exp = exp, obs = obs), - target_dims = list(exp = c(time_dim, memb_dim), - obs = time_dim), - output_dims = time_dim, - fun = .CRPS, Fair = Fair, + target_dims = list(exp = c(time_dim, memb_dim, dat_dim), + obs = c(time_dim, dat_dim)), + fun = .CRPS, + time_dim = time_dim, dat_dim = dat_dim, + Fair = Fair, ncores = ncores)$output1 # Return only the mean RPS @@ -105,15 +126,43 @@ CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', return(crps) } -.CRPS <- function(exp, obs, Fair = FALSE) { - # exp: [sdate, memb] - # obs: [sdate] - +.CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, + Fair = FALSE) { + + # exp: [sdate, memb, (dat_dim)] + # obs: [sdate, (dat_dim)] + if (is.null(dat_dim)) { + nexp <- 1 + nobs <- 1 + dim(exp) <- c(dim(exp), nexp = nexp) + dim(obs) <- c(dim(obs), nobs = nobs) + } else { + nexp <- as.numeric(dim(exp)[dat_dim]) + nobs <- as.numeric(dim(obs)[dat_dim]) + } + if (Fair) { # FairCRPS R_new <- Inf } else {R_new <- NA} - - crps <- SpecsVerification::enscrps_cpp(ens = exp, obs = obs, R_new = R_new) - - return(crps) + + CRPS <- array(dim = c(dim(exp)[time_dim], nexp = nexp, nobs = nobs)) + + for (i in 1:nexp) { + for (j in 1:nobs) { + exp_data <- exp[ , , i] + obs_data <- obs[ , j] + + if (is.null(dim(exp_data))) dim(exp_data) <- c(dim(exp)[1:2]) + if (is.null(dim(obs_data))) dim(obs_data) <- c(dim(obs)[1]) + + crps <- SpecsVerification::enscrps_cpp(ens = exp_data, obs = obs_data, R_new = R_new) + CRPS[,i,j] <- crps + } + } + + if (is.null(dat_dim) | (nexp == 1 & nobs == 1)) { + dim(CRPS) <- c(dim(CRPS)[time_dim]) + } + + return(CRPS) } diff --git a/man/CRPS.Rd b/man/CRPS.Rd new file mode 100644 index 0000000..d25c088 --- /dev/null +++ b/man/CRPS.Rd @@ -0,0 +1,63 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/CRPS.R +\name{CRPS} +\alias{CRPS} +\title{Compute the Continuous Ranked Probability Score} +\usage{ +CRPS( + exp, + obs, + time_dim = "sdate", + memb_dim = "member", + dat_dim = NULL, + Fair = FALSE, + ncores = NULL +) +} +\arguments{ +\item{exp}{A named numerical array of the forecast with at least time +dimension.} + +\item{obs}{A named numerical array of the observation with at least time +dimension. The dimensions must be the same as 'exp' except 'memb_dim'.} + +\item{time_dim}{A character string indicating the name of the time dimension. +The default value is 'sdate'.} + +\item{memb_dim}{A character string indicating the name of the member dimension +to compute the probabilities of the forecast. The default value is 'member'.} + +\item{dat_dim}{A character string indicating the name of dataset dimension. +The length of this dimension can be different between ’exp’ and ’obs’. +The default value is NULL.} + +\item{Fair}{A logical indicating whether to compute the FairCRPS (the +potential RPSS that the forecast would have with an infinite ensemble size). +The default value is FALSE.} + +\item{ncores}{An integer indicating the number of cores to use for parallel +computation. The default value is NULL.} +} +\value{ +A numerical array of CRPS with the same dimensions as "exp" except the +'time_dim' and 'memb_dim' dimensions. +} +\description{ +The Continuous Ranked Probability Score (CRPS; Wilks, 2011) is the continuous +version of the Ranked Probability Score (RPS; Wilks, 2011). It is a skill +metric to evaluate the full distribution of probabilistic forecasts. It has a +negative orientation (i.e., the higher-quality forecast the smaller CRPS) and +it rewards the forecast that has probability concentration around the observed +value. In case of a deterministic forecast, the CRPS is reduced to the mean +absolute error. It has the same units as the data. The function is based on +enscrps_cpp from SpecsVerification. +} +\examples{ +exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) +res <- CRPS(exp = exp, obs = obs) + +} +\references{ +Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 +} diff --git a/tests/testthat/test-CRPS.R b/tests/testthat/test-CRPS.R new file mode 100644 index 0000000..d27b5b5 --- /dev/null +++ b/tests/testthat/test-CRPS.R @@ -0,0 +1,138 @@ +context("s2dv::RPS tests") + +############################################## + +# dat1 +set.seed(1) +exp1 <- array(rnorm(60), dim = c(member = 3, sdate = 10, lat = 2)) +set.seed(2) +obs1 <- array(rnorm(20), dim = c(sdate = 10, lat = 2)) + +# dat2 +set.seed(1) +exp2 <- array(rnorm(20), dim = c(member = 2, sdate = 10)) +set.seed(2) +obs2 <- array(rnorm(10), dim = c(sdate = 10)) + +# dat3 +set.seed(1) +exp3 <- array(rnorm(40), dim = c(member = 2, sdate = 10, dataset = 2)) +set.seed(2) +obs3 <- array(rnorm(30), dim = c(member = 1, sdate = 10, dataset = 3)) + +############################################## +test_that("1. Input checks", { + # exp and obs (1) + expect_error( + CRPS(c()), + "Parameter 'exp' must be a numeric array." + ) + expect_error( + CRPS(exp1, c()), + "Parameter 'obs' must be a numeric array." + ) + + # time_dim + expect_error( + CRPS(exp1, obs1, time_dim = 1), + "Parameter 'time_dim' must be a character string." + ) + expect_error( + CRPS(exp1, obs1, time_dim = 'time'), + "Parameter 'time_dim' is not found in 'exp' or 'obs' dimension." + ) + # memb_dim + expect_error( + CRPS(exp1, obs1, memb_dim = TRUE), + "Parameter 'memb_dim' must be a character string." + ) + expect_error( + CRPS(exp1, obs1, memb_dim = 'memb'), + "Parameter 'memb_dim' is not found in 'exp' dimension." + ) + ## exp and obs (2) + expect_error(CRPS(exp1, array(1:40, dim = c(sdate = 10, lat = 2, member = 2))), + "Not implemented for observations with members ('obs' can have 'memb_dim', but it should be of length = 1).", fixed = TRUE + ) + expect_error( + CRPS(exp1, array(1:9, dim = c(sdate = 9))), + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." + ) + # Fair + expect_error( + CRPS(exp1, obs1, Fair = 1), + "Parameter 'Fair' must be either TRUE or FALSE." + ) + # ncores + expect_error( + CRPS(exp2, obs2, ncores = 1.5), + "Parameter 'ncores' must be either NULL or a positive integer." + ) + +}) + +############################################## +test_that("2. Output checks: dat1", { + + expect_equal( + dim(CRPS(exp1, obs1)), + c(lat = 2) + ) + expect_equal( + as.vector(CRPS(exp1, obs1)), + c(0.5947612, 0.7511546), + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPS(exp1, obs1, Fair = TRUE)), + c(0.4215127, 0.5891242), + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPS(exp1, obs1, Fair = FALSE)), + c(0.5947612, 0.7511546), + tolerance = 0.0001 + ) + +}) + +############################################## +test_that("3. Output checks: dat2", { + + expect_equal( + dim(CRPS(exp2, obs2)), + NULL + ) + expect_equal( + CRPS(exp2, obs2), + 0.9350226, + tolerance = 0.0001 + ) + expect_equal( + CRPS(exp2, obs2, Fair = TRUE), + CRPS(array(exp2, dim = c(dim(exp2), dataset = 1)), array(obs2, dim = c(dim(obs2), dataset = 1)), dat_dim = 'dataset', Fair = TRUE), + tolerance = 0.0001 + ) +}) + + + +############################################## +test_that("3. Output checks: dat3", { + + expect_equal( + dim(CRPS(exp3, obs3, dat_dim = 'dataset')), + c(nexp = 2, nobs = 3) + ) + expect_equal( + as.vector(CRPS(exp3, obs3, dat_dim = 'dataset', Fair = FALSE)), + c(0.9350226, 0.8354833, 1.0047853, 0.9681745, 1.2192761, 1.0171790), + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPS(exp3, obs3, dat_dim = 'dataset', Fair = TRUE)), + c(0.6701312, 0.6198684, 0.7398939, 0.7525596, 0.9543847, 0.8015641), + tolerance = 0.0001 + ) + +}) -- GitLab From 6db566bd51b78e9f316ba6f4f8789e4388dfb6a3 Mon Sep 17 00:00:00 2001 From: aho Date: Thu, 11 Aug 2022 11:21:52 +0200 Subject: [PATCH 071/140] Improvement of format and documentation; Fix minor bugs --- R/CRPS.R | 47 ++++++++++++++++++++------------------ tests/testthat/test-CRPS.R | 6 ++--- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/R/CRPS.R b/R/CRPS.R index 0635b66..cc1f5de 100644 --- a/R/CRPS.R +++ b/R/CRPS.R @@ -1,4 +1,3 @@ -#2345678901234567890123456789012345678901234567890123456789012345678901234567890 #'Compute the Continuous Ranked Probability Score #' #'The Continuous Ranked Probability Score (CRPS; Wilks, 2011) is the continuous @@ -8,28 +7,32 @@ #'it rewards the forecast that has probability concentration around the observed #'value. In case of a deterministic forecast, the CRPS is reduced to the mean #'absolute error. It has the same units as the data. The function is based on -#'enscrps_cpp from SpecsVerification. +#'enscrps_cpp from SpecsVerification. If there is more than one dataset, CRPS +#'will be computed for each pair of exp and obs data. #' #'@param exp A named numerical array of the forecast with at least time #' dimension. #'@param obs A named numerical array of the observation with at least time -#' dimension. The dimensions must be the same as 'exp' except 'memb_dim'. +#' dimension. The dimensions must be the same as 'exp' except 'memb_dim' and +#' 'dat_dim'. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. #'@param memb_dim A character string indicating the name of the member dimension #' to compute the probabilities of the forecast. The default value is 'member'. #'@param dat_dim A character string indicating the name of dataset dimension. -#' The length of this dimension can be different between ’exp’ and ’obs’. -#' The default value is NULL. +#' The length of this dimension can be different between 'exp' and 'obs'. The +#' default value is NULL. #'@param Fair A logical indicating whether to compute the FairCRPS (the -#' potential RPSS that the forecast would have with an infinite ensemble size). +#' potential CRPS that the forecast would have with an infinite ensemble size). #' The default value is FALSE. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' -#'@return -#'A numerical array of CRPS with the same dimensions as "exp" except the -#''time_dim' and 'memb_dim' dimensions. +#'@return +#'A numerical array of CRPS with dimensions c(nexp, nobs, the rest dimensions of +#''exp' except 'time_dim' and 'memb_dim' dimensions). nexp is the number of +#'experiment (i.e., dat_dim in exp), and nobs is the number of observation +#'(i.e., dat_dim in obs). If dat_dim is NULL, nexp and nobs are omitted. #' #'@references #'Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 @@ -80,7 +83,7 @@ CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NU } ## exp and obs (2) if (memb_dim %in% names(dim(obs))) { - if (identical(as.numeric(dim(obs)[memb_dim]),1)){ + if (identical(as.numeric(dim(obs)[memb_dim]), 1)) { obs <- ClimProjDiags::Subset(x = obs, along = memb_dim, indices = 1, drop = 'selected') } else { stop("Not implemented for observations with members ('obs' can have 'memb_dim', but it should be of length = 1).") @@ -104,33 +107,34 @@ CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NU } ## ncores if (!is.null(ncores)) { - if (!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 | - length(ncores) > 1) { + if (!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 | length(ncores) > 1) { stop("Parameter 'ncores' must be either NULL or a positive integer.") } } ############################### + # Compute CRPS crps <- Apply(data = list(exp = exp, obs = obs), target_dims = list(exp = c(time_dim, memb_dim, dat_dim), obs = c(time_dim, dat_dim)), fun = .CRPS, - time_dim = time_dim, dat_dim = dat_dim, + time_dim = time_dim, memb_dim = 'member', dat_dim = dat_dim, Fair = Fair, ncores = ncores)$output1 - # Return only the mean RPS + # Return only the mean CRPS crps <- MeanDims(crps, time_dim, na.rm = FALSE) return(crps) } .CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, - Fair = FALSE) { - + Fair = FALSE) { # exp: [sdate, memb, (dat_dim)] # obs: [sdate, (dat_dim)] + + # Adjust dimensions if needed if (is.null(dat_dim)) { nexp <- 1 nobs <- 1 @@ -141,26 +145,25 @@ CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NU nobs <- as.numeric(dim(obs)[dat_dim]) } - if (Fair) { # FairCRPS - R_new <- Inf - } else {R_new <- NA} + # for FairCRPS + R_new <- ifelse(Fair, Inf, NA) CRPS <- array(dim = c(dim(exp)[time_dim], nexp = nexp, nobs = nobs)) for (i in 1:nexp) { for (j in 1:nobs) { exp_data <- exp[ , , i] - obs_data <- obs[ , j] + obs_data <- obs[, j] if (is.null(dim(exp_data))) dim(exp_data) <- c(dim(exp)[1:2]) if (is.null(dim(obs_data))) dim(obs_data) <- c(dim(obs)[1]) crps <- SpecsVerification::enscrps_cpp(ens = exp_data, obs = obs_data, R_new = R_new) - CRPS[,i,j] <- crps + CRPS[, i, j] <- crps } } - if (is.null(dat_dim) | (nexp == 1 & nobs == 1)) { + if (is.null(dat_dim)) { dim(CRPS) <- c(dim(CRPS)[time_dim]) } diff --git a/tests/testthat/test-CRPS.R b/tests/testthat/test-CRPS.R index d27b5b5..972eb45 100644 --- a/tests/testthat/test-CRPS.R +++ b/tests/testthat/test-CRPS.R @@ -1,4 +1,4 @@ -context("s2dv::RPS tests") +context("s2dv::CRPS tests") ############################################## @@ -109,8 +109,8 @@ test_that("3. Output checks: dat2", { tolerance = 0.0001 ) expect_equal( - CRPS(exp2, obs2, Fair = TRUE), - CRPS(array(exp2, dim = c(dim(exp2), dataset = 1)), array(obs2, dim = c(dim(obs2), dataset = 1)), dat_dim = 'dataset', Fair = TRUE), + as.vector(CRPS(exp2, obs2, Fair = TRUE)), + as.vector(CRPS(array(exp2, dim = c(dim(exp2), dataset = 1)), array(obs2, dim = c(dim(obs2), dataset = 1)), dat_dim = 'dataset', Fair = TRUE)), tolerance = 0.0001 ) }) -- GitLab From 277a8e266a086ea7b104bd50092ae33d4e7e598e Mon Sep 17 00:00:00 2001 From: aho Date: Thu, 11 Aug 2022 11:22:17 +0200 Subject: [PATCH 072/140] Correct typo and update document --- R/RPS.R | 2 +- man/CRPS.Rd | 18 +++++++----- man/CRPSS.Rd | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++ man/RPS.Rd | 4 +-- 4 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 man/CRPSS.Rd diff --git a/R/RPS.R b/R/RPS.R index b0f4c97..c02cf49 100644 --- a/R/RPS.R +++ b/R/RPS.R @@ -26,7 +26,7 @@ #' for computing the thresholds between the probabilistic categories. If NULL, #' the whole period is used. The default value is NULL. #'@param Fair A logical indicating whether to compute the FairRPS (the -#' potential RPSS that the forecast would have with an infinite ensemble size). +#' potential RPS that the forecast would have with an infinite ensemble size). #' The default value is FALSE. #'@param weights A named two-dimensional numerical array of the weights for each #' member and time. The dimension names should include 'memb_dim' and diff --git a/man/CRPS.Rd b/man/CRPS.Rd index d25c088..453c199 100644 --- a/man/CRPS.Rd +++ b/man/CRPS.Rd @@ -19,7 +19,8 @@ CRPS( dimension.} \item{obs}{A named numerical array of the observation with at least time -dimension. The dimensions must be the same as 'exp' except 'memb_dim'.} +dimension. The dimensions must be the same as 'exp' except 'memb_dim' and +'dat_dim'.} \item{time_dim}{A character string indicating the name of the time dimension. The default value is 'sdate'.} @@ -28,19 +29,21 @@ The default value is 'sdate'.} to compute the probabilities of the forecast. The default value is 'member'.} \item{dat_dim}{A character string indicating the name of dataset dimension. -The length of this dimension can be different between ’exp’ and ’obs’. -The default value is NULL.} +The length of this dimension can be different between 'exp' and 'obs'. The +default value is NULL.} \item{Fair}{A logical indicating whether to compute the FairCRPS (the -potential RPSS that the forecast would have with an infinite ensemble size). +potential CRPS that the forecast would have with an infinite ensemble size). The default value is FALSE.} \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} } \value{ -A numerical array of CRPS with the same dimensions as "exp" except the -'time_dim' and 'memb_dim' dimensions. +A numerical array of CRPS with dimensions c(nexp, nobs, the rest dimensions of +'exp' except 'time_dim' and 'memb_dim' dimensions). nexp is the number of +experiment (i.e., dat_dim in exp), and nobs is the number of observation +(i.e., dat_dim in obs). If dat_dim is NULL, nexp and nobs are omitted. } \description{ The Continuous Ranked Probability Score (CRPS; Wilks, 2011) is the continuous @@ -50,7 +53,8 @@ negative orientation (i.e., the higher-quality forecast the smaller CRPS) and it rewards the forecast that has probability concentration around the observed value. In case of a deterministic forecast, the CRPS is reduced to the mean absolute error. It has the same units as the data. The function is based on -enscrps_cpp from SpecsVerification. +enscrps_cpp from SpecsVerification. If there is more than one dataset, CRPS +will be computed for each pair of exp and obs data. } \examples{ exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) diff --git a/man/CRPSS.Rd b/man/CRPSS.Rd new file mode 100644 index 0000000..85705f3 --- /dev/null +++ b/man/CRPSS.Rd @@ -0,0 +1,77 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/CRPSS.R +\name{CRPSS} +\alias{CRPSS} +\title{Compute the Continuous Ranked Probability Skill Score} +\usage{ +CRPSS( + exp, + obs, + ref = NULL, + time_dim = "sdate", + memb_dim = "member", + Fair = FALSE, + ncores = NULL +) +} +\arguments{ +\item{exp}{A named numerical array of the forecast with at least time +dimension.} + +\item{obs}{A named numerical array of the observation with at least time +dimension. The dimensions must be the same as 'exp' except 'memb_dim'.} + +\item{ref}{A named numerical array of the reference forecast data with at +least time dimension. The dimensions must be the same as 'exp' except +'memb_dim'. If it is NULL, the climatological forecast is used as reference +forecast. The default value is NULL.} + +\item{time_dim}{A character string indicating the name of the time dimension. +The default value is 'sdate'.} + +\item{memb_dim}{A character string indicating the name of the member dimension +to compute the probabilities of the forecast and the reference forecast. The +default value is 'member'.} + +\item{Fair}{A logical indicating whether to compute the FairCRPSS (the +potential CRPSS that the forecast would have with an infinite ensemble size). +The default value is FALSE.} + +\item{ncores}{An integer indicating the number of cores to use for parallel +computation. The default value is NULL.} +} +\value{ +\item{$crpss}{ + A numerical array of the CRPSS with the same dimensions as "exp" except the + 'time_dim' and 'memb_dim' dimensions. +} +\item{$sign}{ + A logical array of the statistical significance of the CRPSS with the same + dimensions as 'exp' except the 'time_dim' and 'memb_dim' dimensions. +} +} +\description{ +The Continuous Ranked Probability Skill Score (CRPSS; Wilks, 2011) is the skill score +based on the Continuous Ranked Probability Score (CRPS; Wilks, 2011). It can be used to +assess whether a forecast presents an improvement or worsening with respect to +a reference forecast. The CRPSS ranges between minus infinite and 1. If the +CRPSS is positive, it indicates that the forecast has higher skill than the +reference forecast, while a negative value means that it has a lower skill. +Examples of reference forecasts are the climatological forecast (same +probabilities for all categories for all time steps), persistence, a previous +model version, and another model. It is computed as CRPSS = 1 - CRPS_exp / CRPS_ref. +The statistical significance is obtained based on a Random Walk test at the +95% confidence level (DelSole and Tippett, 2016). +} +\examples{ +exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) +ref <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +res <- CRPSS(exp = exp, obs = obs) ## climatology as reference forecast +res <- CRPSS(exp = exp, obs = obs, ref = ref) ## ref as reference forecast + +} +\references{ +Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 +DelSole and Tippett, 2016; https://doi.org/10.1175/MWR-D-15-0218.1 +} diff --git a/man/RPS.Rd b/man/RPS.Rd index ee5c241..1d6eec2 100644 --- a/man/RPS.Rd +++ b/man/RPS.Rd @@ -37,8 +37,8 @@ corresponds to tercile equiprobable categories.} for computing the thresholds between the probabilistic categories. If NULL, the whole period is used. The default value is NULL.} -\item{Fair}{A logical indicating whether to compute the FairRPSS (the -potential RPSS that the forecast would have with an infinite ensemble size). +\item{Fair}{A logical indicating whether to compute the FairRPS (the +potential RPS that the forecast would have with an infinite ensemble size). The default value is FALSE.} \item{weights}{A named two-dimensional numerical array of the weights for each -- GitLab From 6f32c7bb2cec0c70fd6752c00c6542588904b536 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 11 Aug 2022 12:48:24 +0200 Subject: [PATCH 073/140] Correct memb_dim = 'member' inside Apply of CRPS --- R/CRPS.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/CRPS.R b/R/CRPS.R index cc1f5de..b2f38a5 100644 --- a/R/CRPS.R +++ b/R/CRPS.R @@ -119,7 +119,7 @@ CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NU target_dims = list(exp = c(time_dim, memb_dim, dat_dim), obs = c(time_dim, dat_dim)), fun = .CRPS, - time_dim = time_dim, memb_dim = 'member', dat_dim = dat_dim, + time_dim = time_dim, memb_dim = memb_dim, dat_dim = dat_dim, Fair = Fair, ncores = ncores)$output1 -- GitLab From 484916e77a75b27e8f0a93bddc5710f7c31eeb74 Mon Sep 17 00:00:00 2001 From: aho Date: Thu, 11 Aug 2022 13:39:08 +0200 Subject: [PATCH 074/140] Refine document and correct minor mistakes --- R/RPS.R | 10 ++--- R/RPSS.R | 79 ++++++++++++++++++++++---------------- tests/testthat/test-RPSS.R | 28 +++++++------- 3 files changed, 65 insertions(+), 52 deletions(-) diff --git a/R/RPS.R b/R/RPS.R index 369f8dd..4d73cf7 100644 --- a/R/RPS.R +++ b/R/RPS.R @@ -12,8 +12,8 @@ #'and 1. If there is more than one dataset, RPS will be computed for each pair #'of exp and obs data. #' -#'@param exp A named numerical array of the forecast with at least time -#' dimension. +#'@param exp A named numerical array of the forecast with at least time and +#' member dimension. #'@param obs A named numerical array of the observation with at least time #' dimension. The dimensions must be the same as 'exp' except 'memb_dim' and #' 'dat_dim'. @@ -45,8 +45,8 @@ #'@return #'A numerical array of RPS with dimensions c(nexp, nobs, the rest dimensions of #''exp' except 'time_dim' and 'memb_dim' dimensions). nexp is the number of -#'experiment (i.e., dat_dim in exp), and nobs is the number of observation (i.e. -#', dat_dim in obs). If dat_dim is NULL, nexp and nobs are omitted. +#'experiment (i.e., dat_dim in exp), and nobs is the number of observation +#'(i.e., dat_dim in obs). If dat_dim is NULL, nexp and nobs are omitted. #' #'@references #'Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 @@ -136,7 +136,7 @@ RPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NUL ## weights if (!is.null(weights)) { if (!is.array(weights) | !is.numeric(weights)) - stop("Parameter 'weights' must be a two-dimensional numeric array.") + stop("Parameter 'weights' must be a named numeric array.") if (is.null(dat_dim)) { if (length(dim(weights)) != 2 | any(!names(dim(weights)) %in% c(memb_dim, time_dim))) stop("Parameter 'weights' must have two dimensions with the names of 'memb_dim' and 'time_dim'.") diff --git a/R/RPSS.R b/R/RPSS.R index a6e710b..0707874 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -9,13 +9,16 @@ #'Examples of reference forecasts are the climatological forecast (same #'probabilities for all categories for all time steps), persistence, a previous #'model version, and another model. It is computed as -#'RPSS = 1 - RPS_exp / RPS_ref. The statistical significance is obtained based -#'on a Random Walk test at the 95% confidence level (DelSole and Tippett, 2016). +#'\code{RPSS = 1 - RPS_exp / RPS_ref}. The statistical significance is obtained +#'based on a Random Walk test at the 95% confidence level (DelSole and Tippett, +#'2016). If there is more than one dataset, RPS will be computed for each pair +#'of exp and obs data. #' #'@param exp A named numerical array of the forecast with at least time and #' member dimension. #'@param obs A named numerical array of the observation with at least time -#' dimension. The dimensions must be the same as 'exp' except 'memb_dim'. +#' dimension. The dimensions must be the same as 'exp' except 'memb_dim' and +#' 'dat_dim'. #'@param ref A named numerical array of the reference forecast data with at #' least time and member dimension. The dimensions must be the same as 'exp' #' except 'memb_dim' and 'dat_dim'. If it is NULL, the climatological forecast @@ -26,7 +29,7 @@ #' to compute the probabilities of the forecast and the reference forecast. The #' default value is 'member'. #'@param dat_dim A character string indicating the name of dataset dimension. -#' The length of this dimension can be different between ’exp’ and ’obs’. +#' The length of this dimension can be different between 'exp' and 'obs'. #' The default value is NULL. #'@param prob_thresholds A numeric vector of the relative thresholds (from 0 to #' 1) between the categories. The default value is c(1/3, 2/3), which @@ -51,12 +54,14 @@ #' #'@return #'\item{$rpss}{ -#' A numerical array of the RPSS with the same dimensions as "exp" except the -#' 'time_dim' and 'memb_dim' dimensions. +#' A numerical array of RPSS with dimensions c(nexp, nobs, the rest dimensions +#' of 'exp' except 'time_dim' and 'memb_dim' dimensions). nexp is the number of +#' experiment (i.e., dat_dim in exp), and nobs is the number of observation +#' i.e., dat_dim in obs). If dat_dim is NULL, nexp and nobs are omitted. #'} #'\item{$sign}{ -#' A logical array of the statistical significance of the RPSS with the same -#' dimensions as 'exp' except the 'time_dim' and 'memb_dim' dimensions. +#' A logical array of the statistical significance of the RPSS with the same +#' dimensions as $rpss. #'} #' #'@references @@ -82,6 +87,10 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', stop("Parameter 'exp' must be a numeric array.") if (!is.array(obs) | !is.numeric(obs)) stop("Parameter 'obs' must be a numeric array.") + if (any(is.null(names(dim(exp))))| any(nchar(names(dim(exp))) == 0) | + any(is.null(names(dim(obs))))| any(nchar(names(dim(obs))) == 0)) { + stop("Parameter 'exp' and 'obs' must have dimension names.") + } if (!is.null(ref)) { if (!is.array(ref) | !is.numeric(ref)) stop("Parameter 'ref' must be a numeric array.") @@ -89,10 +98,6 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', stop("Parameter 'ref' must have dimension names.") } } - if(any(is.null(names(dim(exp))))| any(nchar(names(dim(exp))) == 0) | - any(is.null(names(dim(obs))))| any(nchar(names(dim(obs))) == 0)) { - stop("Parameter 'exp' and 'obs' must have dimension names.") - } ## time_dim if (!is.character(time_dim) | length(time_dim) != 1) stop("Parameter 'time_dim' must be a character string.") @@ -122,7 +127,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', " Set it as NULL if there is no dataset dimension.") } } - ## exp and obs (2) + ## exp, obs, and ref (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) name_exp <- name_exp[-which(name_exp == memb_dim)] @@ -142,7 +147,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', name_ref <- sort(names(dim(ref))) name_ref <- name_ref[-which(name_ref == memb_dim)] if (!is.null(dat_dim)) { - if (dat_dim %in% name_obs) { + if (dat_dim %in% name_ref) { name_ref <- name_ref[-which(name_ref == dat_dim)] } } @@ -184,15 +189,17 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', ## weights_exp if (!is.null(weights_exp)) { if (!is.array(weights_exp) | !is.numeric(weights_exp)) - stop("Parameter 'weights_exp' must be a two-dimensional numeric array.") + stop("Parameter 'weights_exp' must be a named numeric array.") + if (is.null(dat_dim)) { if (length(dim(weights_exp)) != 2 | any(!names(dim(weights_exp)) %in% c(memb_dim, time_dim))) - stop("Parameter 'weights_exp' must have two dimensions with the names of memb_dim and time_dim.") + stop("Parameter 'weights_exp' must have two dimensions with the names of 'memb_dim' and 'time_dim'.") if (dim(weights_exp)[memb_dim] != dim(exp)[memb_dim] | dim(weights_exp)[time_dim] != dim(exp)[time_dim]) { - stop("Parameter 'weights_exp' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") + stop("Parameter 'weights_exp' must have the same dimension lengths as 'memb_dim' and 'time_dim' in 'exp'.") } weights_exp <- Reorder(weights_exp, c(time_dim, memb_dim)) + } else { if (length(dim(weights_exp)) != 3 | any(!names(dim(weights_exp)) %in% c(memb_dim, time_dim, dat_dim))) stop("Parameter 'weights_exp' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'.") @@ -209,25 +216,27 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', ## weights_ref if (!is.null(weights_ref)) { if (!is.array(weights_ref) | !is.numeric(weights_ref)) - stop('Parameter "weights_ref" must be a two-dimensional numeric array.') + stop('Parameter "weights_ref" must be a named numeric array.') + if (is.null(dat_dim)) { if (length(dim(weights_ref)) != 2 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim))) - stop("Parameter 'weights_ref' must have two dimensions with the names of memb_dim and time_dim.") + stop("Parameter 'weights_ref' must have two dimensions with the names of 'memb_dim' and 'time_dim'.") if (dim(weights_ref)[memb_dim] != dim(exp)[memb_dim] | dim(weights_ref)[time_dim] != dim(exp)[time_dim]) { - stop("Parameter 'weights_ref' must have the same dimension lengths as memb_dim and time_dim in 'exp'.") + stop("Parameter 'weights_ref' must have the same dimension lengths as 'memb_dim' and 'time_dim' in 'ref'.") } weights_ref <- Reorder(weights_ref, c(time_dim, memb_dim)) + } else { - if (length(dim(weights_ref)) != 3 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim, dat_dim))) - stop("Parameter 'weights_ref' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'.") - if (dim(weights_ref)[memb_dim] != dim(ref)[memb_dim] | - dim(weights_ref)[time_dim] != dim(ref)[time_dim] | - dim(weights_ref)[dat_dim] != dim(ref)[dat_dim]) { - stop(paste0("Parameter 'weights_ref' must have the same dimension lengths ", - "as 'memb_dim', 'time_dim' and 'dat_dim' in 'ref'.")) - } - weights <- Reorder(weights_ref, c(time_dim, memb_dim, dat_dim)) + if (length(dim(weights_ref)) != 3 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim, dat_dim))) + stop("Parameter 'weights_ref' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'.") + if (dim(weights_ref)[memb_dim] != dim(ref)[memb_dim] | + dim(weights_ref)[time_dim] != dim(ref)[time_dim] | + dim(weights_ref)[dat_dim] != dim(ref)[dat_dim]) { + stop(paste0("Parameter 'weights_ref' must have the same dimension lengths ", + "as 'memb_dim', 'time_dim' and 'dat_dim' in 'ref'.")) + } + weights <- Reorder(weights_ref, c(time_dim, memb_dim, dat_dim)) } } @@ -249,7 +258,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } if (!is.null(ref)) { # use "ref" as reference forecast - if ((!is.null(dat_dim)) && (!dat_dim %in% names(dim(ref)))) { + if (!is.null(dat_dim) && (!dat_dim %in% names(dim(ref)))) { target_dims_ref <- c(time_dim, memb_dim) } else { target_dims_ref <- c(time_dim, memb_dim, dat_dim) @@ -295,14 +304,18 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } # RPS of the forecast - rps_exp <- .RPS(exp = exp, obs = obs, time_dim = time_dim, memb_dim = memb_dim, dat_dim = dat_dim, + rps_exp <- .RPS(exp = exp, obs = obs, time_dim = time_dim, memb_dim = memb_dim, dat_dim = dat_dim, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights = weights_exp) # RPS of the reference forecast if (is.null(ref)) { ## using climatology as reference forecast - if (!memb_dim %in% names(dim(obs))) obs <- InsertDim(obs, posdim = 2, lendim = 1, name = memb_dim) - if (is.null(dat_dim)) dim(obs) <- c(dim(obs), nobs = nobs) + if (!memb_dim %in% names(dim(obs))) { + obs <- InsertDim(obs, posdim = 2, lendim = 1, name = memb_dim) + } + if (is.null(dat_dim)) { + dim(obs) <- c(dim(obs), nobs = nobs) + } rps_ref <- array(dim = c(dim(obs)[time_dim], nobs = nobs)) for (j in 1:nobs) { diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index 582e5ff..aefe587 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -99,15 +99,23 @@ test_that("1. Input checks", { # weights_exp and weights_ref expect_error( RPSS(exp1, obs1, weights_exp = c(0, 1)), - "Parameter 'weights_exp' must be a two-dimensional numeric array." + "Parameter 'weights_exp' must be a named numeric array." ) expect_error( RPSS(exp1, obs1, weights_exp = array(1, dim = c(member = 3, time = 10))), - "Parameter 'weights_exp' must have two dimensions with the names of memb_dim and time_dim." + "Parameter 'weights_exp' must have two dimensions with the names of 'memb_dim' and 'time_dim'." ) expect_error( - RPSS(exp1, obs1, weights_ref = array(1, dim = c(member = 3, sdate = 1))), - "Parameter 'weights_ref' must have the same dimension lengths as memb_dim and time_dim in 'exp'." + RPSS(exp1, obs1, ref1, weights_ref = array(1, dim = c(member = 3, sdate = 1))), + "Parameter 'weights_ref' must have the same dimension lengths as 'memb_dim' and 'time_dim' in 'ref'." + ) + expect_error( + RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = weights1), + "Parameter 'weights_exp' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'." + ) + expect_error( + RPSS(exp3, obs3, ref3, dat_dim = 'dataset', weights_ref = weights1), + "Parameter 'weights_ref' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'." ) # ncores expect_error( @@ -315,20 +323,12 @@ test_that("4. Output checks: dat3", { ) expect_equal( as.vector(RPSS(exp3, obs3, dat_dim = 'dataset')$sign)[1:3], - c(FALSE,FALSE, FALSE), + c(FALSE, FALSE, FALSE), ) expect_equal( mean(RPSS(exp3, obs3, dat_dim = 'dataset', Fair = T)$rpss), c(-0.1842105), tolerance = 0.0001 ) - # weights_exp and weights_ref - expect_error( - RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = weights1), - "Parameter 'weights_exp' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'." - ) - expect_error( - RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = array(1, dim = c(member = 3, sdate = 1, dataset = 1))), - "Parameter 'weights_exp' must have the same dimension lengths as 'memb_dim', 'time_dim' and 'dat_dim' in 'exp'." - ) + }) -- GitLab From 0deae318cccd62b8d374057f97b4780b338406a9 Mon Sep 17 00:00:00 2001 From: aho Date: Thu, 11 Aug 2022 13:41:08 +0200 Subject: [PATCH 075/140] Correct RPS unit test --- tests/testthat/test-RPS.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-RPS.R b/tests/testthat/test-RPS.R index b7d7f32..9363a41 100644 --- a/tests/testthat/test-RPS.R +++ b/tests/testthat/test-RPS.R @@ -87,7 +87,7 @@ test_that("1. Input checks", { # weights expect_error( RPS(exp1, obs1, weights = c(0, 1)), - "Parameter 'weights' must be a two-dimensional numeric array." + "Parameter 'weights' must be a named numeric array." ) expect_error( RPS(exp1, obs1, weights = array(1, dim = c(member = 3, time = 10))), -- GitLab From 20c8641650cc2056d688e8c0d200b6b6cec58e67 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Fri, 12 Aug 2022 15:59:15 +0200 Subject: [PATCH 076/140] Correct changes in RPSS.R and some typos in RPS.R --- R/RPS.R | 2 +- R/RPSS.R | 119 ++++++++----- man/RPS.Rd | 10 +- man/RPSS.Rd | 41 +++-- tests/testthat/test-RPSS.R | 346 +++++++++++++++++++++---------------- 5 files changed, 304 insertions(+), 214 deletions(-) diff --git a/R/RPS.R b/R/RPS.R index 4d73cf7..654db3d 100644 --- a/R/RPS.R +++ b/R/RPS.R @@ -20,7 +20,7 @@ #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. #'@param dat_dim A character string indicating the name of dataset dimension. -#' The length of this dimension can be different between ’exp’ and ’obs’. +#' The length of this dimension can be different between 'exp' and 'obs'. #' The default value is NULL. #'@param memb_dim A character string indicating the name of the member dimension #' to compute the probabilities of the forecast. The default value is 'member'. diff --git a/R/RPSS.R b/R/RPSS.R index 0707874..fe4fc18 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -21,8 +21,9 @@ #' 'dat_dim'. #'@param ref A named numerical array of the reference forecast data with at #' least time and member dimension. The dimensions must be the same as 'exp' -#' except 'memb_dim' and 'dat_dim'. If it is NULL, the climatological forecast -#' is used as reference forecast. The default value is NULL. +#' except 'memb_dim' and 'dat_dim', 'dat_dim' can be either equal of 'exp' or +#' NULL. If 'ref' is NULL, the climatological forecast is used as reference +#' forecast. The default value is NULL. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. #'@param memb_dim A character string indicating the name of the member dimension @@ -42,13 +43,14 @@ #' The default value is FALSE. #'@param weights Deprecated and will be removed in the next release. Please use #' 'weights_exp' and 'weights_ref' instead. -#'@param weights_exp A named two-dimensional numerical array of the forecast -#' ensemble weights for each member and time. The dimension names should -#' include 'memb_dim' and 'time_dim'. The default value is NULL. The ensemble -#' should have at least 70 members or span at least 10 time steps and have -#' more than 45 members if consistency between the weighted and unweighted -#' methodologies is desired. -#'@param weights_ref Same as 'weights_exp' but for the reference ensemble. +#'@param weights_exp A named numerical array of the forecast +#' ensemble weights for each dimension of 'exp' array. The dimension names +#' should include 'memb_dim', 'time_dim' and 'dat_dim' if this parameter is not +#' NULL. The default value is NULL. The ensemble should have at least 70 +#' members or span at least 10 time steps and have more than 45 members if +#' consistency between the weighted and unweighted methodologies is desired. +#'@param weights_ref Same as 'weights_exp' but for the reference ensemble. All +#' dimensions mus be equal as 'ref' parameter dimension values and names. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -83,12 +85,14 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', # Check inputs ## exp, obs, and ref (1) - if (!is.array(exp) | !is.numeric(exp)) + if (!is.array(exp) | !is.numeric(exp)) { stop("Parameter 'exp' must be a numeric array.") - if (!is.array(obs) | !is.numeric(obs)) + } + if (!is.array(obs) | !is.numeric(obs)) { stop("Parameter 'obs' must be a numeric array.") + } if (any(is.null(names(dim(exp))))| any(nchar(names(dim(exp))) == 0) | - any(is.null(names(dim(obs))))| any(nchar(names(dim(obs))) == 0)) { + any(is.null(names(dim(obs))))| any(nchar(names(dim(obs))) == 0)) { stop("Parameter 'exp' and 'obs' must have dimension names.") } if (!is.null(ref)) { @@ -99,8 +103,9 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } } ## time_dim - if (!is.character(time_dim) | length(time_dim) != 1) + if (!is.character(time_dim) | length(time_dim) != 1) { stop("Parameter 'time_dim' must be a character string.") + } if (!time_dim %in% names(dim(exp)) | !time_dim %in% names(dim(obs))) { stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } @@ -148,6 +153,9 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', name_ref <- name_ref[-which(name_ref == memb_dim)] if (!is.null(dat_dim)) { if (dat_dim %in% name_ref) { + if (!identical(dim(exp)[dat_dim], dim(ref)[dat_dim])) { + stop(paste0("Parameter 'ref' must have same 'dat_dim' dimension as 'exp' or NULL.")) + } name_ref <- name_ref[-which(name_ref == dat_dim)] } } @@ -175,7 +183,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } } ## Fair - if (!is.logical(Fair) | length(Fair) > 1) { + if (!is.logical(Fair) | length(Fair) > 1) { stop("Parameter 'Fair' must be either TRUE or FALSE.") } ## weights @@ -209,7 +217,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', stop(paste0("Parameter 'weights_exp' must have the same dimension lengths ", "as 'memb_dim', 'time_dim' and 'dat_dim' in 'exp'.")) } - weights <- Reorder(weights_exp, c(time_dim, memb_dim, dat_dim)) + weights_exp <- Reorder(weights_exp, c(time_dim, memb_dim, dat_dim)) } } @@ -218,7 +226,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!is.array(weights_ref) | !is.numeric(weights_ref)) stop('Parameter "weights_ref" must be a named numeric array.') - if (is.null(dat_dim)) { + if (is.null(dat_dim) | ((!is.null(dat_dim)) && (!dat_dim %in% names(dim(ref))))) { if (length(dim(weights_ref)) != 2 | any(!names(dim(weights_ref)) %in% c(memb_dim, time_dim))) stop("Parameter 'weights_ref' must have two dimensions with the names of 'memb_dim' and 'time_dim'.") if (dim(weights_ref)[memb_dim] != dim(exp)[memb_dim] | @@ -236,7 +244,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', stop(paste0("Parameter 'weights_ref' must have the same dimension lengths ", "as 'memb_dim', 'time_dim' and 'dat_dim' in 'ref'.")) } - weights <- Reorder(weights_ref, c(time_dim, memb_dim, dat_dim)) + weights_ref <- Reorder(weights_ref, c(time_dim, memb_dim, dat_dim)) } } @@ -258,10 +266,18 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } if (!is.null(ref)) { # use "ref" as reference forecast - if (!is.null(dat_dim) && (!dat_dim %in% names(dim(ref)))) { - target_dims_ref <- c(time_dim, memb_dim) + if (!is.null(dat_dim) && (dat_dim %in% names(dim(ref)))) { + if (!memb_dim %in% names(dim(ref))) { + target_dims_ref <- c(time_dim, dat_dim) + } else { + target_dims_ref <- c(time_dim, memb_dim, dat_dim) + } } else { - target_dims_ref <- c(time_dim, memb_dim, dat_dim) + if (!memb_dim %in% names(dim(ref))) { + target_dims_ref <- c(time_dim) + } else { + target_dims_ref <- c(time_dim, memb_dim) + } } data <- list(exp = exp, obs = obs, ref = ref) target_dims = list(exp = c(time_dim, memb_dim, dat_dim), @@ -321,20 +337,17 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', for (j in 1:nobs) { obs_data <- obs[ , , j] if (is.null(dim(obs_data))) dim(obs_data) <- c(dim(obs)[1:2]) + # obs_probs: [bin, sdate] obs_probs <- .get_probs(data = obs_data, indices_for_quantiles = indices_for_clim, prob_thresholds = prob_thresholds, weights = NULL) - # obs_probs: [bin, sdate] - + # clim_probs: [bin, sdate] clim_probs <- c(prob_thresholds[1], diff(prob_thresholds), 1 - prob_thresholds[length(prob_thresholds)]) clim_probs <- array(clim_probs, dim = dim(obs_probs)) - # clim_probs: [bin, sdate] # Calculate RPS for each time step probs_clim_cumsum <- apply(clim_probs, 2, cumsum) probs_obs_cumsum <- apply(obs_probs, 2, cumsum) rps_ref[ , j] <- apply((probs_clim_cumsum - probs_obs_cumsum)^2, 2, sum) - - # rps_ref: [sdate, (nobs)] # if (Fair) { # FairRPS # ## adjustment <- rowSums(-1 * (1/R - 1/R.new) * ens.cum * (R - ens.cum)/R/(R - 1)) [formula taken from SpecsVerification::EnsRps] @@ -344,40 +357,59 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', # adjustment <- apply(adjustment, 2, sum) # rps_ref <- rps_ref + adjustment # } + } if (is.null(dat_dim)) { dim(rps_ref) <- dim(exp)[time_dim] } } else { # use "ref" as reference forecast + ref_data <- ref + if (!memb_dim %in% names(dim(ref))) { + ref_data <- InsertDim(ref_data, posdim = 2, lendim = 1, name = memb_dim) + if (!is.null(weights_ref)) { + weights_ref <- InsertDim(weights_ref, posdim = 2, lendim = 1, name = memb_dim) + } + } if (!is.null(dat_dim)) { - if (!dat_dim %in% names(dim(ref))) { - ref <- InsertDim(ref, posdim = 3, lendim = 1, name = dat_dim) + if (!dat_dim %in% names(dim(ref_data))) { + ref_data <- InsertDim(ref_data, posdim = 3, lendim = 1, name = dat_dim) + if (!is.null(weights_ref)) { + weights_ref <- InsertDim(weights_ref, posdim = 3, lendim = 1, name = dat_dim) + } } } - rps_ref <- .RPS(exp = ref, obs = obs, time_dim = time_dim, memb_dim = memb_dim, dat_dim = dat_dim, + + rps_ref <- .RPS(exp = ref_data, obs = obs, time_dim = time_dim, memb_dim = memb_dim, dat_dim = dat_dim, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights = weights_ref) + if (!is.null(dat_dim)) { + if (!dat_dim %in% names(dim(ref))) { + dim(rps_ref) <- dim(rps_ref)[-2] + } + } } if (!is.null(dat_dim)) { - rpss <- array(dim = c(dim(exp)[time_dim], nexp = nexp, nobs = nobs)) - sign <- array(dim = c(dim(exp)[time_dim], nexp = nexp, nobs = nobs)) - for (i in 1:nexp) { - for (j in 1:nobs) { - rps_exp_data <- rps_exp[ , i , j] - if (!is.null(ref)) { - if (dim(rps_ref)['nexp'] == 1) { - rps_ref_data <- rps_ref[ , 1, j] - } else { - rps_ref_data <- rps_ref[ , i, j] - } - } else { - rps_ref_data <- rps_ref[ , j] + rps_exp_mean <- MeanDims(rps_exp, time_dim, na.rm = FALSE) + rps_ref_mean <- MeanDims(rps_ref, time_dim, na.rm = FALSE) + rpss <- array(dim = c(nexp = nexp, nobs = nobs)) + sign <- array(dim = c(nexp = nexp, nobs = nobs)) + + if (length(dim(rps_ref_mean)) == 1) { + for (i in 1:nexp) { + for (j in 1:nobs) { + rpss[i, j] <- 1- rps_exp_mean[i, j] / rps_ref_mean[j] + sign[i, j] <- .RandomWalkTest(skill_A = rps_exp_mean[i, j], skill_B = rps_ref_mean[j])$signif + } + } + } else { + for (i in 1:nexp) { + for (j in 1:nobs) { + rpss[i, j] <- 1- rps_exp_mean[i, j] / rps_ref_mean[i, j] + sign[i, j] <- .RandomWalkTest(skill_A = rps_exp_mean[i, j], skill_B = rps_ref_mean[i, j])$signif } - rpss[ , i , j] <- 1 - mean(rps_exp_data) / mean(rps_ref_data) - sign[ , i , j] <- .RandomWalkTest(skill_A = rps_exp_data, skill_B = rps_ref_data)$signif } } } else { @@ -388,4 +420,3 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', return(list(rpss = rpss, sign = sign)) } - diff --git a/man/RPS.Rd b/man/RPS.Rd index 21b1e02..e5d24a0 100644 --- a/man/RPS.Rd +++ b/man/RPS.Rd @@ -18,8 +18,8 @@ RPS( ) } \arguments{ -\item{exp}{A named numerical array of the forecast with at least time -dimension.} +\item{exp}{A named numerical array of the forecast with at least time and +member dimension.} \item{obs}{A named numerical array of the observation with at least time dimension. The dimensions must be the same as 'exp' except 'memb_dim' and @@ -32,7 +32,7 @@ The default value is 'sdate'.} to compute the probabilities of the forecast. The default value is 'member'.} \item{dat_dim}{A character string indicating the name of dataset dimension. -The length of this dimension can be different between ’exp’ and ’obs’. +The length of this dimension can be different between 'exp' and 'obs'. The default value is NULL.} \item{prob_thresholds}{A numeric vector of the relative thresholds (from 0 to @@ -60,8 +60,8 @@ computation. The default value is NULL.} \value{ A numerical array of RPS with dimensions c(nexp, nobs, the rest dimensions of 'exp' except 'time_dim' and 'memb_dim' dimensions). nexp is the number of -experiment (i.e., dat_dim in exp), and nobs is the number of observation (i.e. -, dat_dim in obs). If dat_dim is NULL, nexp and nobs are omitted. +experiment (i.e., dat_dim in exp), and nobs is the number of observation +(i.e., dat_dim in obs). If dat_dim is NULL, nexp and nobs are omitted. } \description{ The Ranked Probability Score (RPS; Wilks, 2011) is defined as the sum of the diff --git a/man/RPSS.Rd b/man/RPSS.Rd index 90fe0ba..63b2091 100644 --- a/man/RPSS.Rd +++ b/man/RPSS.Rd @@ -25,12 +25,14 @@ RPSS( member dimension.} \item{obs}{A named numerical array of the observation with at least time -dimension. The dimensions must be the same as 'exp' except 'memb_dim'.} +dimension. The dimensions must be the same as 'exp' except 'memb_dim' and +'dat_dim'.} \item{ref}{A named numerical array of the reference forecast data with at least time and member dimension. The dimensions must be the same as 'exp' -except 'memb_dim' and 'dat_dim'. If it is NULL, the climatological forecast -is used as reference forecast. The default value is NULL.} +except 'memb_dim' and 'dat_dim', 'dat_dim' can be either equal of 'exp' or +NULL. If 'ref' is NULL, the climatological forecast is used as reference +forecast. The default value is NULL.} \item{time_dim}{A character string indicating the name of the time dimension. The default value is 'sdate'.} @@ -40,7 +42,7 @@ to compute the probabilities of the forecast and the reference forecast. The default value is 'member'.} \item{dat_dim}{A character string indicating the name of dataset dimension. -The length of this dimension can be different between ’exp’ and ’obs’. +The length of this dimension can be different between 'exp' and 'obs'. The default value is NULL.} \item{prob_thresholds}{A numeric vector of the relative thresholds (from 0 to @@ -58,26 +60,29 @@ The default value is FALSE.} \item{weights}{Deprecated and will be removed in the next release. Please use 'weights_exp' and 'weights_ref' instead.} -\item{weights_exp}{A named two-dimensional numerical array of the forecast -ensemble weights for each member and time. The dimension names should -include 'memb_dim' and 'time_dim'. The default value is NULL. The ensemble -should have at least 70 members or span at least 10 time steps and have -more than 45 members if consistency between the weighted and unweighted -methodologies is desired.} +\item{weights_exp}{A named numerical array of the forecast +ensemble weights for each dimension of 'exp' array. The dimension names +should include 'memb_dim', 'time_dim' and 'dat_dim' if this parameter is not +NULL. The default value is NULL. The ensemble should have at least 70 +members or span at least 10 time steps and have more than 45 members if +consistency between the weighted and unweighted methodologies is desired.} -\item{weights_ref}{Same as 'weights_exp' but for the reference ensemble.} +\item{weights_ref}{Same as 'weights_exp' but for the reference ensemble. All +dimensions mus be equal as 'ref' parameter dimension values and names.} \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} } \value{ \item{$rpss}{ - A numerical array of the RPSS with the same dimensions as "exp" except the - 'time_dim' and 'memb_dim' dimensions. + A numerical array of RPSS with dimensions c(nexp, nobs, the rest dimensions + of 'exp' except 'time_dim' and 'memb_dim' dimensions). nexp is the number of + experiment (i.e., dat_dim in exp), and nobs is the number of observation + i.e., dat_dim in obs). If dat_dim is NULL, nexp and nobs are omitted. } \item{$sign}{ - A logical array of the statistical significance of the RPSS with the same - dimensions as 'exp' except the 'time_dim' and 'memb_dim' dimensions. + A logical array of the statistical significance of the RPSS with the same + dimensions as $rpss. } } \description{ @@ -90,8 +95,10 @@ reference forecast, while a negative value means that it has a lower skill. Examples of reference forecasts are the climatological forecast (same probabilities for all categories for all time steps), persistence, a previous model version, and another model. It is computed as -RPSS = 1 - RPS_exp / RPS_ref. The statistical significance is obtained based -on a Random Walk test at the 95% confidence level (DelSole and Tippett, 2016). +\code{RPSS = 1 - RPS_exp / RPS_ref}. The statistical significance is obtained +based on a Random Walk test at the 95% confidence level (DelSole and Tippett, +2016). If there is more than one dataset, RPS will be computed for each pair +of exp and obs data. } \examples{ exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index aefe587..bfaba92 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -11,6 +11,7 @@ set.seed(3) ref1 <- array(rnorm(40), dim = c(member = 2, sdate = 10, lat = 2)) set.seed(4) weights1 <- array(abs(rnorm(30)), dim = c(member = 3, sdate = 10)) + # dat2 set.seed(1) exp2 <- array(rnorm(20), dim = c(member = 2, sdate = 10)) @@ -20,107 +21,136 @@ set.seed(2) obs2_1 <- array(rnorm(10), dim = c(member = 1, sdate = 10)) set.seed(3) ref2 <- array(rnorm(20), dim = c(member = 2, sdate = 10)) +set.seed(4) +weights2 <- array(abs(rnorm(60)), dim = c(member = 2, sdate = 10)) + # dat3 set.seed(1) -exp3 <- array(rnorm(20), dim = c(member = 2, sdate = 10, dataset = 3)) +exp3 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)) set.seed(2) -obs3 <- array(rnorm(10), dim = c(member = 1, sdate = 10, dataset = 2)) +obs3 <- array(rnorm(20), dim = c(member = 1, sdate = 10, dataset = 2)) set.seed(3) -ref3 <- array(rnorm(20), dim = c(member = 2, sdate = 10, dataset = 3)) +ref3 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)) set.seed(4) weights3 <- array(abs(rnorm(60)), dim = c(member = 2, sdate = 10, dataset = 3)) +# dat4 +set.seed(1) +exp4 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3, lat = 2)) +set.seed(2) +obs4 <- array(rnorm(20), dim = c(member = 1, sdate = 10, dataset = 2, lat = 2)) +set.seed(3) +ref4 <- array(rnorm(60), dim = c(member = 2, sdate = 10, lat = 2)) +set.seed(4) +weights_exp4 <- array(abs(rnorm(60)), dim = c(member = 2, sdate = 10, dataset = 3)) +set.seed(5) +weights_ref4 <- array(abs(rnorm(20)), dim = c(member = 2, sdate = 10)) + +# dat5 +set.seed(1) +exp5 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)) +set.seed(2) +obs5 <- array(rnorm(20), dim = c(member = 1, sdate = 10, dataset = 2)) +set.seed(3) +ref5 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 2)) +set.seed(4) +weights5 <- array(abs(rnorm(60)), dim = c(member = 2, sdate = 10, dataset = 3)) + ############################################## test_that("1. Input checks", { # exp and obs (1) expect_error( - RPSS(c()), - "Parameter 'exp' must be a numeric array." + RPSS(c()), + "Parameter 'exp' must be a numeric array." ) expect_error( - RPSS(exp1, c()), - "Parameter 'obs' must be a numeric array." + RPSS(exp1, c()), + "Parameter 'obs' must be a numeric array." ) # time_dim expect_error( - RPSS(exp1, obs1, time_dim = 1), - "Parameter 'time_dim' must be a character string." + RPSS(exp1, obs1, time_dim = 1), + "Parameter 'time_dim' must be a character string." ) expect_error( - RPSS(exp1, obs1, time_dim = 'time'), - "Parameter 'time_dim' is not found in 'exp' or 'obs' dimension." + RPSS(exp1, obs1, time_dim = 'time'), + "Parameter 'time_dim' is not found in 'exp' or 'obs' dimension." ) expect_error( - RPSS(exp2, obs2, array(rnorm(20), dim = c(member = 2, time = 10))), - "Parameter 'time_dim' is not found in 'ref' dimension." + RPSS(exp2, obs2, array(rnorm(20), dim = c(member = 2, time = 10))), + "Parameter 'time_dim' is not found in 'ref' dimension." ) # memb_dim expect_error( - RPSS(exp1, obs1, memb_dim = TRUE), - "Parameter 'memb_dim' must be a character string." + RPSS(exp1, obs1, memb_dim = TRUE), + "Parameter 'memb_dim' must be a character string." ) expect_error( - RPSS(exp1, obs1, memb_dim = 'memb'), - "Parameter 'memb_dim' is not found in 'exp' dimension." + RPSS(exp1, obs1, memb_dim = 'memb'), + "Parameter 'memb_dim' is not found in 'exp' dimension." ) expect_error( - RPSS(exp2, obs2, array(rnorm(20), dim = c(memb = 2, sdate = 10))), - "Parameter 'memb_dim' is not found in 'ref' dimension." + RPSS(exp2, obs2, array(rnorm(20), dim = c(memb = 2, sdate = 10))), + "Parameter 'memb_dim' is not found in 'ref' dimension." ) # exp, ref, and obs (2) expect_error( - RPSS(exp1, array(1:9, dim = c(sdate = 9))), - "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." + RPSS(exp1, array(1:9, dim = c(sdate = 9))), + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." ) expect_error( - RPSS(exp1, obs1, ref2), - "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." + RPSS(exp1, obs1, ref2), + "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." + ) + expect_error( + RPSS(exp3, obs3, array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 4)), dat_dim = 'dataset'), + "Parameter 'ref' must have same 'dat_dim' dimension as 'exp' or NULL." ) # prob_thresholds expect_error( - RPSS(exp1, obs1, ref1, prob_thresholds = 1), - "Parameter 'prob_thresholds' must be a numeric vector between 0 and 1." + RPSS(exp1, obs1, ref1, prob_thresholds = 1), + "Parameter 'prob_thresholds' must be a numeric vector between 0 and 1." ) # indices_for_clim expect_error( - RPSS(exp1, obs1, ref1, indices_for_clim = array(1:6, dim = c(2, 3))), - "Parameter 'indices_for_clim' must be NULL or a numeric vector." + RPSS(exp1, obs1, ref1, indices_for_clim = array(1:6, dim = c(2, 3))), + "Parameter 'indices_for_clim' must be NULL or a numeric vector." ) expect_error( - RPSS(exp1, obs1, indices_for_clim = 3:11), - "Parameter 'indices_for_clim' should be the indices of 'time_dim'." + RPSS(exp1, obs1, indices_for_clim = 3:11), + "Parameter 'indices_for_clim' should be the indices of 'time_dim'." ) # Fair expect_error( - RPSS(exp1, obs1, Fair = 1), - "Parameter 'Fair' must be either TRUE or FALSE." + RPSS(exp1, obs1, Fair = 1), + "Parameter 'Fair' must be either TRUE or FALSE." ) # weights_exp and weights_ref expect_error( - RPSS(exp1, obs1, weights_exp = c(0, 1)), - "Parameter 'weights_exp' must be a named numeric array." + RPSS(exp1, obs1, weights_exp = c(0, 1)), + "Parameter 'weights_exp' must be a named numeric array." ) expect_error( - RPSS(exp1, obs1, weights_exp = array(1, dim = c(member = 3, time = 10))), - "Parameter 'weights_exp' must have two dimensions with the names of 'memb_dim' and 'time_dim'." + RPSS(exp1, obs1, weights_exp = array(1, dim = c(member = 3, time = 10))), + "Parameter 'weights_exp' must have two dimensions with the names of 'memb_dim' and 'time_dim'." ) expect_error( - RPSS(exp1, obs1, ref1, weights_ref = array(1, dim = c(member = 3, sdate = 1))), - "Parameter 'weights_ref' must have the same dimension lengths as 'memb_dim' and 'time_dim' in 'ref'." + RPSS(exp1, obs1, ref1, weights_ref = array(1, dim = c(member = 3, sdate = 1))), + "Parameter 'weights_ref' must have the same dimension lengths as 'memb_dim' and 'time_dim' in 'ref'." ) expect_error( - RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = weights1), - "Parameter 'weights_exp' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'." + RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = weights1), + "Parameter 'weights_exp' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'." ) expect_error( - RPSS(exp3, obs3, ref3, dat_dim = 'dataset', weights_ref = weights1), - "Parameter 'weights_ref' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'." + RPSS(exp3, obs3, ref3, dat_dim = 'dataset', weights_ref = weights1), + "Parameter 'weights_ref' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'." ) # ncores expect_error( - RPSS(exp2, obs2, ncores = 1.5), - "Parameter 'ncores' must be either NULL or a positive integer." + RPSS(exp2, obs2, ncores = 1.5), + "Parameter 'ncores' must be either NULL or a positive integer." ) }) @@ -129,91 +159,91 @@ test_that("1. Input checks", { test_that("2. Output checks: dat1", { expect_equal( - names(RPSS(exp1, obs1)), - c("rpss", "sign") + names(RPSS(exp1, obs1)), + c("rpss", "sign") ) expect_equal( - names(RPSS(exp1, obs1, ref1)), - c("rpss", "sign") + names(RPSS(exp1, obs1, ref1)), + c("rpss", "sign") ) expect_equal( - dim(RPSS(exp1, obs1)$rpss), - c(lat = 2) + dim(RPSS(exp1, obs1)$rpss), + c(lat = 2) ) expect_equal( - dim(RPSS(exp1, obs1)$sign), - c(lat = 2) + dim(RPSS(exp1, obs1)$sign), + c(lat = 2) ) expect_equal( - dim(RPSS(exp1, obs1, ref1)$rpss), - c(lat = 2) + dim(RPSS(exp1, obs1, ref1)$rpss), + c(lat = 2) ) expect_equal( - dim(RPSS(exp1, obs1, ref1)$sign), - c(lat = 2) + dim(RPSS(exp1, obs1, ref1)$sign), + c(lat = 2) ) # ref = NULL expect_equal( - as.vector(RPSS(exp1, obs1)$rpss), - c(0.15789474, -0.05263158), - tolerance = 0.0001 + as.vector(RPSS(exp1, obs1)$rpss), + c(0.15789474, -0.05263158), + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp1, obs1)$sign), - c(FALSE, FALSE), + as.vector(RPSS(exp1, obs1)$sign), + c(FALSE, FALSE), ) expect_equal( - as.vector(RPSS(exp1, obs1, Fair = T)$rpss), - c(0.5263158, 0.3684211), - tolerance = 0.0001 + as.vector(RPSS(exp1, obs1, Fair = T)$rpss), + c(0.5263158, 0.3684211), + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp1, obs1, indices_for_clim = 3:5)$rpss), - c(-0.4062500, -0.1052632), - tolerance = 0.0001 + as.vector(RPSS(exp1, obs1, indices_for_clim = 3:5)$rpss), + c(-0.4062500, -0.1052632), + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp1, obs1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), - c(0.03030303, -0.14478114), - tolerance = 0.0001 + as.vector(RPSS(exp1, obs1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + c(0.03030303, -0.14478114), + tolerance = 0.0001 ) # ref = ref expect_equal( - as.vector(RPSS(exp1, obs1, ref1)$rpss), - c(0.5259259, 0.4771242), - tolerance = 0.0001 + as.vector(RPSS(exp1, obs1, ref1)$rpss), + c(0.5259259, 0.4771242), + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1, weights_ref = weights1)$rpss), - c(0.6596424, 0.4063579), - tolerance = 0.0001 + as.vector(RPSS(exp1, obs1, ref1, weights_exp = weights1, weights_ref = weights1)$rpss), + c(0.6596424, 0.4063579), + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp1, obs1, ref1)$sign), - c(FALSE, FALSE) + as.vector(RPSS(exp1, obs1, ref1)$sign), + c(FALSE, FALSE) ) expect_equal( - as.vector(RPSS(exp1, obs1, ref1, Fair = T)$rpss), - c(0.6000000, 0.6190476), - tolerance = 0.0001 + as.vector(RPSS(exp1, obs1, ref1, Fair = T)$rpss), + c(0.6000000, 0.6190476), + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$rpss), - c(0.2857143, 0.4166667), - tolerance = 0.0001 + as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$rpss), + c(0.2857143, 0.4166667), + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$sign), - c(FALSE, FALSE) + as.vector(RPSS(exp1, obs1, ref1, indices_for_clim = 3:5)$sign), + c(FALSE, FALSE) ) expect_equal( - as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), - c(0.4754098, 0.3703704), - tolerance = 0.0001 + as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + c(0.4754098, 0.3703704), + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), - c(T, F) + as.vector(RPSS(exp1, obs1, ref1, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), + c(T, F) ) }) @@ -222,113 +252,135 @@ test_that("2. Output checks: dat1", { test_that("3. Output checks: dat2", { expect_equal( - names(RPSS(exp2, obs2)), - c("rpss", "sign") + names(RPSS(exp2, obs2)), + c("rpss", "sign") ) expect_equal( - names(RPSS(exp2, obs2, ref2)), - c("rpss", "sign") + names(RPSS(exp2, obs2, ref2)), + c("rpss", "sign") ) expect_equal( - dim(RPSS(exp2, obs2)$rpss), - NULL + dim(RPSS(exp2, obs2)$rpss), + NULL ) expect_equal( - dim(RPSS(exp2, obs2)$sign), - NULL + dim(RPSS(exp2, obs2)$sign), + NULL ) expect_equal( - dim(RPSS(exp2, obs2, ref2)$rpss), - NULL + dim(RPSS(exp2, obs2, ref2)$rpss), + NULL ) expect_equal( - dim(RPSS(exp2, obs2, ref2)$sign), - NULL + dim(RPSS(exp2, obs2, ref2)$sign), + NULL ) # ref = NULL expect_equal( - as.vector(RPSS(exp2, obs2)$rpss), - c(-0.7763158), - tolerance = 0.0001 + as.vector(RPSS(exp2, obs2)$rpss), + c(-0.7763158), + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp2, obs2)$sign), - FALSE, + as.vector(RPSS(exp2, obs2)$sign), + FALSE, ) expect_equal( - as.vector(RPSS(exp2, obs2, Fair = T)$rpss), - c(-0.1842105), - tolerance = 0.0001 + as.vector(RPSS(exp2, obs2, Fair = T)$rpss), + c(-0.1842105), + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp2, obs2, indices_for_clim = 3:5)$rpss), - -0.8984375, - tolerance = 0.0001 + as.vector(RPSS(exp2, obs2, indices_for_clim = 3:5)$rpss), + -0.8984375, + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp2, obs2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), - c(-0.7272727), - tolerance = 0.0001 + as.vector(RPSS(exp2, obs2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + c(-0.7272727), + tolerance = 0.0001 ) # ref = ref expect_equal( - as.vector(RPSS(exp2, obs2, ref2)$rpss), - 0, - tolerance = 0.0001 + as.vector(RPSS(exp2, obs2, ref2)$rpss), + 0, + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp2, obs2, ref2)$sign), - FALSE + as.vector(RPSS(exp2, obs2, ref2)$sign), + FALSE ) expect_equal( - as.vector(RPSS(exp2, obs2, ref2, Fair = T)$rpss), - 0, - tolerance = 0.0001 + as.vector(RPSS(exp2, obs2, ref2, Fair = T)$rpss), + 0, + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$rpss), - 0.03571429, - tolerance = 0.0001 + as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$rpss), + 0.03571429, + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$sign), - FALSE + as.vector(RPSS(exp2, obs2, ref2, indices_for_clim = 3:5)$sign), + FALSE ) expect_equal( - as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), - 0.06557377, - tolerance = 0.0001 + as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$rpss), + 0.06557377, + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), - FALSE + as.vector(RPSS(exp2, obs2, ref2, prob_thresholds = seq(0.1, 0.9, 0.1))$sign), + FALSE ) expect_equal( - RPSS(exp2, obs2), - RPSS(exp2, obs2_1) + RPSS(exp2, obs2), + RPSS(exp2, obs2_1) ) - }) - ############################################## +}) +############################################## test_that("4. Output checks: dat3", { expect_equal( dim(RPSS(exp3, obs3, dat_dim = 'dataset')$rpss), - c('sdate' = 10, 'nexp' = 3, 'nobs' = 2) + c('nexp' = 3, 'nobs' = 2) + ) + expect_equal( + mean(RPSS(exp3, obs3, dat_dim = 'dataset')$rpss), + c(-0.8157895), + tolerance = 0.0001 + ) + expect_equal( + as.vector(RPSS(exp3, obs3, dat_dim = 'dataset')$sign)[1:3], + c(FALSE, FALSE, FALSE), + ) + expect_equal( + mean(RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = weights3, Fair = T)$rpss), + c(-0.7015067), + tolerance = 0.0001 + ) +}) + +############################################## +test_that("5. Output checks: dat4, dat2 and dat5", { + + expect_equal( + dim(RPSS(exp4, obs4, ref4, dat_dim = 'dataset')$rpss), + c('nexp' = 3, 'nobs' = 2, 'lat' = 2) ) expect_equal( - mean(RPSS(exp3, obs3, dat_dim = 'dataset')$rpss), - c(-0.7763158), - tolerance = 0.0001 + mean(RPSS(exp4, obs4, ref4, dat_dim = 'dataset', weights_exp = weights_exp4, weights_ref = weights_ref4)$rpss), + c(-0.03715614), + tolerance = 0.0001 ) expect_equal( - as.vector(RPSS(exp3, obs3, dat_dim = 'dataset')$sign)[1:3], - c(FALSE, FALSE, FALSE), + RPSS(exp2, obs2)$rpss, + RPSS(exp3, obs3, dat_dim = 'dataset')$rpss[1] ) expect_equal( - mean(RPSS(exp3, obs3, dat_dim = 'dataset', Fair = T)$rpss), - c(-0.1842105), - tolerance = 0.0001 + RPSS(exp2, obs2, weights_exp = weights2)$rpss, + RPSS(exp5, obs5, dat_dim = 'dataset', weights_exp = weights5)$rpss[1] ) }) -- GitLab From 94392abcf4f7a2e88bc21ff249b2a2c5250b71e7 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 17 Aug 2022 09:57:31 +0200 Subject: [PATCH 077/140] Add dat_dim parameter in CRPSS and test file --- R/CRPSS.R | 222 ++++++++++++++++++++------ tests/testthat/test-CRPSS.R | 300 ++++++++++++++++++++++++++++++++++++ 2 files changed, 473 insertions(+), 49 deletions(-) create mode 100644 tests/testthat/test-CRPSS.R diff --git a/R/CRPSS.R b/R/CRPSS.R index 9f5edbd..80a709c 100644 --- a/R/CRPSS.R +++ b/R/CRPSS.R @@ -1,33 +1,40 @@ #'Compute the Continuous Ranked Probability Skill Score #' -#'The Continuous Ranked Probability Skill Score (CRPSS; Wilks, 2011) is the skill score -#'based on the Continuous Ranked Probability Score (CRPS; Wilks, 2011). It can be used to -#'assess whether a forecast presents an improvement or worsening with respect to -#'a reference forecast. The CRPSS ranges between minus infinite and 1. If the -#'CRPSS is positive, it indicates that the forecast has higher skill than the -#'reference forecast, while a negative value means that it has a lower skill. -#'Examples of reference forecasts are the climatological forecast (same -#'probabilities for all categories for all time steps), persistence, a previous -#'model version, and another model. It is computed as CRPSS = 1 - CRPS_exp / CRPS_ref. -#'The statistical significance is obtained based on a Random Walk test at the -#'95% confidence level (DelSole and Tippett, 2016). +#'The Continuous Ranked Probability Skill Score (CRPSS; Wilks, 2011) is the +#'skill score based on the Continuous Ranked Probability Score (CRPS; Wilks, +#'2011). It can be used to assess whether a forecast presents an improvement or +#'worsening with respect to a reference forecast. The CRPSS ranges between minus +#'infinite and 1. If the CRPSS is positive, it indicates that the forecast has +#'higher skill than the reference forecast, while a negative value means that it +#'has a lower skill. Examples of reference forecasts are the climatological +#'forecast (same probabilities for all categories for all time steps), +#'persistence, a previous model version, and another model. It is computed as +#'CRPSS = 1 - CRPS_exp / CRPS_ref. The statistical significance is obtained +#'based on a Random Walk test at the 95% confidence level (DelSole and Tippett, +#'2016). #' #'@param exp A named numerical array of the forecast with at least time #' dimension. #'@param obs A named numerical array of the observation with at least time #' dimension. The dimensions must be the same as 'exp' except 'memb_dim'. #'@param ref A named numerical array of the reference forecast data with at -#' least time dimension. The dimensions must be the same as 'exp' except -#' 'memb_dim'. If it is NULL, the climatological forecast is used as reference -#' forecast. The default value is NULL. +#' least time and member dimension. The dimensions must be the same as 'exp' +#' except 'memb_dim' and 'dat_dim'. If the 'dat_dim' parameter is not NULL, the +#' corresponding dimension in 'ref' array must either be equal to the dataset +#' dimension of the 'exp' array or that 'ref' does not include the dimension of +#' 'dat_dim'. If 'ref' is NULL, the climatological forecast is used as +#' reference forecast. The default value is NULL. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. #'@param memb_dim A character string indicating the name of the member dimension #' to compute the probabilities of the forecast and the reference forecast. The #' default value is 'member'. +#'@param dat_dim A character string indicating the name of dataset dimension. +#' The length of this dimension can be different between 'exp' and 'obs'. +#' The default value is NULL. #'@param Fair A logical indicating whether to compute the FairCRPSS (the -#' potential CRPSS that the forecast would have with an infinite ensemble size). -#' The default value is FALSE. +#' potential CRPSS that the forecast would have with an infinite ensemble +#' size). The default value is FALSE. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -55,22 +62,32 @@ #'@import multiApply #'@importFrom ClimProjDiags Subset #'@export -CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', +CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, Fair = FALSE, ncores = NULL) { # Check inputs ## exp, obs, and ref (1) - if (!is.array(exp) | !is.numeric(exp)) - stop('Parameter "exp" must be a numeric array.') - if (!is.array(obs) | !is.numeric(obs)) - stop('Parameter "obs" must be a numeric array.') + if (!is.array(exp) | !is.numeric(exp)) { + stop("Parameter 'exp' must be a numeric array.") + } + if (!is.array(obs) | !is.numeric(obs)) { + stop("Parameter 'obs' must be a numeric array.") + } + if (any(is.null(names(dim(exp))))| any(nchar(names(dim(exp))) == 0) | + any(is.null(names(dim(obs))))| any(nchar(names(dim(obs))) == 0)) { + stop("Parameter 'exp' and 'obs' must have dimension names.") + } if (!is.null(ref)) { if (!is.array(ref) | !is.numeric(ref)) - stop('Parameter "ref" must be a numeric array.') + stop("Parameter 'ref' must be a numeric array.") + if (any(is.null(names(dim(ref))))| any(nchar(names(dim(ref))) == 0)) { + stop("Parameter 'ref' must have dimension names.") + } } ## time_dim - if (!is.character(time_dim) | length(time_dim) != 1) - stop('Parameter "time_dim" must be a character string.') + if (!is.character(time_dim) | length(time_dim) != 1) { + stop("Parameter 'time_dim' must be a character string.") + } if (!time_dim %in% names(dim(exp)) | !time_dim %in% names(dim(obs))) { stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } @@ -87,27 +104,54 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!is.null(ref) & !memb_dim %in% names(dim(ref))) { stop("Parameter 'memb_dim' is not found in 'ref' dimension.") } + ## dat_dim + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + " Set it as NULL if there is no dataset dimension.") + } + } ## exp and obs (2) if (memb_dim %in% names(dim(obs))) { if (identical(as.numeric(dim(obs)[memb_dim]),1)){ obs <- ClimProjDiags::Subset(x = obs, along = memb_dim, indices = 1, drop = 'selected') - } else {stop("Not implemented for observations with members ('obs' can have 'memb_dim', but it should be of length=1).")} + } else {stop("Not implemented for observations with members ('obs' can have ", + "'memb_dim', but it should be of length=1).")} } name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) name_exp <- name_exp[-which(name_exp == memb_dim)] + if (memb_dim %in% name_obs) { + name_obs <- name_obs[-which(name_obs == memb_dim)] + } + if (!is.null(dat_dim)) { + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] + } if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { - stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions expect 'memb_dim'.")) + stop(paste0("Parameter 'exp' and 'obs' must have same length of all dimensions", + " except 'memb_dim' and 'dat_dim'.")) } if (!is.null(ref)) { name_ref <- sort(names(dim(ref))) name_ref <- name_ref[-which(name_ref == memb_dim)] + if (!is.null(dat_dim)) { + if (dat_dim %in% name_ref) { + if (!identical(dim(exp)[dat_dim], dim(ref)[dat_dim])) { + stop(paste0("If parameter 'ref' has 'dat_dim' dimension it must be", + " equal to 'dat_dim' dimension of 'exp'.")) + } + name_ref <- name_ref[-which(name_ref == dat_dim)] + } + } if (!identical(length(name_exp), length(name_ref)) | !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { - stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions expect 'memb_dim'.")) + stop(paste0("Parameter 'exp' and 'ref' must have same length of ", + "all dimensions except 'memb_dim' and 'dat_dim'.")) } } ## Fair @@ -126,47 +170,127 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', # Compute CRPSS if (!is.null(ref)) { # use "ref" as reference forecast + if (!is.null(dat_dim) && (dat_dim %in% names(dim(ref)))) { + target_dims_ref <- c(time_dim, memb_dim, dat_dim) + } else { + target_dims_ref <- c(time_dim, memb_dim) + } data <- list(exp = exp, obs = obs, ref = ref) - target_dims = list(exp = c(time_dim, memb_dim), - obs = time_dim, - ref = c(time_dim, memb_dim)) + target_dims = list(exp = c(time_dim, memb_dim, dat_dim), + obs = c(time_dim, dat_dim), + ref = target_dims_ref) } else { data <- list(exp = exp, obs = obs) - target_dims = list(exp = c(time_dim, memb_dim), - obs = time_dim) + target_dims = list(exp = c(time_dim, memb_dim, dat_dim), + obs = c(time_dim, dat_dim)) } output <- Apply(data, target_dims = target_dims, fun = .CRPSS, + time_dim = time_dim, memb_dim = memb_dim, + dat_dim = dat_dim, Fair = Fair, ncores = ncores) return(output) } -.CRPSS <- function(exp, obs, ref = NULL, Fair = FALSE) { - # exp: [sdate, memb] - # obs: [sdate] - # ref: [sdate, memb] or NULL +.CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, + Fair = FALSE) { + + # exp: [sdate, memb (dat_dim)] + # obs: [sdate, (dat_dim)] + # ref: [sdate, memb, (dat_dim)] or NULL + + if (is.null(dat_dim)) { + nexp <- 1 + nobs <- 1 + } else { + nexp <- as.numeric(dim(exp)[dat_dim]) + nobs <- as.numeric(dim(obs)[dat_dim]) + } # CRPS of the forecast - crps_exp <- .CRPS(exp = exp, obs = obs, Fair = Fair) + # [sdate, (nexp), (nobs)] + crps_exp <- .CRPS(exp = exp, obs = obs, time_dim = time_dim, memb_dim = memb_dim, + dat_dim = dat_dim, Fair = Fair) # CRPS of the reference forecast - if (is.null(ref)){ + if (is.null(ref)) { ## using climatology as reference forecast ## all the time steps are used as if they were members ## then, ref dimensions are [sdate, memb], both with length(sdate) - ref <- array(data = obs, dim = c(member = length(obs))) - ref <- InsertDim(data = ref, posdim = 1, lendim = length(obs), name = 'sdate') + if (is.null(dat_dim)) { + ref <- array(data = obs, dim = c(member = length(obs))) + ref <- InsertDim(data = ref, posdim = 1, lendim = length(obs), name = 'sdate') + crps_ref <- .CRPS(exp = ref, obs = obs, Fair = Fair) + } else { + ref <- array(dim = c(dim(obs)[1], member = unname(dim(obs)[1]), dim(obs)[2])) + for (j in 1:nobs) { + ref_obs <- array(data = obs[ , j], dim = c(member = length(obs[ , j]))) + ref_obs <- InsertDim(data = ref_obs, posdim = 1, lendim = length(obs[ , j]), name = 'sdate') + ref[ , , j] <- ref_obs + } + + # for FairCRPS + R_new <- ifelse(Fair, Inf, NA) + + crps_ref <- array(dim = c(dim(exp)[time_dim], nobs = nobs)) + + for (j in 1:nobs) { + ref_data <- ref[ , , j] + obs_data <- obs[ , j] + + if (is.null(dim(ref_data))) dim(ref_data) <- c(dim(exp)[1:2]) + if (is.null(dim(obs_data))) dim(obs_data) <- c(dim(obs)[1]) + + crps <- SpecsVerification::enscrps_cpp(ens = ref_data, obs = obs_data, R_new = R_new) + crps_ref[ , j] <- crps + } + } + + } else { + if (!is.null(dat_dim) && (!dat_dim %in% names(dim(ref)))) { + remove_dat_dim <- TRUE + ref <- InsertDim(data = ref, posdim = length(dim(ref)) + 1 , lendim = 1, name = dat_dim) + } + crps_ref <- .CRPS(exp = ref, obs = obs, time_dim = time_dim, memb_dim = memb_dim, + dat_dim = dat_dim, Fair = Fair) + if (!is.null(dat_dim)) { + if (isTRUE(remove_dat_dim)) { + dim(crps_ref) <- dim(crps_ref)[-2] + } + } + } + + if (!is.null(dat_dim)) { + + crps_exp_mean <- MeanDims(crps_exp, time_dim, na.rm = FALSE) + crps_ref_mean <- MeanDims(crps_ref, time_dim, na.rm = FALSE) + crpss <- array(dim = c(nexp = nexp, nobs = nobs)) + sign <- array(dim = c(nexp = nexp, nobs = nobs)) + + if (length(dim(crps_ref_mean)) == 1) { + for (i in 1:nexp) { + for (j in 1:nobs) { + crpss[i, j] <- 1 - crps_exp_mean[i, j] / crps_ref_mean[j] + sign[i, j] <- .RandomWalkTest(skill_A = crps_exp_mean[i, j], skill_B = crps_ref_mean[j])$signif + } + } + } else { + for (i in 1:nexp) { + for (j in 1:nobs) { + crpss[i, j] <- 1 - crps_exp_mean[i, j] / crps_ref_mean[i, j] + sign[i, j] <- .RandomWalkTest(skill_A = crps_exp_mean[i, j], skill_B = crps_ref_mean[i, j])$signif + } + } + } + + } else { + crpss <- 1 - mean(crps_exp) / mean(crps_ref) + # Significance + sign <- .RandomWalkTest(skill_A = crps_exp, skill_B = crps_ref)$signif } - crps_ref <- .CRPS(exp = ref, obs = obs, Fair = Fair) - - # CRPSS - crpss <- 1 - mean(crps_exp) / mean(crps_ref) - - # Significance - sign <- .RandomWalkTest(skill_A = crps_exp, skill_B = crps_ref)$signif return(list(crpss = crpss, sign = sign)) } diff --git a/tests/testthat/test-CRPSS.R b/tests/testthat/test-CRPSS.R new file mode 100644 index 0000000..7221e10 --- /dev/null +++ b/tests/testthat/test-CRPSS.R @@ -0,0 +1,300 @@ +context("s2dv::CRPSS tests") + +############################################## + +# dat1 +set.seed(1) +exp1 <- array(rnorm(60), dim = c(member = 3, sdate = 10, lat = 2)) +set.seed(2) +obs1 <- array(rnorm(20), dim = c(sdate = 10, lat = 2)) +set.seed(3) +ref1 <- array(rnorm(40), dim = c(member = 2, sdate = 10, lat = 2)) + +# dat2 +set.seed(1) +exp2 <- array(rnorm(20), dim = c(member = 2, sdate = 10)) +set.seed(2) +obs2 <- array(rnorm(10), dim = c(sdate = 10)) +set.seed(3) +ref2 <- array(rnorm(20), dim = c(member = 2, sdate = 10)) + +# dat2_2 +set.seed(1) +exp2_2 <- array(rnorm(20), dim = c(member = 2, sdate = 10, dataset = 1)) +set.seed(2) +obs2_2 <- array(rnorm(10), dim = c(sdate = 10, dataset = 1)) +set.seed(3) +ref2_2 <- array(rnorm(20), dim = c(member = 2, sdate = 10, dataset = 1)) + +# dat3 +set.seed(1) +exp3 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)) +set.seed(2) +obs3 <- array(rnorm(20), dim = c(member = 1, sdate = 10, dataset = 2)) +set.seed(3) +ref3 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)) + +# dat4 +set.seed(1) +exp4 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3, lat = 2)) +set.seed(2) +obs4 <- array(rnorm(20), dim = c(member = 1, sdate = 10, dataset = 2, lat = 2)) +set.seed(3) +ref4 <- array(rnorm(60), dim = c(member = 2, sdate = 10, lat = 2)) + +# dat5 +set.seed(1) +exp5 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)) +set.seed(2) +obs5 <- array(rnorm(20), dim = c(member = 1, sdate = 10, dataset = 2)) + +############################################## +test_that("1. Input checks", { + # exp and obs (1) + expect_error( + CRPSS(c()), + "Parameter 'exp' must be a numeric array." + ) + expect_error( + CRPSS(exp1, c()), + "Parameter 'obs' must be a numeric array." + ) + + # time_dim + expect_error( + CRPSS(exp1, obs1, time_dim = 1), + "Parameter 'time_dim' must be a character string." + ) + expect_error( + CRPSS(exp1, obs1, time_dim = 'time'), + "Parameter 'time_dim' is not found in 'exp' or 'obs' dimension." + ) + expect_error( + CRPSS(exp2, obs2, array(rnorm(20), dim = c(member = 2, time = 10))), + "Parameter 'time_dim' is not found in 'ref' dimension." + ) + # memb_dim + expect_error( + CRPSS(exp1, obs1, memb_dim = TRUE), + "Parameter 'memb_dim' must be a character string." + ) + expect_error( + CRPSS(exp1, obs1, memb_dim = 'memb'), + "Parameter 'memb_dim' is not found in 'exp' dimension." + ) + expect_error( + CRPSS(exp2, obs2, array(rnorm(20), dim = c(memb = 2, sdate = 10))), + "Parameter 'memb_dim' is not found in 'ref' dimension." + ) + # exp, ref, and obs (2) + expect_error( + CRPSS(exp1, array(1:9, dim = c(sdate = 9))), + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." + ) + expect_error( + CRPSS(exp2_2, obs2_2, array(1:9, dim = c(sdate = 9, member = 2, dataset = 3)), dat_dim = 'dataset'), + "If parameter 'ref' has 'dat_dim' dimension it must be equal to 'dat_dim' dimension of 'exp'." + ) + expect_error( + CRPSS(exp1, obs1, ref2), + "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." + ) + expect_error( + CRPSS(exp3, array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)), ref3, dat_dim = 'dataset'), + "Not implemented for observations with members ('obs' can have 'memb_dim', but it should be of length=1)." + , fixed = TRUE + ) + # Fair + expect_error( + CRPSS(exp1, obs1, Fair = 1), + "Parameter 'Fair' must be either TRUE or FALSE." + ) + # ncores + expect_error( + CRPSS(exp2, obs2, ncores = 1.5), + "Parameter 'ncores' must be either NULL or a positive integer." + ) + +}) + +############################################## +test_that("2. Output checks: dat1", { + + expect_equal( + names(CRPSS(exp1, obs1)), + c("crpss", "sign") + ) + expect_equal( + names(CRPSS(exp1, obs1, ref1)), + c("crpss", "sign") + ) + expect_equal( + dim(CRPSS(exp1, obs1)$crpss), + c(lat = 2) + ) + expect_equal( + dim(CRPSS(exp1, obs1)$sign), + c(lat = 2) + ) + expect_equal( + dim(CRPSS(exp1, obs1, ref1)$crpss), + c(lat = 2) + ) + expect_equal( + dim(CRPSS(exp1, obs1, ref1)$sign), + c(lat = 2) + ) + # ref = NULL + expect_equal( + as.vector(CRPSS(exp1, obs1)$crpss), + c(-0.1582765, -0.2390707), + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPSS(exp1, obs1)$sign), + c(FALSE, FALSE), + ) + expect_equal( + as.vector(CRPSS(exp1, obs1, Fair = T)$crpss), + c(0.07650872, -0.09326681), + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPSS(exp1, obs1)$crpss), + c(-0.1582765, -0.2390707), + tolerance = 0.0001 + ) + # ref = ref + expect_equal( + as.vector(CRPSS(exp1, obs1, ref1)$crpss), + c(0.3491793, 0.3379610), + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPSS(exp1, obs1, ref1)$sign), + c(FALSE, FALSE) + ) + expect_equal( + as.vector(CRPSS(exp1, obs1, ref1, Fair = T)$crpss), + c( 0.3901440, 0.3788467), + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPSS(exp1, obs1, ref1)$crpss), + c( 0.3491793, 0.3379610), + tolerance = 0.0001 + ) + +}) + +############################################## +test_that("3. Output checks: dat2", { + + expect_equal( + names(CRPSS(exp2, obs2)), + c("crpss", "sign") + ) + expect_equal( + names(CRPSS(exp2, obs2, ref2)), + c("crpss", "sign") + ) + expect_equal( + dim(CRPSS(exp2, obs2)$crpss), + NULL + ) + expect_equal( + dim(CRPSS(exp2, obs2)$sign), + NULL + ) + expect_equal( + dim(CRPSS(exp2, obs2, ref2)$crpss), + NULL + ) + expect_equal( + dim(CRPSS(exp2, obs2, ref2)$sign), + NULL + ) + # ref = NULL + expect_equal( + as.vector(CRPSS(exp2, obs2)$crpss), + c(-0.8209236), + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPSS(exp2, obs2)$crpss), + as.vector(CRPSS(exp2_2, obs2_2, dat_dim = 'dataset')$crpss), + tolerance = 0.0001 + ) + + expect_equal( + as.vector(CRPSS(exp2, obs2)$sign), + TRUE, + ) + expect_equal( + as.vector(CRPSS(exp2, obs2, Fair = T)$crpss), + c(-0.468189), + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPSS(exp2, obs2, Fair = T)$crpss), + as.vector(CRPSS(exp2_2, obs2_2, dat_dim = 'dataset', Fair = T)$crpss), + tolerance = 0.0001 + ) + # ref = ref + expect_equal( + as.vector(CRPSS(exp2, obs2, ref2)$crpss), + -0.02315361, + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPSS(exp2, obs2, ref2)$crpss), + as.vector(CRPSS(exp2_2, obs2_2, ref2_2, dat_dim = 'dataset')$crpss), + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPSS(exp2, obs2, ref2)$sign), + FALSE + ) + expect_equal( + as.vector(CRPSS(exp2, obs2, ref2, Fair = T)$crpss), + 0.030436, + tolerance = 0.0001 + ) + +}) +############################################## +test_that("4. Output checks: dat3", { + + expect_equal( + dim(CRPSS(exp3, obs3, dat_dim = 'dataset')$crpss), + c('nexp' = 3, 'nobs' = 2) + ) + expect_equal( + mean(CRPSS(exp3, obs3, dat_dim = 'dataset')$crpss), + c(-0.7390546), + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPSS(exp3, obs3, dat_dim = 'dataset')$sign)[1:3], + c(FALSE, FALSE, FALSE), + ) + expect_equal( + mean(CRPSS(exp3, obs3, dat_dim = 'dataset', Fair = T)$crpss), + c(-0.5302703), + tolerance = 0.0001 + ) +}) + +############################################## +test_that("5. Output checks: dat4, dat2 and dat5", { + + expect_equal( + dim(CRPSS(exp4, obs4, ref4, dat_dim = 'dataset')$crpss), + c('nexp' = 3, 'nobs' = 2, 'lat' = 2) + ) + expect_equal( + CRPSS(exp2, obs2)$crpss, + CRPSS(exp3, obs3, dat_dim = 'dataset')$crpss[1] + ) + +}) -- GitLab From 3d7113cf4ae839c43ddf51e9f54b87351f048df6 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 17 Aug 2022 10:13:01 +0200 Subject: [PATCH 078/140] Update documentation --- man/CRPSS.Rd | 41 +++++++++++++++++++++++++---------------- man/s2dv-package.Rd | 10 +++++++++- man/sampleDepthData.Rd | 6 ++---- man/sampleMap.Rd | 6 ++---- man/sampleTimeSeries.Rd | 6 ++---- 5 files changed, 40 insertions(+), 29 deletions(-) diff --git a/man/CRPSS.Rd b/man/CRPSS.Rd index 85705f3..5ab9726 100644 --- a/man/CRPSS.Rd +++ b/man/CRPSS.Rd @@ -10,6 +10,7 @@ CRPSS( ref = NULL, time_dim = "sdate", memb_dim = "member", + dat_dim = NULL, Fair = FALSE, ncores = NULL ) @@ -22,9 +23,12 @@ dimension.} dimension. The dimensions must be the same as 'exp' except 'memb_dim'.} \item{ref}{A named numerical array of the reference forecast data with at -least time dimension. The dimensions must be the same as 'exp' except -'memb_dim'. If it is NULL, the climatological forecast is used as reference -forecast. The default value is NULL.} +least time and member dimension. The dimensions must be the same as 'exp' +except 'memb_dim' and 'dat_dim'. If the 'dat_dim' parameter is not NULL, the +corresponding dimension in 'ref' array must either be equal to the dataset +dimension of the 'exp' array or that 'ref' does not include the dimension of +'dat_dim'. If 'ref' is NULL, the climatological forecast is used as +reference forecast. The default value is NULL.} \item{time_dim}{A character string indicating the name of the time dimension. The default value is 'sdate'.} @@ -33,9 +37,13 @@ The default value is 'sdate'.} to compute the probabilities of the forecast and the reference forecast. The default value is 'member'.} +\item{dat_dim}{A character string indicating the name of dataset dimension. +The length of this dimension can be different between 'exp' and 'obs'. +The default value is NULL.} + \item{Fair}{A logical indicating whether to compute the FairCRPSS (the -potential CRPSS that the forecast would have with an infinite ensemble size). -The default value is FALSE.} +potential CRPSS that the forecast would have with an infinite ensemble +size). The default value is FALSE.} \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} @@ -51,17 +59,18 @@ computation. The default value is NULL.} } } \description{ -The Continuous Ranked Probability Skill Score (CRPSS; Wilks, 2011) is the skill score -based on the Continuous Ranked Probability Score (CRPS; Wilks, 2011). It can be used to -assess whether a forecast presents an improvement or worsening with respect to -a reference forecast. The CRPSS ranges between minus infinite and 1. If the -CRPSS is positive, it indicates that the forecast has higher skill than the -reference forecast, while a negative value means that it has a lower skill. -Examples of reference forecasts are the climatological forecast (same -probabilities for all categories for all time steps), persistence, a previous -model version, and another model. It is computed as CRPSS = 1 - CRPS_exp / CRPS_ref. -The statistical significance is obtained based on a Random Walk test at the -95% confidence level (DelSole and Tippett, 2016). +The Continuous Ranked Probability Skill Score (CRPSS; Wilks, 2011) is the +skill score based on the Continuous Ranked Probability Score (CRPS; Wilks, +2011). It can be used to assess whether a forecast presents an improvement or +worsening with respect to a reference forecast. The CRPSS ranges between minus +infinite and 1. If the CRPSS is positive, it indicates that the forecast has +higher skill than the reference forecast, while a negative value means that it +has a lower skill. Examples of reference forecasts are the climatological +forecast (same probabilities for all categories for all time steps), +persistence, a previous model version, and another model. It is computed as +CRPSS = 1 - CRPS_exp / CRPS_ref. The statistical significance is obtained +based on a Random Walk test at the 95% confidence level (DelSole and Tippett, +2016). } \examples{ exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) diff --git a/man/s2dv-package.Rd b/man/s2dv-package.Rd index 3c98a95..ffb6783 100644 --- a/man/s2dv-package.Rd +++ b/man/s2dv-package.Rd @@ -6,7 +6,15 @@ \alias{s2dv-package} \title{s2dv: A Set of Common Tools for Seasonal to Decadal Verification} \description{ -The advanced version of package 's2dverification'. It is intended for 'seasonal to decadal' (s2d) climate forecast verification, but it can also be used in other kinds of forecasts or general climate analysis. This package is specially designed for the comparison between the experimental and observational datasets. The functionality of the included functions covers from data retrieval, data post-processing, skill scores against observation, to visualization. Compared to 's2dverification', 's2dv' is more compatible with the package 'startR', able to use multiple cores for computation and handle multi-dimensional arrays with a higher flexibility. +The advanced version of package 's2dverification'. It is + intended for 'seasonal to decadal' (s2d) climate forecast verification, but + it can also be used in other kinds of forecasts or general climate analysis. + This package is specially designed for the comparison between the experimental + and observational datasets. The functionality of the included functions covers + from data retrieval, data post-processing, skill scores against observation, + to visualization. Compared to 's2dverification', 's2dv' is more compatible + with the package 'startR', able to use multiple cores for computation and + handle multi-dimensional arrays with a higher flexibility. } \references{ \url{https://earth.bsc.es/gitlab/es/s2dv/} diff --git a/man/sampleDepthData.Rd b/man/sampleDepthData.Rd index 47e2f1b..77e4a7a 100644 --- a/man/sampleDepthData.Rd +++ b/man/sampleDepthData.Rd @@ -5,8 +5,7 @@ \alias{sampleDepthData} \title{Sample of Experimental Data for Forecast Verification In Function Of Latitudes And Depths} -\format{ -The data set provides with a variable named 'sampleDepthData'.\cr\cr +\format{The data set provides with a variable named 'sampleDepthData'.\cr\cr sampleDepthData$exp is an array that contains the experimental data and the dimension meanings and values are:\cr @@ -19,8 +18,7 @@ but in this sample is not defined (NULL).\cr\cr sampleDepthData$depths is an array with the 7 longitudes covered by the data.\cr\cr -sampleDepthData$lat is an array with the 21 latitudes covered by the data.\cr\cr -} +sampleDepthData$lat is an array with the 21 latitudes covered by the data.\cr\cr} \usage{ data(sampleDepthData) } diff --git a/man/sampleMap.Rd b/man/sampleMap.Rd index e4ec5a5..eaf8aa5 100644 --- a/man/sampleMap.Rd +++ b/man/sampleMap.Rd @@ -4,8 +4,7 @@ \name{sampleMap} \alias{sampleMap} \title{Sample Of Observational And Experimental Data For Forecast Verification In Function Of Longitudes And Latitudes} -\format{ -The data set provides with a variable named 'sampleMap'.\cr\cr +\format{The data set provides with a variable named 'sampleMap'.\cr\cr sampleMap$mod is an array that contains the experimental data and the dimension meanings and values are:\cr c(# of experimental datasets, # of members, # of starting dates, # of lead-times, # of latitudes, # of longitudes)\cr @@ -17,8 +16,7 @@ sampleMap$obs is an array that contains the observational data and the dimension sampleMap$lat is an array with the 2 latitudes covered by the data (see examples on Load() for details on why such low resolution).\cr\cr - sampleMap$lon is an array with the 3 longitudes covered by the data (see examples on Load() for details on why such low resolution). -} + sampleMap$lon is an array with the 3 longitudes covered by the data (see examples on Load() for details on why such low resolution).} \usage{ data(sampleMap) } diff --git a/man/sampleTimeSeries.Rd b/man/sampleTimeSeries.Rd index 7f058e2..05a8e79 100644 --- a/man/sampleTimeSeries.Rd +++ b/man/sampleTimeSeries.Rd @@ -4,8 +4,7 @@ \name{sampleTimeSeries} \alias{sampleTimeSeries} \title{Sample Of Observational And Experimental Data For Forecast Verification As Area Averages} -\format{ -The data set provides with a variable named 'sampleTimeSeries'.\cr\cr +\format{The data set provides with a variable named 'sampleTimeSeries'.\cr\cr sampleTimeSeries$mod is an array that contains the experimental data and the dimension meanings and values are:\cr c(# of experimental datasets, # of members, # of starting dates, # of lead-times)\cr @@ -17,8 +16,7 @@ sampleTimeSeries$obs is an array that contains the observational data and the di sampleTimeSeries$lat is an array with the 2 latitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution).\cr\cr -sampleTimeSeries$lon is an array with the 3 longitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution). -} +sampleTimeSeries$lon is an array with the 3 longitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution).} \usage{ data(sampleTimeSeries) } -- GitLab From 38db5ce733d9aaab50b8489194e7df247d6c8197 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 17 Aug 2022 10:22:38 +0200 Subject: [PATCH 079/140] Correct documentation and minor bugs --- R/CRPSS.R | 17 ++++++++++------- man/CRPSS.Rd | 10 +++++++--- tests/testthat/test-CRPSS.R | 4 ++-- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/R/CRPSS.R b/R/CRPSS.R index 80a709c..a4b6998 100644 --- a/R/CRPSS.R +++ b/R/CRPSS.R @@ -16,7 +16,8 @@ #'@param exp A named numerical array of the forecast with at least time #' dimension. #'@param obs A named numerical array of the observation with at least time -#' dimension. The dimensions must be the same as 'exp' except 'memb_dim'. +#' dimension. The dimensions must be the same as 'exp' except 'memb_dim' +#' and 'dat_dim'. #'@param ref A named numerical array of the reference forecast data with at #' least time and member dimension. The dimensions must be the same as 'exp' #' except 'memb_dim' and 'dat_dim'. If the 'dat_dim' parameter is not NULL, the @@ -40,8 +41,11 @@ #' #'@return #'\item{$crpss}{ -#' A numerical array of the CRPSS with the same dimensions as "exp" except the -#' 'time_dim' and 'memb_dim' dimensions. +#' A numerical array of the CRPSS with dimensions c(nexp, nobs, the rest +#' dimensions of 'exp' except 'time_dim' and 'memb_dim' dimensions). nexp is +#' the number of experiment (i.e., dat_dim in exp), and nobs is the number of +#' observation (i.e., dat_dim in obs). If 'dat_dim' is NULL, nexp and nobs are +#' omitted. #'} #'\item{$sign}{ #' A logical array of the statistical significance of the CRPSS with the same @@ -116,7 +120,7 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } ## exp and obs (2) if (memb_dim %in% names(dim(obs))) { - if (identical(as.numeric(dim(obs)[memb_dim]),1)){ + if (identical(as.numeric(dim(obs)[memb_dim]), 1)) { obs <- ClimProjDiags::Subset(x = obs, along = memb_dim, indices = 1, drop = 'selected') } else {stop("Not implemented for observations with members ('obs' can have ", "'memb_dim', but it should be of length=1).")} @@ -160,8 +164,7 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } ## ncores if (!is.null(ncores)) { - if (!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 | - length(ncores) > 1) { + if (!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 | length(ncores) > 1) { stop("Parameter 'ncores' must be either NULL or a positive integer.") } } @@ -196,7 +199,7 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } .CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, - Fair = FALSE) { + Fair = FALSE) { # exp: [sdate, memb (dat_dim)] # obs: [sdate, (dat_dim)] diff --git a/man/CRPSS.Rd b/man/CRPSS.Rd index 5ab9726..5f3a0d4 100644 --- a/man/CRPSS.Rd +++ b/man/CRPSS.Rd @@ -20,7 +20,8 @@ CRPSS( dimension.} \item{obs}{A named numerical array of the observation with at least time -dimension. The dimensions must be the same as 'exp' except 'memb_dim'.} +dimension. The dimensions must be the same as 'exp' except 'memb_dim' +and 'dat_dim'.} \item{ref}{A named numerical array of the reference forecast data with at least time and member dimension. The dimensions must be the same as 'exp' @@ -50,8 +51,11 @@ computation. The default value is NULL.} } \value{ \item{$crpss}{ - A numerical array of the CRPSS with the same dimensions as "exp" except the - 'time_dim' and 'memb_dim' dimensions. + A numerical array of the CRPSS with dimensions c(nexp, nobs, the rest + dimensions of 'exp' except 'time_dim' and 'memb_dim' dimensions). nexp is + the number of experiment (i.e., dat_dim in exp), and nobs is the number of + observation (i.e., dat_dim in obs). If 'dat_dim' is NULL, nexp and nobs are + omitted. } \item{$sign}{ A logical array of the statistical significance of the CRPSS with the same diff --git a/tests/testthat/test-CRPSS.R b/tests/testthat/test-CRPSS.R index 7221e10..7e1b035 100644 --- a/tests/testthat/test-CRPSS.R +++ b/tests/testthat/test-CRPSS.R @@ -293,8 +293,8 @@ test_that("5. Output checks: dat4, dat2 and dat5", { c('nexp' = 3, 'nobs' = 2, 'lat' = 2) ) expect_equal( - CRPSS(exp2, obs2)$crpss, - CRPSS(exp3, obs3, dat_dim = 'dataset')$crpss[1] + as.vector(CRPSS(exp2, obs2)$crpss), + as.vector(CRPSS(exp3, obs3, dat_dim = 'dataset')$crpss[1]) ) }) -- GitLab From 4b2550b1d4119e3a3c93d84b144fcc9a70bda531 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 17 Aug 2022 10:35:58 +0200 Subject: [PATCH 080/140] added remove_dat_dim <- FALSE --- R/CRPSS.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/CRPSS.R b/R/CRPSS.R index a4b6998..4ee0c48 100644 --- a/R/CRPSS.R +++ b/R/CRPSS.R @@ -256,6 +256,8 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!is.null(dat_dim) && (!dat_dim %in% names(dim(ref)))) { remove_dat_dim <- TRUE ref <- InsertDim(data = ref, posdim = length(dim(ref)) + 1 , lendim = 1, name = dat_dim) + } else { + remove_dat_dim <- FALSE } crps_ref <- .CRPS(exp = ref, obs = obs, time_dim = time_dim, memb_dim = memb_dim, dat_dim = dat_dim, Fair = Fair) -- GitLab From 8732e63cbd1ccee7290814795e58a8dca0fe9b51 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 17 Aug 2022 12:24:00 +0200 Subject: [PATCH 081/140] Correct documentation and minor bugs of RPSS.R and test-RPSS.R --- R/RPSS.R | 77 ++++++++++++++++++-------------------- man/RPSS.Rd | 32 ++++++++++------ tests/testthat/test-RPSS.R | 26 ++++--------- 3 files changed, 65 insertions(+), 70 deletions(-) diff --git a/R/RPSS.R b/R/RPSS.R index fe4fc18..da4df3b 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -21,9 +21,11 @@ #' 'dat_dim'. #'@param ref A named numerical array of the reference forecast data with at #' least time and member dimension. The dimensions must be the same as 'exp' -#' except 'memb_dim' and 'dat_dim', 'dat_dim' can be either equal of 'exp' or -#' NULL. If 'ref' is NULL, the climatological forecast is used as reference -#' forecast. The default value is NULL. +#' except 'memb_dim' and 'dat_dim'. If the 'dat_dim' parameter is not NULL, the +#' corresponding dimension in 'ref' array must either be equal to the dataset +#' dimension of the 'exp' array or that 'ref' does not include the dimension of +#' 'dat_dim'. If 'ref' is NULL, the climatological forecast is used as +#' reference forecast. The default value is NULL. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. #'@param memb_dim A character string indicating the name of the member dimension @@ -45,12 +47,18 @@ #' 'weights_exp' and 'weights_ref' instead. #'@param weights_exp A named numerical array of the forecast #' ensemble weights for each dimension of 'exp' array. The dimension names -#' should include 'memb_dim', 'time_dim' and 'dat_dim' if this parameter is not -#' NULL. The default value is NULL. The ensemble should have at least 70 +#' should include 'memb_dim' and 'time_dim'. If 'dat_dim' parameter is not NULL +#' , 'weights_ref' must include the same 'dat_dim' dimension as 'exp' array. +#' The default value is NULL. The ensemble should have at least 70 #' members or span at least 10 time steps and have more than 45 members if #' consistency between the weighted and unweighted methodologies is desired. -#'@param weights_ref Same as 'weights_exp' but for the reference ensemble. All -#' dimensions mus be equal as 'ref' parameter dimension values and names. +#'@param weights_ref A named numerical array of the forecast ensemble weights +#' for each dimension of 'ref' array. The dimension names should include +#' 'memb_dim' and 'time_dim'. If 'ref' array has 'dat_dim' dimension , +#' 'weights_ref' must include also 'dat_dim' dimension. The default value is +#' NULL. The ensemble should have at least 70 members or span at least 10 time +#' steps and have more than 45 members if consistency between the weighted and +#' unweighted methodologies is desired. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -71,12 +79,14 @@ #'DelSole and Tippett, 2016; https://doi.org/10.1175/MWR-D-15-0218.1 #' #'@examples -#'exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) -#'obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) -#'ref <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +#'exp <- array(rnorm(3000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +#'obs <- array(rnorm(300), dim = c(lat = 3, lon = 2, sdate = 50)) +#'ref <- array(rnorm(3000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +#'weights_exp <- array(abs(rnorm(500)), dim = c(member = 10, sdate = 50)) +#'weights_ref <- array(abs(rnorm(500)), dim = c(member = 10, sdate = 50)) #'res <- RPSS(exp = exp, obs = obs) ## climatology as reference forecast #'res <- RPSS(exp = exp, obs = obs, ref = ref) ## ref as reference forecast -#' +#'res <- RPSS(exp = exp, obs = obs, ref = ref, weights_exp = weights_exp, weights_ref = weights_ref) ## using weights #'@import multiApply #'@export RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', @@ -154,7 +164,8 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!is.null(dat_dim)) { if (dat_dim %in% name_ref) { if (!identical(dim(exp)[dat_dim], dim(ref)[dat_dim])) { - stop(paste0("Parameter 'ref' must have same 'dat_dim' dimension as 'exp' or NULL.")) + stop(paste0("If parameter 'ref' has 'dat_dim' dimension it must be", + " equal to 'dat_dim' dimension of 'exp'.")) } name_ref <- name_ref[-which(name_ref == dat_dim)] } @@ -162,7 +173,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!identical(length(name_exp), length(name_ref)) | !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { stop(paste0("Parameter 'exp' and 'ref' must have same length of ", - "all dimensions except 'memb_dim' and 'dat_dim'.")) + "all dimensions except 'memb_dim'. 'dat_dim' can be NULL in 'ref'.")) } } ## prob_thresholds @@ -267,17 +278,9 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!is.null(ref)) { # use "ref" as reference forecast if (!is.null(dat_dim) && (dat_dim %in% names(dim(ref)))) { - if (!memb_dim %in% names(dim(ref))) { - target_dims_ref <- c(time_dim, dat_dim) - } else { - target_dims_ref <- c(time_dim, memb_dim, dat_dim) - } + target_dims_ref <- c(time_dim, memb_dim, dat_dim) } else { - if (!memb_dim %in% names(dim(ref))) { - target_dims_ref <- c(time_dim) - } else { target_dims_ref <- c(time_dim, memb_dim) - } } data <- list(exp = exp, obs = obs, ref = ref) target_dims = list(exp = c(time_dim, memb_dim, dat_dim), @@ -364,27 +367,21 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } } else { # use "ref" as reference forecast - ref_data <- ref - if (!memb_dim %in% names(dim(ref))) { - ref_data <- InsertDim(ref_data, posdim = 2, lendim = 1, name = memb_dim) - if (!is.null(weights_ref)) { - weights_ref <- InsertDim(weights_ref, posdim = 2, lendim = 1, name = memb_dim) - } - } - if (!is.null(dat_dim)) { - if (!dat_dim %in% names(dim(ref_data))) { - ref_data <- InsertDim(ref_data, posdim = 3, lendim = 1, name = dat_dim) - if (!is.null(weights_ref)) { - weights_ref <- InsertDim(weights_ref, posdim = 3, lendim = 1, name = dat_dim) - } - } + if (!is.null(dat_dim) && (!dat_dim %in% names(dim(ref)))) { + remove_dat_dim <- TRUE + ref <- InsertDim(ref, posdim = 3, lendim = 1, name = dat_dim) + if (!is.null(weights_ref)) { + weights_ref <- InsertDim(weights_ref, posdim = 3, lendim = 1, name = dat_dim) + } + } else { + remove_dat_dim <- FALSE } - rps_ref <- .RPS(exp = ref_data, obs = obs, time_dim = time_dim, memb_dim = memb_dim, dat_dim = dat_dim, + rps_ref <- .RPS(exp = ref, obs = obs, time_dim = time_dim, memb_dim = memb_dim, dat_dim = dat_dim, prob_thresholds = prob_thresholds, indices_for_clim = indices_for_clim, Fair = Fair, weights = weights_ref) if (!is.null(dat_dim)) { - if (!dat_dim %in% names(dim(ref))) { + if (isTRUE(remove_dat_dim)) { dim(rps_ref) <- dim(rps_ref)[-2] } } @@ -400,14 +397,14 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (length(dim(rps_ref_mean)) == 1) { for (i in 1:nexp) { for (j in 1:nobs) { - rpss[i, j] <- 1- rps_exp_mean[i, j] / rps_ref_mean[j] + rpss[i, j] <- 1 - rps_exp_mean[i, j] / rps_ref_mean[j] sign[i, j] <- .RandomWalkTest(skill_A = rps_exp_mean[i, j], skill_B = rps_ref_mean[j])$signif } } } else { for (i in 1:nexp) { for (j in 1:nobs) { - rpss[i, j] <- 1- rps_exp_mean[i, j] / rps_ref_mean[i, j] + rpss[i, j] <- 1 - rps_exp_mean[i, j] / rps_ref_mean[i, j] sign[i, j] <- .RandomWalkTest(skill_A = rps_exp_mean[i, j], skill_B = rps_ref_mean[i, j])$signif } } diff --git a/man/RPSS.Rd b/man/RPSS.Rd index 63b2091..95ccecb 100644 --- a/man/RPSS.Rd +++ b/man/RPSS.Rd @@ -30,9 +30,11 @@ dimension. The dimensions must be the same as 'exp' except 'memb_dim' and \item{ref}{A named numerical array of the reference forecast data with at least time and member dimension. The dimensions must be the same as 'exp' -except 'memb_dim' and 'dat_dim', 'dat_dim' can be either equal of 'exp' or -NULL. If 'ref' is NULL, the climatological forecast is used as reference -forecast. The default value is NULL.} +except 'memb_dim' and 'dat_dim'. If the 'dat_dim' parameter is not NULL, the +corresponding dimension in 'ref' array must either be equal to the dataset +dimension of the 'exp' array or that 'ref' does not include the dimension of +'dat_dim'. If 'ref' is NULL, the climatological forecast is used as +reference forecast. The default value is NULL.} \item{time_dim}{A character string indicating the name of the time dimension. The default value is 'sdate'.} @@ -62,13 +64,19 @@ The default value is FALSE.} \item{weights_exp}{A named numerical array of the forecast ensemble weights for each dimension of 'exp' array. The dimension names -should include 'memb_dim', 'time_dim' and 'dat_dim' if this parameter is not -NULL. The default value is NULL. The ensemble should have at least 70 +should include 'memb_dim' and 'time_dim'. If 'dat_dim' parameter is not NULL +, 'weights_ref' must include the same 'dat_dim' dimension as 'exp' array. +The default value is NULL. The ensemble should have at least 70 members or span at least 10 time steps and have more than 45 members if consistency between the weighted and unweighted methodologies is desired.} -\item{weights_ref}{Same as 'weights_exp' but for the reference ensemble. All -dimensions mus be equal as 'ref' parameter dimension values and names.} +\item{weights_ref}{A named numerical array of the forecast ensemble weights +for each dimension of 'ref' array. The dimension names should include +'memb_dim' and 'time_dim'. If 'ref' array has 'dat_dim' dimension , +'weights_ref' must include also 'dat_dim' dimension. The default value is +NULL. The ensemble should have at least 70 members or span at least 10 time +steps and have more than 45 members if consistency between the weighted and +unweighted methodologies is desired.} \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} @@ -101,12 +109,14 @@ based on a Random Walk test at the 95% confidence level (DelSole and Tippett, of exp and obs data. } \examples{ -exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) -obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) -ref <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +exp <- array(rnorm(3000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +obs <- array(rnorm(300), dim = c(lat = 3, lon = 2, sdate = 50)) +ref <- array(rnorm(3000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +weights_exp <- array(abs(rnorm(500)), dim = c(member = 10, sdate = 50)) +weights_ref <- array(abs(rnorm(500)), dim = c(member = 10, sdate = 50)) res <- RPSS(exp = exp, obs = obs) ## climatology as reference forecast res <- RPSS(exp = exp, obs = obs, ref = ref) ## ref as reference forecast - +res <- RPSS(exp = exp, obs = obs, ref = ref, weights_exp = weights_exp, weights_ref = weights_ref) ## using weights } \references{ Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index bfaba92..494fa25 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -29,8 +29,6 @@ set.seed(1) exp3 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)) set.seed(2) obs3 <- array(rnorm(20), dim = c(member = 1, sdate = 10, dataset = 2)) -set.seed(3) -ref3 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)) set.seed(4) weights3 <- array(abs(rnorm(60)), dim = c(member = 2, sdate = 10, dataset = 3)) @@ -46,16 +44,6 @@ weights_exp4 <- array(abs(rnorm(60)), dim = c(member = 2, sdate = 10, dataset = set.seed(5) weights_ref4 <- array(abs(rnorm(20)), dim = c(member = 2, sdate = 10)) -# dat5 -set.seed(1) -exp5 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)) -set.seed(2) -obs5 <- array(rnorm(20), dim = c(member = 1, sdate = 10, dataset = 2)) -set.seed(3) -ref5 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 2)) -set.seed(4) -weights5 <- array(abs(rnorm(60)), dim = c(member = 2, sdate = 10, dataset = 3)) - ############################################## test_that("1. Input checks", { # exp and obs (1) @@ -101,11 +89,11 @@ test_that("1. Input checks", { ) expect_error( RPSS(exp1, obs1, ref2), - "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." + "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim'. 'dat_dim' can be NULL in 'ref'." ) - expect_error( + expect_error( RPSS(exp3, obs3, array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 4)), dat_dim = 'dataset'), - "Parameter 'ref' must have same 'dat_dim' dimension as 'exp' or NULL." + "If parameter 'ref' has 'dat_dim' dimension it must be equal to 'dat_dim' dimension of 'exp'." ) # prob_thresholds expect_error( @@ -144,8 +132,8 @@ test_that("1. Input checks", { "Parameter 'weights_exp' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'." ) expect_error( - RPSS(exp3, obs3, ref3, dat_dim = 'dataset', weights_ref = weights1), - "Parameter 'weights_ref' must have three dimensions with the names of 'memb_dim', 'time_dim' and 'dat_dim'." + RPSS(exp4, obs4, ref4, dat_dim = 'dataset', weights_ref = weights1), + "Parameter 'weights_ref' must have the same dimension lengths as 'memb_dim' and 'time_dim' in 'ref'." ) # ncores expect_error( @@ -363,7 +351,7 @@ test_that("4. Output checks: dat3", { }) ############################################## -test_that("5. Output checks: dat4, dat2 and dat5", { +test_that("5. Output checks: dat2, dat3 and dat4", { expect_equal( dim(RPSS(exp4, obs4, ref4, dat_dim = 'dataset')$rpss), @@ -380,7 +368,7 @@ test_that("5. Output checks: dat4, dat2 and dat5", { ) expect_equal( RPSS(exp2, obs2, weights_exp = weights2)$rpss, - RPSS(exp5, obs5, dat_dim = 'dataset', weights_exp = weights5)$rpss[1] + RPSS(exp4, obs4, dat_dim = 'dataset', weights_exp = weights_exp4)$rpss[1] ) }) -- GitLab From 09325779a1b338b007265bfa7dbdd7936a2f8371 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 17 Aug 2022 12:41:27 +0200 Subject: [PATCH 082/140] Correct output text in CRPSS and in test file --- R/CRPS.R | 4 ++-- R/CRPSS.R | 2 +- tests/testthat/test-CRPSS.R | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/CRPS.R b/R/CRPS.R index b2f38a5..7dedf4f 100644 --- a/R/CRPS.R +++ b/R/CRPS.R @@ -153,13 +153,13 @@ CRPS <- function(exp, obs, time_dim = 'sdate', memb_dim = 'member', dat_dim = NU for (i in 1:nexp) { for (j in 1:nobs) { exp_data <- exp[ , , i] - obs_data <- obs[, j] + obs_data <- obs[ , j] if (is.null(dim(exp_data))) dim(exp_data) <- c(dim(exp)[1:2]) if (is.null(dim(obs_data))) dim(obs_data) <- c(dim(obs)[1]) crps <- SpecsVerification::enscrps_cpp(ens = exp_data, obs = obs_data, R_new = R_new) - CRPS[, i, j] <- crps + CRPS[ , i, j] <- crps } } diff --git a/R/CRPSS.R b/R/CRPSS.R index 4ee0c48..2097b8e 100644 --- a/R/CRPSS.R +++ b/R/CRPSS.R @@ -155,7 +155,7 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!identical(length(name_exp), length(name_ref)) | !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { stop(paste0("Parameter 'exp' and 'ref' must have same length of ", - "all dimensions except 'memb_dim' and 'dat_dim'.")) + "all dimensions except 'memb_dim'. 'dat_dim' can be NULL in 'ref'.")) } } ## Fair diff --git a/tests/testthat/test-CRPSS.R b/tests/testthat/test-CRPSS.R index 7e1b035..c1a34fb 100644 --- a/tests/testthat/test-CRPSS.R +++ b/tests/testthat/test-CRPSS.R @@ -97,7 +97,7 @@ test_that("1. Input checks", { ) expect_error( CRPSS(exp1, obs1, ref2), - "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." + "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim'. 'dat_dim' can be NULL in 'ref'" ) expect_error( CRPSS(exp3, array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)), ref3, dat_dim = 'dataset'), -- GitLab From cd12c15fc4a9a4808c92a41045ec2275599ba1f0 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 18 Aug 2022 10:48:12 +0200 Subject: [PATCH 083/140] Improve documentation and test file CRPSS --- R/CRPSS.R | 23 +++++++++++++---------- man/CRPSS.Rd | 10 +++++----- tests/testthat/test-CRPSS.R | 8 +------- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/R/CRPSS.R b/R/CRPSS.R index 2097b8e..c77ba3d 100644 --- a/R/CRPSS.R +++ b/R/CRPSS.R @@ -20,11 +20,11 @@ #' and 'dat_dim'. #'@param ref A named numerical array of the reference forecast data with at #' least time and member dimension. The dimensions must be the same as 'exp' -#' except 'memb_dim' and 'dat_dim'. If the 'dat_dim' parameter is not NULL, the -#' corresponding dimension in 'ref' array must either be equal to the dataset -#' dimension of the 'exp' array or that 'ref' does not include the dimension of -#' 'dat_dim'. If 'ref' is NULL, the climatological forecast is used as -#' reference forecast. The default value is NULL. +#' except 'memb_dim' and 'dat_dim' which is NULL if there is only one +#' reference dataset. If there are multiple reference datasets 'dat_dim' +#' dimension must have the same length as in 'exp'. If 'ref' is NULL, the +#' climatological forecast is used as reference forecast. The default value +#' is NULL. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. #'@param memb_dim A character string indicating the name of the member dimension @@ -122,8 +122,10 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (memb_dim %in% names(dim(obs))) { if (identical(as.numeric(dim(obs)[memb_dim]), 1)) { obs <- ClimProjDiags::Subset(x = obs, along = memb_dim, indices = 1, drop = 'selected') - } else {stop("Not implemented for observations with members ('obs' can have ", - "'memb_dim', but it should be of length=1).")} + } else { + stop("Not implemented for observations with members ('obs' can have ", + "'memb_dim', but it should be of length=1).") + } } name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) @@ -138,7 +140,7 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of all dimensions", - " except 'memb_dim' and 'dat_dim'.")) + " except 'memb_dim' and 'dat_dim'.")) } if (!is.null(ref)) { name_ref <- sort(names(dim(ref))) @@ -147,7 +149,7 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (dat_dim %in% name_ref) { if (!identical(dim(exp)[dat_dim], dim(ref)[dat_dim])) { stop(paste0("If parameter 'ref' has 'dat_dim' dimension it must be", - " equal to 'dat_dim' dimension of 'exp'.")) + " equal to 'dat_dim' dimension of 'exp'.")) } name_ref <- name_ref[-which(name_ref == dat_dim)] } @@ -155,7 +157,8 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!identical(length(name_exp), length(name_ref)) | !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { stop(paste0("Parameter 'exp' and 'ref' must have same length of ", - "all dimensions except 'memb_dim'. 'dat_dim' can be NULL in 'ref'.")) + "all dimensions except 'memb_dim' and 'dat_dim' if there is ", + "only one reference dataset.")) } } ## Fair diff --git a/man/CRPSS.Rd b/man/CRPSS.Rd index 5f3a0d4..903a7f5 100644 --- a/man/CRPSS.Rd +++ b/man/CRPSS.Rd @@ -25,11 +25,11 @@ and 'dat_dim'.} \item{ref}{A named numerical array of the reference forecast data with at least time and member dimension. The dimensions must be the same as 'exp' -except 'memb_dim' and 'dat_dim'. If the 'dat_dim' parameter is not NULL, the -corresponding dimension in 'ref' array must either be equal to the dataset -dimension of the 'exp' array or that 'ref' does not include the dimension of -'dat_dim'. If 'ref' is NULL, the climatological forecast is used as -reference forecast. The default value is NULL.} +except 'memb_dim' and 'dat_dim' which is NULL if there is only one +reference dataset. If there are multiple reference datasets 'dat_dim' +dimension must have the same length as in 'exp'. If 'ref' is NULL, the +climatological forecast is used as reference forecast. The default value +is NULL.} \item{time_dim}{A character string indicating the name of the time dimension. The default value is 'sdate'.} diff --git a/tests/testthat/test-CRPSS.R b/tests/testthat/test-CRPSS.R index c1a34fb..68b763a 100644 --- a/tests/testthat/test-CRPSS.R +++ b/tests/testthat/test-CRPSS.R @@ -42,12 +42,6 @@ obs4 <- array(rnorm(20), dim = c(member = 1, sdate = 10, dataset = 2, lat = 2)) set.seed(3) ref4 <- array(rnorm(60), dim = c(member = 2, sdate = 10, lat = 2)) -# dat5 -set.seed(1) -exp5 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)) -set.seed(2) -obs5 <- array(rnorm(20), dim = c(member = 1, sdate = 10, dataset = 2)) - ############################################## test_that("1. Input checks", { # exp and obs (1) @@ -97,7 +91,7 @@ test_that("1. Input checks", { ) expect_error( CRPSS(exp1, obs1, ref2), - "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim'. 'dat_dim' can be NULL in 'ref'" + "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim' and 'dat_dim' if there is only one reference dataset." ) expect_error( CRPSS(exp3, array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)), ref3, dat_dim = 'dataset'), -- GitLab From 6ed71a35a8736bb8a9306ceaac2a6199da16987a Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 18 Aug 2022 13:04:00 +0200 Subject: [PATCH 084/140] Correct documentation, examples and test file of RPSS --- R/RPSS.R | 52 ++++++++++++++++++++------------------ tests/testthat/test-RPSS.R | 21 +++++++++++---- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/R/RPSS.R b/R/RPSS.R index da4df3b..08feceb 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -21,11 +21,11 @@ #' 'dat_dim'. #'@param ref A named numerical array of the reference forecast data with at #' least time and member dimension. The dimensions must be the same as 'exp' -#' except 'memb_dim' and 'dat_dim'. If the 'dat_dim' parameter is not NULL, the -#' corresponding dimension in 'ref' array must either be equal to the dataset -#' dimension of the 'exp' array or that 'ref' does not include the dimension of -#' 'dat_dim'. If 'ref' is NULL, the climatological forecast is used as -#' reference forecast. The default value is NULL. +#' except 'memb_dim' and 'dat_dim' which is NULL if there is only one +#' reference dataset. If there are multiple reference datasets 'dat_dim' +#' dimension must have the same length as in 'exp'. If 'ref' is NULL, the +#' climatological forecast is used as reference forecast. The default value +#' is NULL. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. #'@param memb_dim A character string indicating the name of the member dimension @@ -45,20 +45,15 @@ #' The default value is FALSE. #'@param weights Deprecated and will be removed in the next release. Please use #' 'weights_exp' and 'weights_ref' instead. -#'@param weights_exp A named numerical array of the forecast -#' ensemble weights for each dimension of 'exp' array. The dimension names -#' should include 'memb_dim' and 'time_dim'. If 'dat_dim' parameter is not NULL -#' , 'weights_ref' must include the same 'dat_dim' dimension as 'exp' array. -#' The default value is NULL. The ensemble should have at least 70 -#' members or span at least 10 time steps and have more than 45 members if -#' consistency between the weighted and unweighted methodologies is desired. -#'@param weights_ref A named numerical array of the forecast ensemble weights -#' for each dimension of 'ref' array. The dimension names should include -#' 'memb_dim' and 'time_dim'. If 'ref' array has 'dat_dim' dimension , -#' 'weights_ref' must include also 'dat_dim' dimension. The default value is -#' NULL. The ensemble should have at least 70 members or span at least 10 time -#' steps and have more than 45 members if consistency between the weighted and -#' unweighted methodologies is desired. +#'@param weights_exp A named numerical array of the forecast ensemble weights +#' for each 'time_dim', 'memb_dim' and 'dat_dim' dimension of 'exp' array. The +#' dimension names should include 'memb_dim', 'time_dim' and 'dat_dim' if there +#' are multiple datasets. All dimension lengths must be equal to 'exp' +#' dimensions lengths. The default value is NULL. The ensemble should have at +#' least 70 members or span at least 10 time steps and have more than 45 +#' members if consistency between the weighted and unweighted methodologies is +#' desired. +#'@param weights_ref Same as 'weights_exp' but for the reference ensemble. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -79,14 +74,22 @@ #'DelSole and Tippett, 2016; https://doi.org/10.1175/MWR-D-15-0218.1 #' #'@examples +#'set.seed(1) #'exp <- array(rnorm(3000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +#'set.seed(2) #'obs <- array(rnorm(300), dim = c(lat = 3, lon = 2, sdate = 50)) +#'set.seed(3) #'ref <- array(rnorm(3000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) -#'weights_exp <- array(abs(rnorm(500)), dim = c(member = 10, sdate = 50)) -#'weights_ref <- array(abs(rnorm(500)), dim = c(member = 10, sdate = 50)) +#'set.seed(4) +#'weights_unit <- c(0.5, 0.25, 0.1, 0.05, 0.05, 0.02, 0.01, 0.01, 0.005, 0.05) +#'weights <- sapply(1:length(weights_unit), function(i) { +#' n <- abs(rnorm(50)) +#' n/sum(n)*weights_unit[i] +#' }) +#'dim(weights) <- c('sdate' = 50, 'member' = 10) #'res <- RPSS(exp = exp, obs = obs) ## climatology as reference forecast #'res <- RPSS(exp = exp, obs = obs, ref = ref) ## ref as reference forecast -#'res <- RPSS(exp = exp, obs = obs, ref = ref, weights_exp = weights_exp, weights_ref = weights_ref) ## using weights +#'res <- RPSS(exp = exp, obs = obs, ref = ref, weights_exp = weights, weights_ref = weights) ## using #'@import multiApply #'@export RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', @@ -165,7 +168,7 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (dat_dim %in% name_ref) { if (!identical(dim(exp)[dat_dim], dim(ref)[dat_dim])) { stop(paste0("If parameter 'ref' has 'dat_dim' dimension it must be", - " equal to 'dat_dim' dimension of 'exp'.")) + " equal to 'dat_dim' dimension of 'exp'.")) } name_ref <- name_ref[-which(name_ref == dat_dim)] } @@ -173,7 +176,8 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!identical(length(name_exp), length(name_ref)) | !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { stop(paste0("Parameter 'exp' and 'ref' must have same length of ", - "all dimensions except 'memb_dim'. 'dat_dim' can be NULL in 'ref'.")) + "all dimensions except 'memb_dim' and 'dat_dim' if there is ", + "only one reference dataset.")) } } ## prob_thresholds diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index 494fa25..801cdf8 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -29,6 +29,8 @@ set.seed(1) exp3 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)) set.seed(2) obs3 <- array(rnorm(20), dim = c(member = 1, sdate = 10, dataset = 2)) +set.seed(3) +ref3 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)) set.seed(4) weights3 <- array(abs(rnorm(60)), dim = c(member = 2, sdate = 10, dataset = 3)) @@ -38,7 +40,7 @@ exp4 <- array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3, lat = 2)) set.seed(2) obs4 <- array(rnorm(20), dim = c(member = 1, sdate = 10, dataset = 2, lat = 2)) set.seed(3) -ref4 <- array(rnorm(60), dim = c(member = 2, sdate = 10, lat = 2)) +ref4 <- array(rnorm(40), dim = c(member = 2, sdate = 10, lat = 2)) set.seed(4) weights_exp4 <- array(abs(rnorm(60)), dim = c(member = 2, sdate = 10, dataset = 3)) set.seed(5) @@ -87,14 +89,14 @@ test_that("1. Input checks", { RPSS(exp1, array(1:9, dim = c(sdate = 9))), "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." ) - expect_error( - RPSS(exp1, obs1, ref2), - "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim'. 'dat_dim' can be NULL in 'ref'." - ) expect_error( RPSS(exp3, obs3, array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 4)), dat_dim = 'dataset'), "If parameter 'ref' has 'dat_dim' dimension it must be equal to 'dat_dim' dimension of 'exp'." ) + expect_error( + RPSS(exp1, obs1, ref2), + "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim' and 'dat_dim' if there is only one reference dataset." + ) # prob_thresholds expect_error( RPSS(exp1, obs1, ref1, prob_thresholds = 1), @@ -348,6 +350,15 @@ test_that("4. Output checks: dat3", { c(-0.7015067), tolerance = 0.0001 ) + expect_equal( + mean(RPSS(exp3, obs3, ref3, dat_dim = 'dataset', weights_ref = weights3, Fair = T)$rpss), + c(0.06145283), + tolerance = 0.0001 + ) + expect_equal( + RPSS(exp3, obs3, ref3, dat_dim = 'dataset', weights_ref = weights3, Fair = T)$rpss[1], + RPSS(exp3, obs3, ref2, dat_dim = 'dataset', weights_ref = weights2, Fair = T)$rpss[1] + ) }) ############################################## -- GitLab From d3ea2bc6914a5e4d6c10f2ba4cfdf8ccbc5cd3cd Mon Sep 17 00:00:00 2001 From: aho Date: Thu, 18 Aug 2022 15:29:05 +0200 Subject: [PATCH 085/140] Improve examples, refine phrasing and add more unit tests --- R/RPSS.R | 43 +++++++++++++++++-------------------- man/RPSS.Rd | 44 +++++++++++++++++++------------------- tests/testthat/test-RPSS.R | 37 ++++++++++++++++++++++++-------- 3 files changed, 70 insertions(+), 54 deletions(-) diff --git a/R/RPSS.R b/R/RPSS.R index 08feceb..b5ff907 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -21,11 +21,11 @@ #' 'dat_dim'. #'@param ref A named numerical array of the reference forecast data with at #' least time and member dimension. The dimensions must be the same as 'exp' -#' except 'memb_dim' and 'dat_dim' which is NULL if there is only one -#' reference dataset. If there are multiple reference datasets 'dat_dim' -#' dimension must have the same length as in 'exp'. If 'ref' is NULL, the -#' climatological forecast is used as reference forecast. The default value -#' is NULL. +#' except 'memb_dim' and 'dat_dim'. If there is only one reference dataset, +#' it should not have dataset dimension. If there is corresponding reference +#' for each experiement, the dataset dimension must have the same length as in +#' 'exp'. If 'ref' is NULL, the climatological forecast is used as reference +#' forecast. The default value is NULL. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. #'@param memb_dim A character string indicating the name of the member dimension @@ -45,15 +45,14 @@ #' The default value is FALSE. #'@param weights Deprecated and will be removed in the next release. Please use #' 'weights_exp' and 'weights_ref' instead. -#'@param weights_exp A named numerical array of the forecast ensemble weights -#' for each 'time_dim', 'memb_dim' and 'dat_dim' dimension of 'exp' array. The -#' dimension names should include 'memb_dim', 'time_dim' and 'dat_dim' if there +#'@param weights_exp A named numerical array of the forecast ensemble weights. +#' The dimension should include 'memb_dim', 'time_dim' and 'dat_dim' if there #' are multiple datasets. All dimension lengths must be equal to 'exp' -#' dimensions lengths. The default value is NULL. The ensemble should have at -#' least 70 members or span at least 10 time steps and have more than 45 -#' members if consistency between the weighted and unweighted methodologies is -#' desired. -#'@param weights_ref Same as 'weights_exp' but for the reference ensemble. +#' dimension lengths. The default value is NULL, which means no weighting is +#' applied. The ensemble should have at least 70 members or span at least 10 +#' time steps and have more than 45 members if consistency between the weighted +#' and unweighted methodologies is desired. +#'@param weights_ref Same as 'weights_exp' but for the reference forecast. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -80,16 +79,14 @@ #'obs <- array(rnorm(300), dim = c(lat = 3, lon = 2, sdate = 50)) #'set.seed(3) #'ref <- array(rnorm(3000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) -#'set.seed(4) -#'weights_unit <- c(0.5, 0.25, 0.1, 0.05, 0.05, 0.02, 0.01, 0.01, 0.005, 0.05) -#'weights <- sapply(1:length(weights_unit), function(i) { -#' n <- abs(rnorm(50)) -#' n/sum(n)*weights_unit[i] +#'weights <- sapply(1:dim(exp)['sdate'], function(i) { +#' n <- abs(rnorm(10)) +#' n/sum(n) #' }) -#'dim(weights) <- c('sdate' = 50, 'member' = 10) +#'dim(weights) <- c(member = 10, sdate = 50) #'res <- RPSS(exp = exp, obs = obs) ## climatology as reference forecast #'res <- RPSS(exp = exp, obs = obs, ref = ref) ## ref as reference forecast -#'res <- RPSS(exp = exp, obs = obs, ref = ref, weights_exp = weights, weights_ref = weights) ## using +#'res <- RPSS(exp = exp, obs = obs, ref = ref, weights_exp = weights, weights_ref = weights) #'@import multiApply #'@export RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', @@ -167,15 +164,15 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!is.null(dat_dim)) { if (dat_dim %in% name_ref) { if (!identical(dim(exp)[dat_dim], dim(ref)[dat_dim])) { - stop(paste0("If parameter 'ref' has 'dat_dim' dimension it must be", - " equal to 'dat_dim' dimension of 'exp'.")) + stop(paste0("If parameter 'ref' has dataset dimension, it must be", + " equal to dataset dimension of 'exp'.")) } name_ref <- name_ref[-which(name_ref == dat_dim)] } } if (!identical(length(name_exp), length(name_ref)) | !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { - stop(paste0("Parameter 'exp' and 'ref' must have same length of ", + stop(paste0("Parameter 'exp' and 'ref' must have the same length of ", "all dimensions except 'memb_dim' and 'dat_dim' if there is ", "only one reference dataset.")) } diff --git a/man/RPSS.Rd b/man/RPSS.Rd index 95ccecb..a68f21c 100644 --- a/man/RPSS.Rd +++ b/man/RPSS.Rd @@ -30,11 +30,11 @@ dimension. The dimensions must be the same as 'exp' except 'memb_dim' and \item{ref}{A named numerical array of the reference forecast data with at least time and member dimension. The dimensions must be the same as 'exp' -except 'memb_dim' and 'dat_dim'. If the 'dat_dim' parameter is not NULL, the -corresponding dimension in 'ref' array must either be equal to the dataset -dimension of the 'exp' array or that 'ref' does not include the dimension of -'dat_dim'. If 'ref' is NULL, the climatological forecast is used as -reference forecast. The default value is NULL.} +except 'memb_dim' and 'dat_dim'. If there is only one reference dataset, +it should not have dataset dimension. If there is corresponding reference +for each experiement, the dataset dimension must have the same length as in +'exp'. If 'ref' is NULL, the climatological forecast is used as reference +forecast. The default value is NULL.} \item{time_dim}{A character string indicating the name of the time dimension. The default value is 'sdate'.} @@ -62,21 +62,15 @@ The default value is FALSE.} \item{weights}{Deprecated and will be removed in the next release. Please use 'weights_exp' and 'weights_ref' instead.} -\item{weights_exp}{A named numerical array of the forecast -ensemble weights for each dimension of 'exp' array. The dimension names -should include 'memb_dim' and 'time_dim'. If 'dat_dim' parameter is not NULL -, 'weights_ref' must include the same 'dat_dim' dimension as 'exp' array. -The default value is NULL. The ensemble should have at least 70 -members or span at least 10 time steps and have more than 45 members if -consistency between the weighted and unweighted methodologies is desired.} +\item{weights_exp}{A named numerical array of the forecast ensemble weights. +The dimension should include 'memb_dim', 'time_dim' and 'dat_dim' if there +are multiple datasets. All dimension lengths must be equal to 'exp' +dimension lengths. The default value is NULL, which means no weighting is +applied. The ensemble should have at least 70 members or span at least 10 +time steps and have more than 45 members if consistency between the weighted + and unweighted methodologies is desired.} -\item{weights_ref}{A named numerical array of the forecast ensemble weights -for each dimension of 'ref' array. The dimension names should include -'memb_dim' and 'time_dim'. If 'ref' array has 'dat_dim' dimension , -'weights_ref' must include also 'dat_dim' dimension. The default value is -NULL. The ensemble should have at least 70 members or span at least 10 time -steps and have more than 45 members if consistency between the weighted and -unweighted methodologies is desired.} +\item{weights_ref}{Same as 'weights_exp' but for the reference forecast.} \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} @@ -109,14 +103,20 @@ based on a Random Walk test at the 95% confidence level (DelSole and Tippett, of exp and obs data. } \examples{ +set.seed(1) exp <- array(rnorm(3000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) +set.seed(2) obs <- array(rnorm(300), dim = c(lat = 3, lon = 2, sdate = 50)) +set.seed(3) ref <- array(rnorm(3000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) -weights_exp <- array(abs(rnorm(500)), dim = c(member = 10, sdate = 50)) -weights_ref <- array(abs(rnorm(500)), dim = c(member = 10, sdate = 50)) +weights <- sapply(1:dim(exp)['sdate'], function(i) { + n <- abs(rnorm(10)) + n/sum(n) + }) +dim(weights) <- c(member = 10, sdate = 50) res <- RPSS(exp = exp, obs = obs) ## climatology as reference forecast res <- RPSS(exp = exp, obs = obs, ref = ref) ## ref as reference forecast -res <- RPSS(exp = exp, obs = obs, ref = ref, weights_exp = weights_exp, weights_ref = weights_ref) ## using weights +res <- RPSS(exp = exp, obs = obs, ref = ref, weights_exp = weights, weights_ref = weights) } \references{ Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 diff --git a/tests/testthat/test-RPSS.R b/tests/testthat/test-RPSS.R index 801cdf8..7976905 100644 --- a/tests/testthat/test-RPSS.R +++ b/tests/testthat/test-RPSS.R @@ -91,11 +91,11 @@ test_that("1. Input checks", { ) expect_error( RPSS(exp3, obs3, array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 4)), dat_dim = 'dataset'), - "If parameter 'ref' has 'dat_dim' dimension it must be equal to 'dat_dim' dimension of 'exp'." + "If parameter 'ref' has dataset dimension, it must be equal to dataset dimension of 'exp'." ) expect_error( RPSS(exp1, obs1, ref2), - "Parameter 'exp' and 'ref' must have same length of all dimensions except 'memb_dim' and 'dat_dim' if there is only one reference dataset." + "Parameter 'exp' and 'ref' must have the same length of all dimensions except 'memb_dim' and 'dat_dim' if there is only one reference dataset." ) # prob_thresholds expect_error( @@ -350,19 +350,33 @@ test_that("4. Output checks: dat3", { c(-0.7015067), tolerance = 0.0001 ) + expect_equal( + as.vector(RPSS(exp3, obs3, dat_dim = 'dataset', weights_exp = weights3, Fair = T)$rpss), + c(-0.7962495, -0.4511230, -0.8497759, -0.5538201, -1.1312592, -0.4268125), + tolerance = 0.0001 + ) expect_equal( mean(RPSS(exp3, obs3, ref3, dat_dim = 'dataset', weights_ref = weights3, Fair = T)$rpss), c(0.06145283), tolerance = 0.0001 ) expect_equal( - RPSS(exp3, obs3, ref3, dat_dim = 'dataset', weights_ref = weights3, Fair = T)$rpss[1], - RPSS(exp3, obs3, ref2, dat_dim = 'dataset', weights_ref = weights2, Fair = T)$rpss[1] + as.vector(RPSS(exp3, obs3, ref3, dat_dim = 'dataset', weights_ref = weights3, Fair = T)$rpss), + c(0.32938699, 0.29749323, -0.66130649, 0.09722641, 0.10193502, 0.20398179), + tolerance = 0.0001 + ) + expect_equal( + RPSS(exp3, obs3, ref3, dat_dim = 'dataset', weights_ref = weights3, Fair = T)$rpss[1, ], + RPSS(exp3, obs3, ref2, dat_dim = 'dataset', weights_ref = weights2, Fair = T)$rpss[1, ] + ) + expect_equal( + RPSS(exp3, obs3, dat_dim = 'dataset')$rpss[1], + RPSS(exp2, obs2)$rpss ) }) ############################################## -test_that("5. Output checks: dat2, dat3 and dat4", { +test_that("5. Output checks: dat4", { expect_equal( dim(RPSS(exp4, obs4, ref4, dat_dim = 'dataset')$rpss), @@ -374,12 +388,17 @@ test_that("5. Output checks: dat2, dat3 and dat4", { tolerance = 0.0001 ) expect_equal( - RPSS(exp2, obs2)$rpss, - RPSS(exp3, obs3, dat_dim = 'dataset')$rpss[1] + as.vector(RPSS(exp4, obs4, ref4, dat_dim = 'dataset', weights_exp = weights_exp4, weights_ref = weights_ref4)$rpss[, , 2]), + c(-0.22913563, 0.04784362, -0.15832178, 0.13330236, -0.05285335, 0.26871923), + tolerance = 0.0001 + ) + expect_equal( + RPSS(exp4, obs4, dat_dim = 'dataset', weights_exp = weights_exp4)$rpss[1], + RPSS(exp2, obs2, weights_exp = weights2)$rpss ) expect_equal( - RPSS(exp2, obs2, weights_exp = weights2)$rpss, - RPSS(exp4, obs4, dat_dim = 'dataset', weights_exp = weights_exp4)$rpss[1] + RPSS(exp4, obs4, dat_dim = 'dataset', weights_exp = weights_exp4)$rpss[, , 1], + RPSS(exp3, obs3, weights_exp = weights3, dat_dim = 'dataset')$rpss ) }) -- GitLab From d879b98b02a74dc70b9a96ef6c5afe3f234e26e3 Mon Sep 17 00:00:00 2001 From: aho Date: Fri, 19 Aug 2022 16:14:21 +0200 Subject: [PATCH 086/140] Refine code, add more unit tests --- R/CRPSS.R | 86 +++++++++++++++++-------------------- man/CRPSS.Rd | 16 +++---- man/s2dv-package.Rd | 10 +---- man/sampleDepthData.Rd | 6 ++- man/sampleMap.Rd | 6 ++- man/sampleTimeSeries.Rd | 6 ++- tests/testthat/test-CRPSS.R | 44 ++++++++++++++++--- 7 files changed, 98 insertions(+), 76 deletions(-) diff --git a/R/CRPSS.R b/R/CRPSS.R index c77ba3d..a6b4a14 100644 --- a/R/CRPSS.R +++ b/R/CRPSS.R @@ -8,7 +8,7 @@ #'higher skill than the reference forecast, while a negative value means that it #'has a lower skill. Examples of reference forecasts are the climatological #'forecast (same probabilities for all categories for all time steps), -#'persistence, a previous model version, and another model. It is computed as +#'persistence, a previous model version, or another model. It is computed as #'CRPSS = 1 - CRPS_exp / CRPS_ref. The statistical significance is obtained #'based on a Random Walk test at the 95% confidence level (DelSole and Tippett, #'2016). @@ -20,11 +20,11 @@ #' and 'dat_dim'. #'@param ref A named numerical array of the reference forecast data with at #' least time and member dimension. The dimensions must be the same as 'exp' -#' except 'memb_dim' and 'dat_dim' which is NULL if there is only one -#' reference dataset. If there are multiple reference datasets 'dat_dim' -#' dimension must have the same length as in 'exp'. If 'ref' is NULL, the -#' climatological forecast is used as reference forecast. The default value -#' is NULL. +#' except 'memb_dim' and 'dat_dim'. If there is only one reference dataset, +#' it should not have dataset dimension. If there is corresponding reference +#' for each experiement, the dataset dimension must have the same length as in +#' 'exp'. If 'ref' is NULL, the climatological forecast is used as reference +#' forecast. The default value is NULL. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. #'@param memb_dim A character string indicating the name of the member dimension @@ -48,8 +48,8 @@ #' omitted. #'} #'\item{$sign}{ -#' A logical array of the statistical significance of the CRPSS with the same -#' dimensions as 'exp' except the 'time_dim' and 'memb_dim' dimensions. +#' A logical array of the statistical significance of the CRPSS with the same +#' dimensions as $crpss. #'} #' #'@references @@ -118,21 +118,18 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', " Set it as NULL if there is no dataset dimension.") } } - ## exp and obs (2) + ## exp, obs, and ref (2) if (memb_dim %in% names(dim(obs))) { if (identical(as.numeric(dim(obs)[memb_dim]), 1)) { obs <- ClimProjDiags::Subset(x = obs, along = memb_dim, indices = 1, drop = 'selected') } else { stop("Not implemented for observations with members ('obs' can have ", - "'memb_dim', but it should be of length=1).") + "'memb_dim', but it should be of length = 1).") } } name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) name_exp <- name_exp[-which(name_exp == memb_dim)] - if (memb_dim %in% name_obs) { - name_obs <- name_obs[-which(name_obs == memb_dim)] - } if (!is.null(dat_dim)) { name_exp <- name_exp[-which(name_exp == dat_dim)] name_obs <- name_obs[-which(name_obs == dat_dim)] @@ -148,8 +145,8 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', if (!is.null(dat_dim)) { if (dat_dim %in% name_ref) { if (!identical(dim(exp)[dat_dim], dim(ref)[dat_dim])) { - stop(paste0("If parameter 'ref' has 'dat_dim' dimension it must be", - " equal to 'dat_dim' dimension of 'exp'.")) + stop(paste0("If parameter 'ref' has dataset dimension it must be", + " equal to dataset dimension of 'exp'.")) } name_ref <- name_ref[-which(name_ref == dat_dim)] } @@ -177,7 +174,7 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', # Compute CRPSS if (!is.null(ref)) { # use "ref" as reference forecast if (!is.null(dat_dim) && (dat_dim %in% names(dim(ref)))) { - target_dims_ref <- c(time_dim, memb_dim, dat_dim) + target_dims_ref <- c(time_dim, memb_dim, dat_dim) } else { target_dims_ref <- c(time_dim, memb_dim) } @@ -204,9 +201,9 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', .CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', dat_dim = NULL, Fair = FALSE) { - # exp: [sdate, memb (dat_dim)] - # obs: [sdate, (dat_dim)] - # ref: [sdate, memb, (dat_dim)] or NULL + # exp: [sdate, memb, (dat)] + # obs: [sdate, (dat)] + # ref: [sdate, memb, (dat)] or NULL if (is.null(dat_dim)) { nexp <- 1 @@ -216,46 +213,39 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', nobs <- as.numeric(dim(obs)[dat_dim]) } - # CRPS of the forecast + #----- CRPS of the forecast # [sdate, (nexp), (nobs)] crps_exp <- .CRPS(exp = exp, obs = obs, time_dim = time_dim, memb_dim = memb_dim, dat_dim = dat_dim, Fair = Fair) - # CRPS of the reference forecast + #----- CRPS of the reference forecast if (is.null(ref)) { ## using climatology as reference forecast ## all the time steps are used as if they were members ## then, ref dimensions are [sdate, memb], both with length(sdate) - if (is.null(dat_dim)) { - ref <- array(data = obs, dim = c(member = length(obs))) - ref <- InsertDim(data = ref, posdim = 1, lendim = length(obs), name = 'sdate') - crps_ref <- .CRPS(exp = ref, obs = obs, Fair = Fair) - } else { - ref <- array(dim = c(dim(obs)[1], member = unname(dim(obs)[1]), dim(obs)[2])) - for (j in 1:nobs) { - ref_obs <- array(data = obs[ , j], dim = c(member = length(obs[ , j]))) - ref_obs <- InsertDim(data = ref_obs, posdim = 1, lendim = length(obs[ , j]), name = 'sdate') - ref[ , , j] <- ref_obs - } - - # for FairCRPS - R_new <- ifelse(Fair, Inf, NA) - - crps_ref <- array(dim = c(dim(exp)[time_dim], nobs = nobs)) - for (j in 1:nobs) { - ref_data <- ref[ , , j] - obs_data <- obs[ , j] - - if (is.null(dim(ref_data))) dim(ref_data) <- c(dim(exp)[1:2]) - if (is.null(dim(obs_data))) dim(obs_data) <- c(dim(obs)[1]) + obs_time_len <- dim(obs)[time_dim] + if (is.null(dat_dim)) { + ref <- array(data = rep(obs, each = obs_time_len), dim = c(obs_time_len, obs_time_len)) + names(dim(ref)) <- c(time_dim, memb_dim) + # ref: [sdate, memb]; obs: [sdate] + crps_ref <- .CRPS(exp = ref, obs = obs, time_dim = time_dim, memb_dim = memb_dim, + dat_dim = dat_dim, Fair = Fair) + # crps_ref should be [sdate] - crps <- SpecsVerification::enscrps_cpp(ens = ref_data, obs = obs_data, R_new = R_new) - crps_ref[ , j] <- crps + } else { + crps_ref <- array(dim = c(obs_time_len, nobs)) + names(dim(crps_ref)) <- c(time_dim, 'nobs') + for (i_obs in 1:nobs) { + ref <- array(data = rep(obs[, i_obs], each = obs_time_len), dim = c(obs_time_len, obs_time_len)) + names(dim(ref)) <- c(time_dim, memb_dim) + crps_ref[, i_obs] <- .CRPS(exp = ref, obs = ClimProjDiags::Subset(obs, dat_dim, i_obs, drop = 'selected'), + time_dim = time_dim, memb_dim = memb_dim, dat_dim = NULL, Fair = Fair) } + # crps_ref should be [sdate, nobs] } - } else { + } else { # ref is not NULL if (!is.null(dat_dim) && (!dat_dim %in% names(dim(ref)))) { remove_dat_dim <- TRUE ref <- InsertDim(data = ref, posdim = length(dim(ref)) + 1 , lendim = 1, name = dat_dim) @@ -264,6 +254,8 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } crps_ref <- .CRPS(exp = ref, obs = obs, time_dim = time_dim, memb_dim = memb_dim, dat_dim = dat_dim, Fair = Fair) + # crps_ref should be [sdate, (nexp), (nobs)] + if (!is.null(dat_dim)) { if (isTRUE(remove_dat_dim)) { dim(crps_ref) <- dim(crps_ref)[-2] @@ -271,7 +263,9 @@ CRPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } } + #----- CRPSS if (!is.null(dat_dim)) { + # If ref != NULL & ref has dat_dim, crps_ref = [sdate, nexp, nobs]; else, crps_ref = [sdate, nobs] crps_exp_mean <- MeanDims(crps_exp, time_dim, na.rm = FALSE) crps_ref_mean <- MeanDims(crps_ref, time_dim, na.rm = FALSE) diff --git a/man/CRPSS.Rd b/man/CRPSS.Rd index 903a7f5..31bf501 100644 --- a/man/CRPSS.Rd +++ b/man/CRPSS.Rd @@ -25,11 +25,11 @@ and 'dat_dim'.} \item{ref}{A named numerical array of the reference forecast data with at least time and member dimension. The dimensions must be the same as 'exp' -except 'memb_dim' and 'dat_dim' which is NULL if there is only one -reference dataset. If there are multiple reference datasets 'dat_dim' -dimension must have the same length as in 'exp'. If 'ref' is NULL, the -climatological forecast is used as reference forecast. The default value -is NULL.} +except 'memb_dim' and 'dat_dim'. If there is only one reference dataset, +it should not have dataset dimension. If there is corresponding reference +for each experiement, the dataset dimension must have the same length as in +'exp'. If 'ref' is NULL, the climatological forecast is used as reference +forecast. The default value is NULL.} \item{time_dim}{A character string indicating the name of the time dimension. The default value is 'sdate'.} @@ -58,8 +58,8 @@ computation. The default value is NULL.} omitted. } \item{$sign}{ - A logical array of the statistical significance of the CRPSS with the same - dimensions as 'exp' except the 'time_dim' and 'memb_dim' dimensions. + A logical array of the statistical significance of the CRPSS with the same + dimensions as $crpss. } } \description{ @@ -71,7 +71,7 @@ infinite and 1. If the CRPSS is positive, it indicates that the forecast has higher skill than the reference forecast, while a negative value means that it has a lower skill. Examples of reference forecasts are the climatological forecast (same probabilities for all categories for all time steps), -persistence, a previous model version, and another model. It is computed as +persistence, a previous model version, or another model. It is computed as CRPSS = 1 - CRPS_exp / CRPS_ref. The statistical significance is obtained based on a Random Walk test at the 95% confidence level (DelSole and Tippett, 2016). diff --git a/man/s2dv-package.Rd b/man/s2dv-package.Rd index ffb6783..3c98a95 100644 --- a/man/s2dv-package.Rd +++ b/man/s2dv-package.Rd @@ -6,15 +6,7 @@ \alias{s2dv-package} \title{s2dv: A Set of Common Tools for Seasonal to Decadal Verification} \description{ -The advanced version of package 's2dverification'. It is - intended for 'seasonal to decadal' (s2d) climate forecast verification, but - it can also be used in other kinds of forecasts or general climate analysis. - This package is specially designed for the comparison between the experimental - and observational datasets. The functionality of the included functions covers - from data retrieval, data post-processing, skill scores against observation, - to visualization. Compared to 's2dverification', 's2dv' is more compatible - with the package 'startR', able to use multiple cores for computation and - handle multi-dimensional arrays with a higher flexibility. +The advanced version of package 's2dverification'. It is intended for 'seasonal to decadal' (s2d) climate forecast verification, but it can also be used in other kinds of forecasts or general climate analysis. This package is specially designed for the comparison between the experimental and observational datasets. The functionality of the included functions covers from data retrieval, data post-processing, skill scores against observation, to visualization. Compared to 's2dverification', 's2dv' is more compatible with the package 'startR', able to use multiple cores for computation and handle multi-dimensional arrays with a higher flexibility. } \references{ \url{https://earth.bsc.es/gitlab/es/s2dv/} diff --git a/man/sampleDepthData.Rd b/man/sampleDepthData.Rd index 77e4a7a..47e2f1b 100644 --- a/man/sampleDepthData.Rd +++ b/man/sampleDepthData.Rd @@ -5,7 +5,8 @@ \alias{sampleDepthData} \title{Sample of Experimental Data for Forecast Verification In Function Of Latitudes And Depths} -\format{The data set provides with a variable named 'sampleDepthData'.\cr\cr +\format{ +The data set provides with a variable named 'sampleDepthData'.\cr\cr sampleDepthData$exp is an array that contains the experimental data and the dimension meanings and values are:\cr @@ -18,7 +19,8 @@ but in this sample is not defined (NULL).\cr\cr sampleDepthData$depths is an array with the 7 longitudes covered by the data.\cr\cr -sampleDepthData$lat is an array with the 21 latitudes covered by the data.\cr\cr} +sampleDepthData$lat is an array with the 21 latitudes covered by the data.\cr\cr +} \usage{ data(sampleDepthData) } diff --git a/man/sampleMap.Rd b/man/sampleMap.Rd index eaf8aa5..e4ec5a5 100644 --- a/man/sampleMap.Rd +++ b/man/sampleMap.Rd @@ -4,7 +4,8 @@ \name{sampleMap} \alias{sampleMap} \title{Sample Of Observational And Experimental Data For Forecast Verification In Function Of Longitudes And Latitudes} -\format{The data set provides with a variable named 'sampleMap'.\cr\cr +\format{ +The data set provides with a variable named 'sampleMap'.\cr\cr sampleMap$mod is an array that contains the experimental data and the dimension meanings and values are:\cr c(# of experimental datasets, # of members, # of starting dates, # of lead-times, # of latitudes, # of longitudes)\cr @@ -16,7 +17,8 @@ sampleMap$obs is an array that contains the observational data and the dimension sampleMap$lat is an array with the 2 latitudes covered by the data (see examples on Load() for details on why such low resolution).\cr\cr - sampleMap$lon is an array with the 3 longitudes covered by the data (see examples on Load() for details on why such low resolution).} + sampleMap$lon is an array with the 3 longitudes covered by the data (see examples on Load() for details on why such low resolution). +} \usage{ data(sampleMap) } diff --git a/man/sampleTimeSeries.Rd b/man/sampleTimeSeries.Rd index 05a8e79..7f058e2 100644 --- a/man/sampleTimeSeries.Rd +++ b/man/sampleTimeSeries.Rd @@ -4,7 +4,8 @@ \name{sampleTimeSeries} \alias{sampleTimeSeries} \title{Sample Of Observational And Experimental Data For Forecast Verification As Area Averages} -\format{The data set provides with a variable named 'sampleTimeSeries'.\cr\cr +\format{ +The data set provides with a variable named 'sampleTimeSeries'.\cr\cr sampleTimeSeries$mod is an array that contains the experimental data and the dimension meanings and values are:\cr c(# of experimental datasets, # of members, # of starting dates, # of lead-times)\cr @@ -16,7 +17,8 @@ sampleTimeSeries$obs is an array that contains the observational data and the di sampleTimeSeries$lat is an array with the 2 latitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution).\cr\cr -sampleTimeSeries$lon is an array with the 3 longitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution).} +sampleTimeSeries$lon is an array with the 3 longitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution). +} \usage{ data(sampleTimeSeries) } diff --git a/tests/testthat/test-CRPSS.R b/tests/testthat/test-CRPSS.R index 68b763a..6937edb 100644 --- a/tests/testthat/test-CRPSS.R +++ b/tests/testthat/test-CRPSS.R @@ -87,7 +87,7 @@ test_that("1. Input checks", { ) expect_error( CRPSS(exp2_2, obs2_2, array(1:9, dim = c(sdate = 9, member = 2, dataset = 3)), dat_dim = 'dataset'), - "If parameter 'ref' has 'dat_dim' dimension it must be equal to 'dat_dim' dimension of 'exp'." + "If parameter 'ref' has dataset dimension it must be equal to dataset dimension of 'exp'." ) expect_error( CRPSS(exp1, obs1, ref2), @@ -95,7 +95,7 @@ test_that("1. Input checks", { ) expect_error( CRPSS(exp3, array(rnorm(60), dim = c(member = 2, sdate = 10, dataset = 3)), ref3, dat_dim = 'dataset'), - "Not implemented for observations with members ('obs' can have 'memb_dim', but it should be of length=1)." + "Not implemented for observations with members ('obs' can have 'memb_dim', but it should be of length = 1)." , fixed = TRUE ) # Fair @@ -269,26 +269,56 @@ test_that("4. Output checks: dat3", { tolerance = 0.0001 ) expect_equal( - as.vector(CRPSS(exp3, obs3, dat_dim = 'dataset')$sign)[1:3], - c(FALSE, FALSE, FALSE), + as.vector(CRPSS(exp3, obs3, dat_dim = 'dataset')$crpss), + c(-0.8209236, -0.6270744, -1.0829403, -0.6574485, -0.5970569, -0.6488837), + tolerance = 0.0001 + ) + expect_equal( + as.vector(CRPSS(exp3, obs3, dat_dim = 'dataset')$sign), + rep(FALSE, 6), ) expect_equal( mean(CRPSS(exp3, obs3, dat_dim = 'dataset', Fair = T)$crpss), c(-0.5302703), tolerance = 0.0001 ) + expect_equal( + as.vector(CRPSS(exp3, obs3, dat_dim = 'dataset', Fair = T)$crpss), + c(-0.4681890, -0.3580685, -1.0116628, -0.3730576, -0.3965618, -0.5740819), + tolerance = 0.0001 + ) + # ref = ref3 + expect_equal( + as.vector(CRPSS(exp3, obs3, ref = ref3, dat_dim = 'dataset')$crpss), + c(-0.02315361, -0.05914715, -0.24638960, -0.09337738, 0.14668803, -0.01454008), + tolerance = 0.0001 + ) + + expect_equal( + as.vector(CRPSS(exp3, obs3, dat_dim = 'dataset')$crpss[1]), + as.vector(CRPSS(exp2, obs2)$crpss) + ) + }) ############################################## -test_that("5. Output checks: dat4, dat2 and dat5", { +test_that("5. Output checks: dat4", { expect_equal( dim(CRPSS(exp4, obs4, ref4, dat_dim = 'dataset')$crpss), c('nexp' = 3, 'nobs' = 2, 'lat' = 2) ) expect_equal( - as.vector(CRPSS(exp2, obs2)$crpss), - as.vector(CRPSS(exp3, obs3, dat_dim = 'dataset')$crpss[1]) + as.vector(CRPSS(exp4, obs4, dat_dim = 'dataset', Fair = T)$crpss)[1:6], + c(-0.4681890, -0.3580685, -1.0116628, -0.3730576, -0.3965618, -0.5740819), + tolerance = 0.0001 ) + # ref = ref3 + expect_equal( + as.vector(CRPSS(exp4, obs4, ref = ref4, dat_dim = 'dataset')$crpss)[1:6], + c(-0.02315361, 0.08576776, -0.17037744, -0.09337738, -0.05353853, -0.08772739), + tolerance = 0.0001 + ) + }) -- GitLab From 8291ee5c1b9ee40cbd6e8dea88ba0ce60c4cb8b7 Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Tue, 30 Aug 2022 12:37:01 +0200 Subject: [PATCH 087/140] included two-sided test --- R/DiffCorr.R | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/R/DiffCorr.R b/R/DiffCorr.R index c250814..8fdcbf0 100644 --- a/R/DiffCorr.R +++ b/R/DiffCorr.R @@ -38,6 +38,8 @@ #' steps with no missing values in all "exp", "ref", and "obs" will be used. If #' "na.fail", an error will arise if any of "exp", "ref", or "obs" contains any #' NA. The default value is "return.na". +#'@param two.sided A logical indicating whether to perform a two-sided test. If +#' FALSE, a one.sided test is performed. The default value is FALSE. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -72,7 +74,7 @@ #'@export DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', memb_dim = NULL, method = 'pearson', alpha = NULL, - handle.na = 'return.na', ncores = NULL) { + handle.na = 'return.na', two.sided = FALSE, ncores = NULL) { # Check inputs ## exp, ref, and obs (1) @@ -143,6 +145,11 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', if (!handle.na %in% c('return.na', 'only.complete.triplets', 'na.fail')) { stop('Parameter "handle.na" must be "return.na", "only.complete.triplets" or "na.fail".') } + ## two.sided + if (!is.logical(two.sided)){ + stop('Parameter "two.sided" must be TRUE or FALSE') + } + ## ncores if (!is.null(ncores)) { if (any(!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 | length(ncores) > 1)) { @@ -184,22 +191,24 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', ref = time_dim, N.eff = NULL), output_dims = output_dims, fun = .DiffCorr, method = method, - alpha = alpha, handle.na = handle.na, ncores = ncores) + alpha = alpha, handle.na = handle.na, + two.sided = two.sided, ncores = ncores) } else { output <- Apply(data = list(exp = exp, obs = obs, ref = ref), target_dims = list(exp = time_dim, obs = time_dim, ref = time_dim), output_dims = output_dims, N.eff = N.eff, fun = .DiffCorr, method = method, - alpha = alpha, handle.na = handle.na, ncores = ncores) + alpha = alpha, handle.na = handle.na, + two.sided = two.sided, ncores = ncores) } return(output) } -.DiffCorr <- function(exp, obs, ref, N.eff = NA, method = 'pearson', alpha = NULL, handle.na = 'return.na') { +.DiffCorr <- function(exp, obs, ref, N.eff = NA, method = 'pearson', alpha = NULL, handle.na = 'return.na', two.sided = FALSE) { - .diff.corr <- function(exp, obs, ref, method = 'pearson', N.eff = NA, alpha = NULL) { + .diff.corr <- function(exp, obs, ref, method = 'pearson', N.eff = NA, alpha = NULL, two.sided = FALSE) { # Correlation difference cor.exp <- cor(x = exp, y = obs, method = method) @@ -220,7 +229,11 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', if (is.null(alpha)) { output$p.val <- p.value } else { - output$sign <- ifelse(!is.na(p.value) & p.value <= alpha, TRUE, FALSE) + if (isFALSE(two.sided)){ + output$sign <- ifelse(!is.na(p.value) & p.value <= alpha, TRUE, FALSE) + } else if (isTRUE(two.sided)){ + output$sign <- ifelse(!is.na(p.value) & p.value <= alpha/2, TRUE, FALSE) + } } return(output) } -- GitLab From c770d4b2634805627bda95107d2373f9fc1ad9f1 Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Tue, 30 Aug 2022 16:49:47 +0200 Subject: [PATCH 088/140] first version --- R/Bias.R | 32 +++++++++++++++++++++++++ R/BiasSS.R | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 R/Bias.R create mode 100644 R/BiasSS.R diff --git a/R/Bias.R b/R/Bias.R new file mode 100644 index 0000000..e3d5242 --- /dev/null +++ b/R/Bias.R @@ -0,0 +1,32 @@ + +# exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) +# obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, year = 50)) +# bias <- Bias(exp = exp, obs = obs, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) + +Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, ncores = NULL) { + + ## Checks + + + ## Ensemble mean + if (!is.null(memb_dim)) { + exp <- MeanDims(exp, memb_dim, na.rm = na.rm) + } + + ## Mean bias + bias <- multiApply::Apply(data = list(exp, obs), + target_dims = time_dim, + fun = .Bias, + ncores = ncores)$output1 + ## Return the mean bias + bias <- MeanDims(bias, time_dim, na.rm = na.rm) + + return(bias) +} + +.Bias <- function(exp, obs) { + + bias <- exp - obs + + return(bias) +} diff --git a/R/BiasSS.R b/R/BiasSS.R new file mode 100644 index 0000000..f71e5b5 --- /dev/null +++ b/R/BiasSS.R @@ -0,0 +1,68 @@ + + +# exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) +# ref <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) +# obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, year = 50)) +# biasSS1 <- BiasSS(exp = exp, obs = obs, ref = ref, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) +# biasSS2 <- BiasSS(exp = exp, obs = obs, ref = NULL, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) + +BiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, ncores = NULL) { + + ## Checks + + + ## Ensemble mean + if (!is.null(memb_dim)) { + exp <- MeanDims(exp, memb_dim, na.rm = na.rm) + if (!is.null(ref)) { + ref <- MeanDims(ref, memb_dim, na.rm = na.rm) + } + } + + ## Mean bias skill score + if (is.null(ref)) { + ss <- multiApply::Apply(data = list(exp, obs), + target_dims = time_dim, + fun = .BiasSS, na.rm = na.rm, + ref = ref, ncores = ncores) + + } else { + ss <- multiApply::Apply(data = list(exp, obs, ref), + target_dims = time_dim, + fun = .BiasSS, na.rm = na.rm, + ncores = ncores) + } + return(ss) +} + +.BiasSS <- function(exp, obs, ref = NULL, na.rm = FALSE) { + + if (isTRUE(na.rm)) { + if (is.null(ref)) { + good_values <- !is.na(exp) & !is.na(obs) + exp <- exp[good_values] + obs <- obs[good_values] + } else { + good_values <- !is.na(exp) & !is.na(ref) & !is.na(obs) + exp <- exp[good_values] + ref <- ref[good_values] + obs <- obs[good_values] + } + } + + ## Bias of the exp + bias_exp <- .Bias(exp = exp, obs = obs) + + ## Bias of the ref + if (is.null(ref)) { ## Climatological forecast + ref <- mean(obs, na.rm = na.rm) + } + bias_ref <- .Bias(exp = ref, obs = obs) + + ## Skill score and significance + ss <- list() + ss$biasSS <- 1 - mean(abs(bias_exp), na.rm = na.rm) / mean(abs(bias_ref), na.rm = na.rm) + ss$sign <- .RandomWalkTest(skill_A = bias_exp, skill_B = bias_ref)$signif + + return(ss) +} -- GitLab From dfaad430a399cd9a858c688f1d1451079b62f2b6 Mon Sep 17 00:00:00 2001 From: aho Date: Tue, 30 Aug 2022 17:31:35 +0200 Subject: [PATCH 089/140] Trivial document and format fix; unit test for two-sided added --- R/DiffCorr.R | 16 ++++++++-------- man/DiffCorr.Rd | 12 ++++++++---- tests/testthat/test-DiffCorr.R | 13 +++++++++++++ 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/R/DiffCorr.R b/R/DiffCorr.R index 8fdcbf0..9fb6067 100644 --- a/R/DiffCorr.R +++ b/R/DiffCorr.R @@ -4,10 +4,10 @@ #'Positive values of the correlation difference indicate that the forecast is #'more skillful than the reference forecast, while negative values mean that #'the reference forecast is more skillful. The statistical significance of the -#'correlation differences is computed with a one-sided test for equality of -#'dependent correlation coefficients (Steiger, 1980; Siegert et al., 2017) using -#'effective degrees of freedom to account for the autocorrelation of the time -#'series (von Storch and Zwiers, 1999). +#'correlation differences is computed with a one-sided or two-sided test for +#'equality of dependent correlation coefficients (Steiger, 1980; Siegert et al., +#'2017) using effective degrees of freedom to account for the autocorrelation of +#'the time series (von Storch and Zwiers, 1999). #' #'@param exp A named numerical array of the forecast data with at least time #' dimension. @@ -146,8 +146,8 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', stop('Parameter "handle.na" must be "return.na", "only.complete.triplets" or "na.fail".') } ## two.sided - if (!is.logical(two.sided)){ - stop('Parameter "two.sided" must be TRUE or FALSE') + if (!is.logical(two.sided)) { + stop("Parameter 'two.sided' must be TRUE or FALSE.") } ## ncores if (!is.null(ncores)) { @@ -229,9 +229,9 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', if (is.null(alpha)) { output$p.val <- p.value } else { - if (isFALSE(two.sided)){ + if (isFALSE(two.sided)) { output$sign <- ifelse(!is.na(p.value) & p.value <= alpha, TRUE, FALSE) - } else if (isTRUE(two.sided)){ + } else if (isTRUE(two.sided)) { output$sign <- ifelse(!is.na(p.value) & p.value <= alpha/2, TRUE, FALSE) } } diff --git a/man/DiffCorr.Rd b/man/DiffCorr.Rd index d8ff65c..ca16cd4 100644 --- a/man/DiffCorr.Rd +++ b/man/DiffCorr.Rd @@ -14,6 +14,7 @@ DiffCorr( method = "pearson", alpha = NULL, handle.na = "return.na", + two.sided = FALSE, ncores = NULL ) } @@ -56,6 +57,9 @@ steps with no missing values in all "exp", "ref", and "obs" will be used. If "na.fail", an error will arise if any of "exp", "ref", or "obs" contains any NA. The default value is "return.na".} +\item{two.sided}{A logical indicating whether to perform a two-sided test. If +FALSE, a one.sided test is performed. The default value is FALSE.} + \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} } @@ -81,10 +85,10 @@ Compute the correlation difference between two deterministic forecasts. Positive values of the correlation difference indicate that the forecast is more skillful than the reference forecast, while negative values mean that the reference forecast is more skillful. The statistical significance of the -correlation differences is computed with a one-sided test for equality of -dependent correlation coefficients (Steiger, 1980; Siegert et al., 2017) using -effective degrees of freedom to account for the autocorrelation of the time -series (von Storch and Zwiers, 1999). +correlation differences is computed with a one-sided or two-sided test for +equality of dependent correlation coefficients (Steiger, 1980; Siegert et al., +2017) using effective degrees of freedom to account for the autocorrelation of +the time series (von Storch and Zwiers, 1999). } \examples{ exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) diff --git a/tests/testthat/test-DiffCorr.R b/tests/testthat/test-DiffCorr.R index e0834e1..9a087fc 100644 --- a/tests/testthat/test-DiffCorr.R +++ b/tests/testthat/test-DiffCorr.R @@ -89,6 +89,11 @@ test_that("1. Input checks", { DiffCorr(exp2, obs2, ref2, handle.na = TRUE), 'Parameter "handle.na" must be "return.na", "only.complete.triplets" or "na.fail".' ) + # two.sided + expect_error( + DiffCorr(exp2, obs2, ref2, two.sided = 2), + "Parameter 'two.sided' must be TRUE or FALSE." + ) # ncores expect_error( DiffCorr(exp2, obs2, ref2, ncores = 1.5), @@ -131,10 +136,18 @@ c(0.27347087, 0.50556882, 0.08855968, 0.24199701, 0.22935182, 0.88336336), tolerance = 0.0001 ) expect_equal( +as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05)$diff.corr), +as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, two.sided = T)$diff.corr) +) +expect_equal( as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05)$sign), rep(FALSE, 6) ) expect_equal( +as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, two.sided = T)$sign), +rep(FALSE, 6) +) +expect_equal( suppressWarnings(as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', method = "spearman")$diff.corr)), c(0.07272727, 0.54545455, 0.10909091, -0.01212121, -0.03636364, 1.01818182), tolerance = 0.0001 -- GitLab From e59119cff930f6569594f129fc5481a7b087c702 Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Wed, 31 Aug 2022 12:23:28 +0200 Subject: [PATCH 090/140] posible tests: two-sided, one-sided-higher and one-sided-lower --- R/DiffCorr.R | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/R/DiffCorr.R b/R/DiffCorr.R index 9fb6067..0775106 100644 --- a/R/DiffCorr.R +++ b/R/DiffCorr.R @@ -38,8 +38,11 @@ #' steps with no missing values in all "exp", "ref", and "obs" will be used. If #' "na.fail", an error will arise if any of "exp", "ref", or "obs" contains any #' NA. The default value is "return.na". -#'@param two.sided A logical indicating whether to perform a two-sided test. If -#' FALSE, a one.sided test is performed. The default value is FALSE. +#'@param type A character string indicating the type of test. It can be "two-sided" +#' (to assess whether the skill of "exp" and "ref" are significantly different), +#' "one-sided-higher" (to assess whether the skill of "exp" is significantly higher +#' than that of "ref"), or "one-sided-lower" (to assess whether the skill of "exp" +#' is significantly lower than that of "ref"). The default value is "two-sided". #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -74,7 +77,7 @@ #'@export DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', memb_dim = NULL, method = 'pearson', alpha = NULL, - handle.na = 'return.na', two.sided = FALSE, ncores = NULL) { + handle.na = 'return.na', type = "two-sided", ncores = NULL) { # Check inputs ## exp, ref, and obs (1) @@ -145,9 +148,9 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', if (!handle.na %in% c('return.na', 'only.complete.triplets', 'na.fail')) { stop('Parameter "handle.na" must be "return.na", "only.complete.triplets" or "na.fail".') } - ## two.sided - if (!is.logical(two.sided)) { - stop("Parameter 'two.sided' must be TRUE or FALSE.") + ## type + if (!type %in% c('two-sided','one-sided-higher','one-sided-lower')) { + stop("Parameter 'type' must be 'two-sided', 'one-sided-higher' or 'one-sided-lower'.") } ## ncores if (!is.null(ncores)) { @@ -192,7 +195,7 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', output_dims = output_dims, fun = .DiffCorr, method = method, alpha = alpha, handle.na = handle.na, - two.sided = two.sided, ncores = ncores) + type = type, ncores = ncores) } else { output <- Apply(data = list(exp = exp, obs = obs, ref = ref), target_dims = list(exp = time_dim, obs = time_dim, @@ -200,15 +203,15 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', output_dims = output_dims, N.eff = N.eff, fun = .DiffCorr, method = method, alpha = alpha, handle.na = handle.na, - two.sided = two.sided, ncores = ncores) + type = type, ncores = ncores) } return(output) } -.DiffCorr <- function(exp, obs, ref, N.eff = NA, method = 'pearson', alpha = NULL, handle.na = 'return.na', two.sided = FALSE) { +.DiffCorr <- function(exp, obs, ref, N.eff = NA, method = 'pearson', alpha = NULL, handle.na = 'return.na', type = 'two.sided') { - .diff.corr <- function(exp, obs, ref, method = 'pearson', N.eff = NA, alpha = NULL, two.sided = FALSE) { + .diff.corr <- function(exp, obs, ref, method = 'pearson', N.eff = NA, alpha = NULL, type = 'two.sided') { # Correlation difference cor.exp <- cor(x = exp, y = obs, method = method) @@ -229,10 +232,12 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', if (is.null(alpha)) { output$p.val <- p.value } else { - if (isFALSE(two.sided)) { - output$sign <- ifelse(!is.na(p.value) & p.value <= alpha, TRUE, FALSE) - } else if (isTRUE(two.sided)) { + if (type == 'two-sided') { output$sign <- ifelse(!is.na(p.value) & p.value <= alpha/2, TRUE, FALSE) + } else if (type == 'one-sided-higher') { + output$sign <- ifelse(!is.na(p.value) & p.value <= alpha & output$diff.corr > 0, TRUE, FALSE) + } else if (type == 'one-sided-lower') { + output$sign <- ifelse(!is.na(p.value) & p.value <= alpha & output$diff.corr < 0, TRUE, FALSE) } } return(output) @@ -250,7 +255,7 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', ref <- ref[!nna] output <- .diff.corr(exp = exp, obs = obs, ref = ref, method = method, - N.eff = N.eff, alpha = alpha) + N.eff = N.eff, alpha = alpha, type = type) } else if (handle.na == 'return.na') { # Data contain NA, return NAs directly without passing to .diff.corr @@ -263,7 +268,7 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', } else { ## There is no NA output <- .diff.corr(exp = exp, obs = obs, ref = ref, method = method, - N.eff = N.eff, alpha = alpha) + N.eff = N.eff, alpha = alpha, type = type) } return(output) } -- GitLab From 1e3303ac34252b6461083e132b0073107971820c Mon Sep 17 00:00:00 2001 From: ALBA LLABRES Date: Wed, 31 Aug 2022 12:45:52 +0200 Subject: [PATCH 091/140] first version documentation added --- R/Bias.R | 39 ++++++++++++++++++++++++++++---- R/BiasSS.R | 65 ++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 93 insertions(+), 11 deletions(-) diff --git a/R/Bias.R b/R/Bias.R index e3d5242..3f76dc7 100644 --- a/R/Bias.R +++ b/R/Bias.R @@ -1,7 +1,38 @@ - -# exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) -# obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, year = 50)) -# bias <- Bias(exp = exp, obs = obs, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) +#'Compute the Mean Bias +#' +#'The Mean Bias or Mean Error (Wilks, 2011) is defined as the mean difference +#'between the ensemble mean forecast and the observations. It is a deterministic +#'metric. Positive values indicate that the forecasts are on average too high +#'and negative values indicate that the forecasts are on average too low; however, +#'it gives no information about the typical magnitude of individual forecast errors. +#' +#'@param exp A named numerical array of the forecast with at least time +#' dimension. +#'@param obs A named numerical array of the observation with at least time +#' dimension. The dimensions must be the same as 'exp' except 'memb_dim' and +#' 'dat_dim'. +#'@param time_dim A character string indicating the name of the time dimension. +#' The default value is 'sdate'. +#'@param memb_dim A character string indicating the name of the member dimension +#' to compute the ensemble mean; it should be set to NULL if the parameter 'exp' +#' is already the ensemble mean. The default value is NULL. +#'@param ncores An integer indicating the number of cores to use for parallel +#' computation. The default value is NULL. +#' +#'@return +#'A numerical array of Bias with dimensions the dimensions of +#''exp' except 'time_dim' and 'memb_dim' dimensions. +#' +#'@references +#'Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 +#' +#'@examples +#'exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) +#'obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, year = 50)) +#'bias <- Bias(exp = exp, obs = obs, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) +#' +#'@import multiApply +#'@export Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, ncores = NULL) { diff --git a/R/BiasSS.R b/R/BiasSS.R index f71e5b5..ef94c85 100644 --- a/R/BiasSS.R +++ b/R/BiasSS.R @@ -1,10 +1,61 @@ - - -# exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) -# ref <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) -# obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, year = 50)) -# biasSS1 <- BiasSS(exp = exp, obs = obs, ref = ref, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) -# biasSS2 <- BiasSS(exp = exp, obs = obs, ref = NULL, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) +#'Compute the Mean Bias Skill Score +#' +#'The Mean Bias Skill Score is based on the Mean Error (Wilks, 2011) +#'between the ensemble mean forecast and the observations. It measures +#'the accuracy of the forecast and it can be used to assess whether a +#'forecast presents an improvement or a worsening with respect to a reference +#'forecast. The Mean Bias Skill Score ranges between minus infinite and 1. +#'Positive values indicate that the forecast has higher skill than the +#'reference forecast, while negative values indicate that it has a lower skill. +#'Examples of reference forecasts are the climatological forecast (average of +#'the observations), a previous model version, or another model. It is computed +#'as BiasSS = 1 - Bias_exp / Bias_ref. The statistical significance is obtained +#'based on a Random Walk test at the 95% confidence level (DelSole and Tippett, +#'2016). +#' +#'@param exp A named numerical array of the forecast with at least time +#' dimension. +#'@param obs A named numerical array of the observation with at least time +#' dimension. The dimensions must be the same as 'exp' except 'memb_dim' and +#' 'dat_dim'. +#'@param ref A named numerical array of the reference forecast data with at +#' least time and member dimension. The dimensions must be the same as 'exp' +#' except 'memb_dim' and 'dat_dim'. If there is only one reference dataset, +#' it should not have dataset dimension. If there is corresponding reference +#' for each experiement, the dataset dimension must have the same length as in +#' 'exp'. If 'ref' is NULL, the climatological forecast is used as reference +#' forecast. The default value is NULL. +#'@param time_dim A character string indicating the name of the time dimension. +#' The default value is 'sdate'. +#'@param memb_dim A character string indicating the name of the member dimension +#' to compute the ensemble mean; it should be set to NULL if the parameter 'exp' +#' is already the ensemble mean. The default value is NULL. +#'@param ncores An integer indicating the number of cores to use for parallel +#' computation. The default value is NULL. +#' +#'@return +#'\item{$biasSS}{ +#' A numerical array of BiasSS with dimensions the dimensions of +#' 'exp' except 'time_dim' and 'memb_dim' dimensions. +#'} +#'\item{$sign}{ +#' A logical array of the statistical significance of the BiasSS +#' with the same dimensions as $biasSS. +#'} +#' +#'@references +#'Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 +#'DelSole and Tippett, 2016; https://doi.org/10.1175/MWR-D-15-0218.1 +#' +#'@examples +#'exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) +#'ref <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) +#'obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, year = 50)) +#'biasSS1 <- BiasSS(exp = exp, obs = obs, ref = ref, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) +#'biasSS2 <- BiasSS(exp = exp, obs = obs, ref = NULL, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) +#' +#'@import multiApply +#'@export BiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, ncores = NULL) { -- GitLab From 95f5fa48c3fe3f0b08bbd2039df4e716010e117b Mon Sep 17 00:00:00 2001 From: ALBA LLABRES Date: Wed, 31 Aug 2022 13:23:28 +0200 Subject: [PATCH 092/140] documentation improvement --- R/BiasSS.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/BiasSS.R b/R/BiasSS.R index ef94c85..e59189b 100644 --- a/R/BiasSS.R +++ b/R/BiasSS.R @@ -2,9 +2,9 @@ #' #'The Mean Bias Skill Score is based on the Mean Error (Wilks, 2011) #'between the ensemble mean forecast and the observations. It measures -#'the accuracy of the forecast and it can be used to assess whether a -#'forecast presents an improvement or a worsening with respect to a reference -#'forecast. The Mean Bias Skill Score ranges between minus infinite and 1. +#'the accuracy of the forecast in comparison with a reference forecast to assess +#'whether the forecast presents an improvement or a worsening with respect to +#'that reference. The Mean Bias Skill Score ranges between minus infinite and 1. #'Positive values indicate that the forecast has higher skill than the #'reference forecast, while negative values indicate that it has a lower skill. #'Examples of reference forecasts are the climatological forecast (average of -- GitLab From 6b34657b30eac316a819bf9d52837194b36bb549 Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Wed, 31 Aug 2022 15:09:33 +0200 Subject: [PATCH 093/140] added checks (dat_dim missing) --- R/Bias.R | 67 +++++++++++++++++++++++++++++++++--- R/BiasSS.R | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 159 insertions(+), 7 deletions(-) diff --git a/R/Bias.R b/R/Bias.R index 3f76dc7..15c3f4e 100644 --- a/R/Bias.R +++ b/R/Bias.R @@ -16,6 +16,8 @@ #'@param memb_dim A character string indicating the name of the member dimension #' to compute the ensemble mean; it should be set to NULL if the parameter 'exp' #' is already the ensemble mean. The default value is NULL. +#'@param na.rm A logical value indicating if NAs should be removed (TRUE) or +#' kept (FALSE) for computation. The default value is FALSE. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -33,11 +35,68 @@ #' #'@import multiApply #'@export - Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, ncores = NULL) { - ## Checks - + # Check inputs + ## exp and obs (1) + if (!is.array(exp) | !is.numeric(exp)) + stop('Parameter "exp" must be a numeric array.') + if (!is.array(obs) | !is.numeric(obs)) + stop('Parameter "obs" must be a numeric array.') + if(any(is.null(names(dim(exp))))| any(nchar(names(dim(exp))) == 0) | + any(is.null(names(dim(obs))))| any(nchar(names(dim(obs))) == 0)) { + stop("Parameter 'exp' and 'obs' must have dimension names.") + } + ## time_dim + if (!is.character(time_dim) | length(time_dim) != 1) + stop('Parameter "time_dim" must be a character string.') + if (!time_dim %in% names(dim(exp)) | !time_dim %in% names(dim(obs))) { + stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") + } + ## memb_dim + if (!is.character(memb_dim) | length(memb_dim) > 1) { + stop("Parameter 'memb_dim' must be a character string.") + } + if (!memb_dim %in% names(dim(exp))) { + stop("Parameter 'memb_dim' is not found in 'exp' dimension.") + } + ## dat_dim + # if (!is.null(dat_dim)) { + # if (!is.character(dat_dim) | length(dat_dim) > 1) { + # stop("Parameter 'dat_dim' must be a character string.") + # } + # if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + # stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + # " Set it as NULL if there is no dataset dimension.") + # } + # } + ## exp and obs (2) + name_exp <- sort(names(dim(exp))) + name_obs <- sort(names(dim(obs))) + name_exp <- name_exp[-which(name_exp == memb_dim)] + if (memb_dim %in% name_obs) { + name_obs <- name_obs[-which(name_obs == memb_dim)] + } + # if (!is.null(dat_dim)) { + # name_exp <- name_exp[-which(name_exp == dat_dim)] + # name_obs <- name_obs[-which(name_obs == dat_dim)] + # } + if (!identical(length(name_exp), length(name_obs)) | + !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { + stop(paste0("Parameter 'exp' and 'obs' must have same length of ", + "all dimensions except 'memb_dim'")) # and 'dat_dim'.")) + } + ## na.rm + if (!is.logical(na.rm) | length(na.rm) > 1) { + stop("Parameter 'na.rm' must be one logical value.") + } + ## ncores + if (!is.null(ncores)) { + if (!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 | + length(ncores) > 1) { + stop("Parameter 'ncores' must be either NULL or a positive integer.") + } + } ## Ensemble mean if (!is.null(memb_dim)) { @@ -56,8 +115,6 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, n } .Bias <- function(exp, obs) { - bias <- exp - obs - return(bias) } diff --git a/R/BiasSS.R b/R/BiasSS.R index e59189b..d456e8f 100644 --- a/R/BiasSS.R +++ b/R/BiasSS.R @@ -30,6 +30,8 @@ #'@param memb_dim A character string indicating the name of the member dimension #' to compute the ensemble mean; it should be set to NULL if the parameter 'exp' #' is already the ensemble mean. The default value is NULL. +#'@param na.rm A logical value indicating if NAs should be removed (TRUE) or +#' kept (FALSE) for computation. The default value is FALSE. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -59,8 +61,101 @@ BiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, ncores = NULL) { - ## Checks - + # Check inputs + ## exp, obs, and ref (1) + if (!is.array(exp) | !is.numeric(exp)) { + stop("Parameter 'exp' must be a numeric array.") + } + if (!is.array(obs) | !is.numeric(obs)) { + stop("Parameter 'obs' must be a numeric array.") + } + if (any(is.null(names(dim(exp))))| any(nchar(names(dim(exp))) == 0) | + any(is.null(names(dim(obs))))| any(nchar(names(dim(obs))) == 0)) { + stop("Parameter 'exp' and 'obs' must have dimension names.") + } + if (!is.null(ref)) { + if (!is.array(ref) | !is.numeric(ref)) + stop("Parameter 'ref' must be a numeric array.") + if (any(is.null(names(dim(ref))))| any(nchar(names(dim(ref))) == 0)) { + stop("Parameter 'ref' must have dimension names.") + } + } + ## time_dim + if (!is.character(time_dim) | length(time_dim) != 1) { + stop("Parameter 'time_dim' must be a character string.") + } + if (!time_dim %in% names(dim(exp)) | !time_dim %in% names(dim(obs))) { + stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") + } + if (!is.null(ref) & !time_dim %in% names(dim(ref))) { + stop("Parameter 'time_dim' is not found in 'ref' dimension.") + } + ## memb_dim + if (!is.character(memb_dim) | length(memb_dim) > 1) { + stop("Parameter 'memb_dim' must be a character string.") + } + if (!memb_dim %in% names(dim(exp))) { + stop("Parameter 'memb_dim' is not found in 'exp' dimension.") + } + if (!is.null(ref) & !memb_dim %in% names(dim(ref))) { + stop("Parameter 'memb_dim' is not found in 'ref' dimension.") + } + ## dat_dim + # if (!is.null(dat_dim)) { + # if (!is.character(dat_dim) | length(dat_dim) > 1) { + # stop("Parameter 'dat_dim' must be a character string.") + # } + # if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + # stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + # " Set it as NULL if there is no dataset dimension.") + # } + # } + ## exp, obs, and ref (2) + name_exp <- sort(names(dim(exp))) + name_obs <- sort(names(dim(obs))) + name_exp <- name_exp[-which(name_exp == memb_dim)] + if (memb_dim %in% name_obs) { + name_obs <- name_obs[-which(name_obs == memb_dim)] + } + # if (!is.null(dat_dim)) { + # name_exp <- name_exp[-which(name_exp == dat_dim)] + # name_obs <- name_obs[-which(name_obs == dat_dim)] + # } + if (!identical(length(name_exp), length(name_obs)) | + !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { + stop(paste0("Parameter 'exp' and 'obs' must have same length of ", + "all dimensions except 'memb_dim'")) # and 'dat_dim'.")) + } + if (!is.null(ref)) { + name_ref <- sort(names(dim(ref))) + name_ref <- name_ref[-which(name_ref == memb_dim)] + # if (!is.null(dat_dim)) { + # if (dat_dim %in% name_ref) { + # if (!identical(dim(exp)[dat_dim], dim(ref)[dat_dim])) { + # stop(paste0("If parameter 'ref' has dataset dimension, it must be", + # " equal to dataset dimension of 'exp'.")) + # } + # name_ref <- name_ref[-which(name_ref == dat_dim)] + # } + # } + if (!identical(length(name_exp), length(name_ref)) | + !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { + stop(paste0("Parameter 'exp' and 'ref' must have the same length of ", + "all dimensions except 'memb_dim'")) #, + # " and 'dat_dim' if there is only one reference dataset.")) + } + } + ## na.rm + if (!is.logical(na.rm) | length(na.rm) > 1) { + stop("Parameter 'na.rm' must be one logical value.") + } + ## ncores + if (!is.null(ncores)) { + if (!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 | + length(ncores) > 1) { + stop("Parameter 'ncores' must be either NULL or a positive integer.") + } + } ## Ensemble mean if (!is.null(memb_dim)) { -- GitLab From 831ef2d316c7f5245a273f453bcef23a28a6abf1 Mon Sep 17 00:00:00 2001 From: aho Date: Wed, 31 Aug 2022 17:17:03 +0200 Subject: [PATCH 094/140] Change param 'type' to 'test.type'; amend unit tests --- R/DiffCorr.R | 41 +++++++++++++++++++--------------- man/DiffCorr.Rd | 10 ++++++--- tests/testthat/test-DiffCorr.R | 26 ++++++++++++++------- 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/R/DiffCorr.R b/R/DiffCorr.R index 0775106..f4ff2a9 100644 --- a/R/DiffCorr.R +++ b/R/DiffCorr.R @@ -38,11 +38,12 @@ #' steps with no missing values in all "exp", "ref", and "obs" will be used. If #' "na.fail", an error will arise if any of "exp", "ref", or "obs" contains any #' NA. The default value is "return.na". -#'@param type A character string indicating the type of test. It can be "two-sided" -#' (to assess whether the skill of "exp" and "ref" are significantly different), -#' "one-sided-higher" (to assess whether the skill of "exp" is significantly higher -#' than that of "ref"), or "one-sided-lower" (to assess whether the skill of "exp" -#' is significantly lower than that of "ref"). The default value is "two-sided". +#'@param test.type A character string indicating the type of significance test. +#' It can be "two-sided" (to assess whether the skill of "exp" and "ref" are +#' significantly different), "one-sided-higher" (to assess whether the skill of +#' "exp" is significantly higher than that of "ref"), or "one-sided-lower" (to +#' assess whether the skill of "exp" is significantly lower than that of +#' "ref"). The default value is "two-sided". #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -77,7 +78,7 @@ #'@export DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', memb_dim = NULL, method = 'pearson', alpha = NULL, - handle.na = 'return.na', type = "two-sided", ncores = NULL) { + handle.na = 'return.na', test.type = "two-sided", ncores = NULL) { # Check inputs ## exp, ref, and obs (1) @@ -148,10 +149,13 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', if (!handle.na %in% c('return.na', 'only.complete.triplets', 'na.fail')) { stop('Parameter "handle.na" must be "return.na", "only.complete.triplets" or "na.fail".') } - ## type - if (!type %in% c('two-sided','one-sided-higher','one-sided-lower')) { - stop("Parameter 'type' must be 'two-sided', 'one-sided-higher' or 'one-sided-lower'.") + ## test.type + if (!test.type %in% c('two-sided', 'one-sided-higher', 'one-sided-lower')) { + stop("Parameter 'test.type' must be 'two-sided', 'one-sided-higher' or 'one-sided-lower'.") } + #NOTE: warning can be removed in the next release + warning("The significance test has changed after s2dv_1.2.0. The default method is 'two-sided' and the one-sided test is distinguished into the higher or lower side.") + ## ncores if (!is.null(ncores)) { if (any(!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 | @@ -195,7 +199,7 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', output_dims = output_dims, fun = .DiffCorr, method = method, alpha = alpha, handle.na = handle.na, - type = type, ncores = ncores) + test.type = test.type, ncores = ncores) } else { output <- Apply(data = list(exp = exp, obs = obs, ref = ref), target_dims = list(exp = time_dim, obs = time_dim, @@ -203,15 +207,16 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', output_dims = output_dims, N.eff = N.eff, fun = .DiffCorr, method = method, alpha = alpha, handle.na = handle.na, - type = type, ncores = ncores) + test.type = test.type, ncores = ncores) } return(output) } -.DiffCorr <- function(exp, obs, ref, N.eff = NA, method = 'pearson', alpha = NULL, handle.na = 'return.na', type = 'two.sided') { +.DiffCorr <- function(exp, obs, ref, N.eff = NA, method = 'pearson', alpha = NULL, + handle.na = 'return.na', test.type = 'two.sided') { - .diff.corr <- function(exp, obs, ref, method = 'pearson', N.eff = NA, alpha = NULL, type = 'two.sided') { + .diff.corr <- function(exp, obs, ref, method = 'pearson', N.eff = NA, alpha = NULL, test.type = 'two.sided') { # Correlation difference cor.exp <- cor(x = exp, y = obs, method = method) @@ -232,11 +237,11 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', if (is.null(alpha)) { output$p.val <- p.value } else { - if (type == 'two-sided') { + if (test.type == 'two-sided') { output$sign <- ifelse(!is.na(p.value) & p.value <= alpha/2, TRUE, FALSE) - } else if (type == 'one-sided-higher') { + } else if (test.type == 'one-sided-higher') { output$sign <- ifelse(!is.na(p.value) & p.value <= alpha & output$diff.corr > 0, TRUE, FALSE) - } else if (type == 'one-sided-lower') { + } else if (test.type == 'one-sided-lower') { output$sign <- ifelse(!is.na(p.value) & p.value <= alpha & output$diff.corr < 0, TRUE, FALSE) } } @@ -255,7 +260,7 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', ref <- ref[!nna] output <- .diff.corr(exp = exp, obs = obs, ref = ref, method = method, - N.eff = N.eff, alpha = alpha, type = type) + N.eff = N.eff, alpha = alpha, test.type = test.type) } else if (handle.na == 'return.na') { # Data contain NA, return NAs directly without passing to .diff.corr @@ -268,7 +273,7 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', } else { ## There is no NA output <- .diff.corr(exp = exp, obs = obs, ref = ref, method = method, - N.eff = N.eff, alpha = alpha, type = type) + N.eff = N.eff, alpha = alpha, test.type = test.type) } return(output) } diff --git a/man/DiffCorr.Rd b/man/DiffCorr.Rd index ca16cd4..36b2fb9 100644 --- a/man/DiffCorr.Rd +++ b/man/DiffCorr.Rd @@ -14,7 +14,7 @@ DiffCorr( method = "pearson", alpha = NULL, handle.na = "return.na", - two.sided = FALSE, + test.type = "two-sided", ncores = NULL ) } @@ -57,8 +57,12 @@ steps with no missing values in all "exp", "ref", and "obs" will be used. If "na.fail", an error will arise if any of "exp", "ref", or "obs" contains any NA. The default value is "return.na".} -\item{two.sided}{A logical indicating whether to perform a two-sided test. If -FALSE, a one.sided test is performed. The default value is FALSE.} +\item{test.type}{A character string indicating the type of significance test. +It can be "two-sided" (to assess whether the skill of "exp" and "ref" are +significantly different), "one-sided-higher" (to assess whether the skill of +"exp" is significantly higher than that of "ref"), or "one-sided-lower" (to +assess whether the skill of "exp" is significantly lower than that of +"ref"). The default value is "two-sided".} \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} diff --git a/tests/testthat/test-DiffCorr.R b/tests/testthat/test-DiffCorr.R index 9a087fc..60ac7d4 100644 --- a/tests/testthat/test-DiffCorr.R +++ b/tests/testthat/test-DiffCorr.R @@ -89,14 +89,14 @@ test_that("1. Input checks", { DiffCorr(exp2, obs2, ref2, handle.na = TRUE), 'Parameter "handle.na" must be "return.na", "only.complete.triplets" or "na.fail".' ) - # two.sided + # test.type expect_error( - DiffCorr(exp2, obs2, ref2, two.sided = 2), - "Parameter 'two.sided' must be TRUE or FALSE." + DiffCorr(exp2, obs2, ref2, test.type = "two.sided"), + "Parameter 'test.type' must be 'two-sided', 'one-sided-higher' or 'one-sided-lower'." ) # ncores expect_error( - DiffCorr(exp2, obs2, ref2, ncores = 1.5), + suppressWarnings(DiffCorr(exp2, obs2, ref2, ncores = 1.5)), 'Parameter "ncores" must be either NULL or a positive integer.' ) @@ -104,6 +104,8 @@ test_that("1. Input checks", { ############################################## test_that("2. Output checks: dat1", { +suppressWarnings({ + expect_equal( names(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb')), c("diff.corr", "p.val") @@ -136,15 +138,19 @@ c(0.27347087, 0.50556882, 0.08855968, 0.24199701, 0.22935182, 0.88336336), tolerance = 0.0001 ) expect_equal( -as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05)$diff.corr), -as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, two.sided = T)$diff.corr) +as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, test.type = "one-sided-higher")$diff.corr), +as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, test.type = "two-sided")$diff.corr) ) expect_equal( as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05)$sign), rep(FALSE, 6) ) expect_equal( -as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, two.sided = T)$sign), +as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, test.type = "one-sided-higher")$sign), +rep(FALSE, 6) +) +expect_equal( +as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, test.type = "one-sided-lower")$sign), rep(FALSE, 6) ) expect_equal( @@ -194,12 +200,15 @@ DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', handle.na = 'only.complete.triplet "There is no complete set of forecasts and observations." ) - +}) # suppressWarnings }) ############################################## test_that("3. Output checks: dat2", { + +suppressWarnings({ + expect_equal( names(DiffCorr(exp2, obs2, ref2)), c("diff.corr", "p.val") @@ -223,4 +232,5 @@ DiffCorr(exp2, obs2, ref2)$diff, tolerance = 0.0001 ) +}) # suppressWarnings }) -- GitLab From 144d56ae10c62b70ad13a89db2f831037b1cac20 Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Mon, 5 Sep 2022 13:32:58 +0200 Subject: [PATCH 095/140] Z-test for two-sided --- R/DiffCorr.R | 72 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/R/DiffCorr.R b/R/DiffCorr.R index f4ff2a9..277e4d2 100644 --- a/R/DiffCorr.R +++ b/R/DiffCorr.R @@ -40,10 +40,10 @@ #' NA. The default value is "return.na". #'@param test.type A character string indicating the type of significance test. #' It can be "two-sided" (to assess whether the skill of "exp" and "ref" are -#' significantly different), "one-sided-higher" (to assess whether the skill of -#' "exp" is significantly higher than that of "ref"), or "one-sided-lower" (to -#' assess whether the skill of "exp" is significantly lower than that of -#' "ref"). The default value is "two-sided". +#' significantly different with a z-test on Fisher z-transformed correlation +#' coefficients) or "one-sided" (to assess whether the skill of "exp" is +#' significantly higher than that of "ref" with the test described in +#' Steiger, 1980). The default value is "two-sided". #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -150,11 +150,11 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', stop('Parameter "handle.na" must be "return.na", "only.complete.triplets" or "na.fail".') } ## test.type - if (!test.type %in% c('two-sided', 'one-sided-higher', 'one-sided-lower')) { - stop("Parameter 'test.type' must be 'two-sided', 'one-sided-higher' or 'one-sided-lower'.") + if (!test.type %in% c('two-sided', 'one-sided')) { + stop("Parameter 'test.type' must be 'two-sided' or 'one-sided'.") } #NOTE: warning can be removed in the next release - warning("The significance test has changed after s2dv_1.2.0. The default method is 'two-sided' and the one-sided test is distinguished into the higher or lower side.") + warning("The default significance test has changed after s2dv_1.2.0. The default method is 'two-sided'.") ## ncores if (!is.null(ncores)) { @@ -216,7 +216,7 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', .DiffCorr <- function(exp, obs, ref, N.eff = NA, method = 'pearson', alpha = NULL, handle.na = 'return.na', test.type = 'two.sided') { - .diff.corr <- function(exp, obs, ref, method = 'pearson', N.eff = NA, alpha = NULL, test.type = 'two.sided') { + .diff.corr_one.sided <- function(exp, obs, ref, method = 'pearson', N.eff = NA, alpha = NULL) { # Correlation difference cor.exp <- cor(x = exp, y = obs, method = method) @@ -233,21 +233,40 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', } R <- (1 - r12 * r12 - r13 * r13 - r23 * r23) + 2 * r12 * r13 * r23 t <- (r12 - r13) * sqrt((N.eff - 1) * (1 + r23) / (2 * ((N.eff - 1) / (N.eff - 3)) * R + 0.25 * (r12 + r13)^2 * (1 - r23)^3)) - p.value <- 1 - pt(t, df = N.eff - 3) + p.value <- pt(t, df = N.eff - 3, lower.tail = FALSE) if (is.null(alpha)) { output$p.val <- p.value } else { - if (test.type == 'two-sided') { - output$sign <- ifelse(!is.na(p.value) & p.value <= alpha/2, TRUE, FALSE) - } else if (test.type == 'one-sided-higher') { - output$sign <- ifelse(!is.na(p.value) & p.value <= alpha & output$diff.corr > 0, TRUE, FALSE) - } else if (test.type == 'one-sided-lower') { - output$sign <- ifelse(!is.na(p.value) & p.value <= alpha & output$diff.corr < 0, TRUE, FALSE) - } + output$sign <- ifelse(!is.na(p.value) & p.value <= alpha & output$diff.corr > 0, TRUE, FALSE) } return(output) } - + + .diff.corr_two.sided <- function(exp, obs, ref, method = 'pearson', N.eff = NA, alpha = NULL) { + + # Correlation difference + cor.exp <- cor(x = exp, y = obs, method = method) + cor.ref <- cor(x = ref, y = obs, method = method) + output <- NULL + output$diff.corr <- cor.exp - cor.ref + + # Significance with two-sided z-test on Fisher z-transformed correlation coefficients (Hinkel et al, 1988) + if (is.na(N.eff)) { + N.eff <- .Eno(x = obs, na.action = na.pass) ## effective degrees of freedom + } + Z <- abs(0.5*log((1+cor.exp)/(1-cor.exp)) - 0.5*log((1+cor.ref)/(1-cor.ref))) / sqrt(1/(N.eff-3) + 1/(N.eff-3)) + # sign <- Z >= qnorm(p = alpha/2, lower.tail = FALSE) + p.value <- pnorm(q = Z, lower.tail = FALSE) + + if (is.null(alpha)) { + output$p.val <- p.value + } else { + output$sign <- ifelse(!is.na(p.value) & p.value <= alpha/2, TRUE, FALSE) + } + + return(output) + } + #================================================== if (anyNA(exp) | anyNA(obs) | anyNA(ref)) { ## There are NAs @@ -259,8 +278,13 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', obs <- obs[!nna] ref <- ref[!nna] - output <- .diff.corr(exp = exp, obs = obs, ref = ref, method = method, - N.eff = N.eff, alpha = alpha, test.type = test.type) + if (identical(test.type,'two-sided')){ + output <- .diff.corr_two.sided(exp = exp, obs = obs, ref = ref, method = method, + N.eff = N.eff, alpha = alpha) + } else if (identical(test.type,'one-sided')){ + output <- .diff.corr_one.sided(exp = exp, obs = obs, ref = ref, method = method, + N.eff = N.eff, alpha = alpha) + } } else if (handle.na == 'return.na') { # Data contain NA, return NAs directly without passing to .diff.corr @@ -272,8 +296,14 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', } } else { ## There is no NA - output <- .diff.corr(exp = exp, obs = obs, ref = ref, method = method, - N.eff = N.eff, alpha = alpha, test.type = test.type) + + if (identical(test.type,'two-sided')){ + output <- .diff.corr_two.sided(exp = exp, obs = obs, ref = ref, method = method, + N.eff = N.eff, alpha = alpha) + } else if (identical(test.type,'one-sided')){ + output <- .diff.corr_one.sided(exp = exp, obs = obs, ref = ref, method = method, + N.eff = N.eff, alpha = alpha) + } } return(output) } -- GitLab From 73dfed032efb96e9b3aa8e2c0d767703255f3943 Mon Sep 17 00:00:00 2001 From: aho Date: Mon, 5 Sep 2022 18:17:27 +0200 Subject: [PATCH 096/140] Refine doc and checks for memb_dim --- NAMESPACE | 2 ++ R/Bias.R | 51 ++++++++++++++++++++------------ R/BiasSS.R | 78 ++++++++++++++++++++++++++++--------------------- man/Bias.Rd | 57 ++++++++++++++++++++++++++++++++++++ man/BiasSS.Rd | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 217 insertions(+), 52 deletions(-) create mode 100644 man/Bias.Rd create mode 100644 man/BiasSS.Rd diff --git a/NAMESPACE b/NAMESPACE index 032ebf9..6b21447 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,8 @@ export(AMV) export(AnimateMap) export(Ano) export(Ano_CrossValid) +export(Bias) +export(BiasSS) export(BrierScore) export(CDORemap) export(Clim) diff --git a/R/Bias.R b/R/Bias.R index 15c3f4e..f731484 100644 --- a/R/Bias.R +++ b/R/Bias.R @@ -3,8 +3,9 @@ #'The Mean Bias or Mean Error (Wilks, 2011) is defined as the mean difference #'between the ensemble mean forecast and the observations. It is a deterministic #'metric. Positive values indicate that the forecasts are on average too high -#'and negative values indicate that the forecasts are on average too low; however, -#'it gives no information about the typical magnitude of individual forecast errors. +#'and negative values indicate that the forecasts are on average too low. +#'However, it gives no information about the typical magnitude of individual +#'forecast errors. #' #'@param exp A named numerical array of the forecast with at least time #' dimension. @@ -22,8 +23,8 @@ #' computation. The default value is NULL. #' #'@return -#'A numerical array of Bias with dimensions the dimensions of -#''exp' except 'time_dim' and 'memb_dim' dimensions. +#'A numerical array of bias with dimensions of 'exp' except 'time_dim' and +#''memb_dim' dimensions. #' #'@references #'Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 @@ -34,15 +35,16 @@ #'bias <- Bias(exp = exp, obs = obs, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) #' #'@import multiApply +#'@importFrom ClimProjDiags Subset #'@export Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, ncores = NULL) { # Check inputs ## exp and obs (1) if (!is.array(exp) | !is.numeric(exp)) - stop('Parameter "exp" must be a numeric array.') + stop("Parameter 'exp' must be a numeric array.") if (!is.array(obs) | !is.numeric(obs)) - stop('Parameter "obs" must be a numeric array.') + stop("Parameter 'obs' must be a numeric array.") if(any(is.null(names(dim(exp))))| any(nchar(names(dim(exp))) == 0) | any(is.null(names(dim(obs))))| any(nchar(names(dim(obs))) == 0)) { stop("Parameter 'exp' and 'obs' must have dimension names.") @@ -54,11 +56,21 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, n stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } ## memb_dim - if (!is.character(memb_dim) | length(memb_dim) > 1) { - stop("Parameter 'memb_dim' must be a character string.") - } - if (!memb_dim %in% names(dim(exp))) { - stop("Parameter 'memb_dim' is not found in 'exp' dimension.") + if (!is.null(memb_dim)) { + if (!is.character(memb_dim) | length(memb_dim) > 1) { + stop("Parameter 'memb_dim' must be a character string.") + } + if (!memb_dim %in% names(dim(exp))) { + stop("Parameter 'memb_dim' is not found in 'exp' dimension.") + } + if (memb_dim %in% names(dim(obs))) { + if (identical(as.numeric(dim(obs)[memb_dim]), 1)) { + obs <- ClimProjDiags::Subset(x = obs, along = memb_dim, indices = 1, drop = 'selected') + } else { + stop("Not implemented for observations with members ('obs' can have 'memb_dim', ", + "but it should be of length = 1).") + } + } } ## dat_dim # if (!is.null(dat_dim)) { @@ -73,9 +85,8 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, n ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) - name_exp <- name_exp[-which(name_exp == memb_dim)] - if (memb_dim %in% name_obs) { - name_obs <- name_obs[-which(name_obs == memb_dim)] + if (!is.null(memb_dim)) { + name_exp <- name_exp[-which(name_exp == memb_dim)] } # if (!is.null(dat_dim)) { # name_exp <- name_exp[-which(name_exp == dat_dim)] @@ -97,17 +108,19 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, n stop("Parameter 'ncores' must be either NULL or a positive integer.") } } - + + ############################### + ## Ensemble mean if (!is.null(memb_dim)) { exp <- MeanDims(exp, memb_dim, na.rm = na.rm) } ## Mean bias - bias <- multiApply::Apply(data = list(exp, obs), - target_dims = time_dim, - fun = .Bias, - ncores = ncores)$output1 + bias <- Apply(data = list(exp, obs), + target_dims = time_dim, + fun = .Bias, + ncores = ncores)$output1 ## Return the mean bias bias <- MeanDims(bias, time_dim, na.rm = na.rm) diff --git a/R/BiasSS.R b/R/BiasSS.R index d456e8f..ee748ed 100644 --- a/R/BiasSS.R +++ b/R/BiasSS.R @@ -1,17 +1,17 @@ #'Compute the Mean Bias Skill Score #' -#'The Mean Bias Skill Score is based on the Mean Error (Wilks, 2011) -#'between the ensemble mean forecast and the observations. It measures -#'the accuracy of the forecast in comparison with a reference forecast to assess -#'whether the forecast presents an improvement or a worsening with respect to -#'that reference. The Mean Bias Skill Score ranges between minus infinite and 1. -#'Positive values indicate that the forecast has higher skill than the -#'reference forecast, while negative values indicate that it has a lower skill. -#'Examples of reference forecasts are the climatological forecast (average of -#'the observations), a previous model version, or another model. It is computed -#'as BiasSS = 1 - Bias_exp / Bias_ref. The statistical significance is obtained -#'based on a Random Walk test at the 95% confidence level (DelSole and Tippett, -#'2016). +#'The Mean Bias Skill Score is based on the Mean Error (Wilks, 2011) between the +#'ensemble mean forecast and the observations. It measures the accuracy of the +#'forecast in comparison with a reference forecast to assess whether the +#'forecast presents an improvement or a worsening with respect to that +#'reference. The Mean Bias Skill Score ranges between minus infinite and 1. +#'Positive values indicate that the forecast has higher skill than the reference +#'forecast, while negative values indicate that it has a lower skill. Examples +#'of reference forecasts are the climatological forecast (average of the +#'observations), a previous model version, or another model. It is computed as +#'\code{BiasSS = 1 - Bias_exp / Bias_ref}. The statistical significance is +#'obtained based on a Random Walk test at the 95% confidence level (DelSole and +#'Tippett, 2016). #' #'@param exp A named numerical array of the forecast with at least time #' dimension. @@ -19,17 +19,17 @@ #' dimension. The dimensions must be the same as 'exp' except 'memb_dim' and #' 'dat_dim'. #'@param ref A named numerical array of the reference forecast data with at -#' least time and member dimension. The dimensions must be the same as 'exp' -#' except 'memb_dim' and 'dat_dim'. If there is only one reference dataset, -#' it should not have dataset dimension. If there is corresponding reference -#' for each experiement, the dataset dimension must have the same length as in -#' 'exp'. If 'ref' is NULL, the climatological forecast is used as reference -#' forecast. The default value is NULL. +#' least time dimension. The dimensions must be the same as 'exp' except +#' 'memb_dim' and 'dat_dim'. If there is only one reference dataset, it should +#' not have dataset dimension. If there is corresponding reference for each +#' experiement, the dataset dimension must have the same length as in 'exp'. If +#' 'ref' is NULL, the climatological forecast is used as reference forecast. +#' The default value is NULL. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. #'@param memb_dim A character string indicating the name of the member dimension #' to compute the ensemble mean; it should be set to NULL if the parameter 'exp' -#' is already the ensemble mean. The default value is NULL. +#' and 'ref' are already the ensemble mean. The default value is NULL. #'@param na.rm A logical value indicating if NAs should be removed (TRUE) or #' kept (FALSE) for computation. The default value is FALSE. #'@param ncores An integer indicating the number of cores to use for parallel @@ -91,14 +91,21 @@ BiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na stop("Parameter 'time_dim' is not found in 'ref' dimension.") } ## memb_dim - if (!is.character(memb_dim) | length(memb_dim) > 1) { - stop("Parameter 'memb_dim' must be a character string.") - } - if (!memb_dim %in% names(dim(exp))) { - stop("Parameter 'memb_dim' is not found in 'exp' dimension.") - } - if (!is.null(ref) & !memb_dim %in% names(dim(ref))) { - stop("Parameter 'memb_dim' is not found in 'ref' dimension.") + if (!is.null(memb_dim)) { + if (!is.character(memb_dim) | length(memb_dim) > 1) { + stop("Parameter 'memb_dim' must be a character string.") + } + if (!memb_dim %in% names(dim(exp))) { + stop("Parameter 'memb_dim' is not found in 'exp' dimension.") + } + if (memb_dim %in% names(dim(obs))) { + if (identical(as.numeric(dim(obs)[memb_dim]), 1)) { + obs <- ClimProjDiags::Subset(x = obs, along = memb_dim, indices = 1, drop = 'selected') + } else { + stop("Not implemented for observations with members ('obs' can have 'memb_dim', ", + "but it should be of length = 1).") + } + } } ## dat_dim # if (!is.null(dat_dim)) { @@ -113,9 +120,8 @@ BiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na ## exp, obs, and ref (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) - name_exp <- name_exp[-which(name_exp == memb_dim)] - if (memb_dim %in% name_obs) { - name_obs <- name_obs[-which(name_obs == memb_dim)] + if (!is.null(memb_dim)) { + name_exp <- name_exp[-which(name_exp == memb_dim)] } # if (!is.null(dat_dim)) { # name_exp <- name_exp[-which(name_exp == dat_dim)] @@ -128,7 +134,9 @@ BiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na } if (!is.null(ref)) { name_ref <- sort(names(dim(ref))) - name_ref <- name_ref[-which(name_ref == memb_dim)] + if (memb_dim %in% name_ref) { + name_ref <- name_ref[-which(name_ref == memb_dim)] + } # if (!is.null(dat_dim)) { # if (dat_dim %in% name_ref) { # if (!identical(dim(exp)[dat_dim], dim(ref)[dat_dim])) { @@ -156,7 +164,9 @@ BiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na stop("Parameter 'ncores' must be either NULL or a positive integer.") } } - + + ############################ + ## Ensemble mean if (!is.null(memb_dim)) { exp <- MeanDims(exp, memb_dim, na.rm = na.rm) @@ -182,7 +192,9 @@ BiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na } .BiasSS <- function(exp, obs, ref = NULL, na.rm = FALSE) { - + # exp and obs: [sdate] + # ref: [sdate] or NULL + if (isTRUE(na.rm)) { if (is.null(ref)) { good_values <- !is.na(exp) & !is.na(obs) diff --git a/man/Bias.Rd b/man/Bias.Rd new file mode 100644 index 0000000..a58d263 --- /dev/null +++ b/man/Bias.Rd @@ -0,0 +1,57 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Bias.R +\name{Bias} +\alias{Bias} +\title{Compute the Mean Bias} +\usage{ +Bias( + exp, + obs, + time_dim = "sdate", + memb_dim = NULL, + na.rm = FALSE, + ncores = NULL +) +} +\arguments{ +\item{exp}{A named numerical array of the forecast with at least time +dimension.} + +\item{obs}{A named numerical array of the observation with at least time +dimension. The dimensions must be the same as 'exp' except 'memb_dim' and +'dat_dim'.} + +\item{time_dim}{A character string indicating the name of the time dimension. +The default value is 'sdate'.} + +\item{memb_dim}{A character string indicating the name of the member dimension +to compute the ensemble mean; it should be set to NULL if the parameter 'exp' +is already the ensemble mean. The default value is NULL.} + +\item{na.rm}{A logical value indicating if NAs should be removed (TRUE) or +kept (FALSE) for computation. The default value is FALSE.} + +\item{ncores}{An integer indicating the number of cores to use for parallel +computation. The default value is NULL.} +} +\value{ +A numerical array of bias with dimensions of 'exp' except 'time_dim' and +'memb_dim' dimensions. +} +\description{ +The Mean Bias or Mean Error (Wilks, 2011) is defined as the mean difference +between the ensemble mean forecast and the observations. It is a deterministic +metric. Positive values indicate that the forecasts are on average too high +and negative values indicate that the forecasts are on average too low. +However, it gives no information about the typical magnitude of individual +forecast errors. +} +\examples{ +exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) +obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, year = 50)) +bias <- Bias(exp = exp, obs = obs, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) + +} +\references{ +Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 +} diff --git a/man/BiasSS.Rd b/man/BiasSS.Rd new file mode 100644 index 0000000..bce496e --- /dev/null +++ b/man/BiasSS.Rd @@ -0,0 +1,81 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/BiasSS.R +\name{BiasSS} +\alias{BiasSS} +\title{Compute the Mean Bias Skill Score} +\usage{ +BiasSS( + exp, + obs, + ref = NULL, + time_dim = "sdate", + memb_dim = NULL, + na.rm = FALSE, + ncores = NULL +) +} +\arguments{ +\item{exp}{A named numerical array of the forecast with at least time +dimension.} + +\item{obs}{A named numerical array of the observation with at least time +dimension. The dimensions must be the same as 'exp' except 'memb_dim' and +'dat_dim'.} + +\item{ref}{A named numerical array of the reference forecast data with at +least time dimension. The dimensions must be the same as 'exp' except +'memb_dim' and 'dat_dim'. If there is only one reference dataset, it should +not have dataset dimension. If there is corresponding reference for each +experiement, the dataset dimension must have the same length as in 'exp'. If +'ref' is NULL, the climatological forecast is used as reference forecast. +The default value is NULL.} + +\item{time_dim}{A character string indicating the name of the time dimension. +The default value is 'sdate'.} + +\item{memb_dim}{A character string indicating the name of the member dimension +to compute the ensemble mean; it should be set to NULL if the parameter 'exp' +and 'ref' are already the ensemble mean. The default value is NULL.} + +\item{na.rm}{A logical value indicating if NAs should be removed (TRUE) or +kept (FALSE) for computation. The default value is FALSE.} + +\item{ncores}{An integer indicating the number of cores to use for parallel +computation. The default value is NULL.} +} +\value{ +\item{$biasSS}{ + A numerical array of BiasSS with dimensions the dimensions of + 'exp' except 'time_dim' and 'memb_dim' dimensions. +} +\item{$sign}{ + A logical array of the statistical significance of the BiasSS + with the same dimensions as $biasSS. +} +} +\description{ +The Mean Bias Skill Score is based on the Mean Error (Wilks, 2011) between the +ensemble mean forecast and the observations. It measures the accuracy of the +forecast in comparison with a reference forecast to assess whether the +forecast presents an improvement or a worsening with respect to that +reference. The Mean Bias Skill Score ranges between minus infinite and 1. +Positive values indicate that the forecast has higher skill than the reference +forecast, while negative values indicate that it has a lower skill. Examples +of reference forecasts are the climatological forecast (average of the +observations), a previous model version, or another model. It is computed as +\code{BiasSS = 1 - Bias_exp / Bias_ref}. The statistical significance is +obtained based on a Random Walk test at the 95% confidence level (DelSole and +Tippett, 2016). +} +\examples{ +exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) +ref <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) +obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, year = 50)) +biasSS1 <- BiasSS(exp = exp, obs = obs, ref = ref, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) +biasSS2 <- BiasSS(exp = exp, obs = obs, ref = NULL, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) + +} +\references{ +Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 +DelSole and Tippett, 2016; https://doi.org/10.1175/MWR-D-15-0218.1 +} -- GitLab From 0ffeef39a83272ab935e6f5ed86580cac5d2538d Mon Sep 17 00:00:00 2001 From: aho Date: Mon, 5 Sep 2022 18:30:49 +0200 Subject: [PATCH 097/140] Correct if condition for ref ensemble mean --- R/BiasSS.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/BiasSS.R b/R/BiasSS.R index ee748ed..87edfdd 100644 --- a/R/BiasSS.R +++ b/R/BiasSS.R @@ -170,7 +170,7 @@ BiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na ## Ensemble mean if (!is.null(memb_dim)) { exp <- MeanDims(exp, memb_dim, na.rm = na.rm) - if (!is.null(ref)) { + if (!is.null(ref) & memb_dim %in% names(dim(ref))) { ref <- MeanDims(ref, memb_dim, na.rm = na.rm) } } -- GitLab From 376749deed58305aadff19a0f219fc5abee320b5 Mon Sep 17 00:00:00 2001 From: aho Date: Tue, 6 Sep 2022 12:01:13 +0200 Subject: [PATCH 098/140] Run tests by CICD --- .Rbuildignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.Rbuildignore b/.Rbuildignore index 2814b11..67f31a7 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -10,7 +10,7 @@ README\.md$ vignettes .gitlab-ci.yml # unit tests should be ignored when building the package for CRAN -^tests$ +#^tests$ # CDO is not in windbuilder, so we can test the unit tests by winbuilder # but test-CDORemap.R needs to be hidden #tests/testthat/test-CDORemap.R -- GitLab From 13cec946d79a8fcb660baae5be96f7ef74e4d759 Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Tue, 6 Sep 2022 13:28:55 +0200 Subject: [PATCH 099/140] possibility of computing the time_mean and absolute_bias --- R/Bias.R | 48 +++++++++++++++++++++++++++++++++++------------- R/BiasSS.R | 30 +++++++++++++++--------------- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/R/Bias.R b/R/Bias.R index f731484..316efc8 100644 --- a/R/Bias.R +++ b/R/Bias.R @@ -4,8 +4,7 @@ #'between the ensemble mean forecast and the observations. It is a deterministic #'metric. Positive values indicate that the forecasts are on average too high #'and negative values indicate that the forecasts are on average too low. -#'However, it gives no information about the typical magnitude of individual -#'forecast errors. +#'It also allows to compute the Absolute Mean Bias. #' #'@param exp A named numerical array of the forecast with at least time #' dimension. @@ -19,25 +18,30 @@ #' is already the ensemble mean. The default value is NULL. #'@param na.rm A logical value indicating if NAs should be removed (TRUE) or #' kept (FALSE) for computation. The default value is FALSE. +#'@param absolute A logical value indicating whether to compute the absolute bias. +#' The default value is FALSE. +#'@param time_dim A logical value indicating whether to compute the temporal +#' mean of the bias. The default value is TRUE. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' #'@return -#'A numerical array of bias with dimensions of 'exp' except 'time_dim' and -#''memb_dim' dimensions. +#'A numerical array of bias with dimensions of 'exp' except 'time_dim' (if time_mean = T) +#'and 'memb_dim' dimensions. #' #'@references #'Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 #' #'@examples -#'exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) -#'obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, year = 50)) -#'bias <- Bias(exp = exp, obs = obs, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) +#'exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, sdate = 50)) +#'obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, sdate = 50)) +#'bias <- Bias(exp = exp, obs = obs, memb_dim = 'member') #' #'@import multiApply #'@importFrom ClimProjDiags Subset #'@export -Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, ncores = NULL) { +Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, + absolute = FALSE, time_mean = TRUE, ncores = NULL) { # Check inputs ## exp and obs (1) @@ -101,6 +105,14 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, n if (!is.logical(na.rm) | length(na.rm) > 1) { stop("Parameter 'na.rm' must be one logical value.") } + ## absolute + if (!is.logical(absolute) | length(absolute) > 1) { + stop("Parameter 'absolute' must be one logical value.") + } + ## time_mean + if (!is.logical(time_mean) | length(time_mean) > 1) { + stop("Parameter 'time_mean' must be one logical value.") + } ## ncores if (!is.null(ncores)) { if (!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 | @@ -116,18 +128,28 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, n exp <- MeanDims(exp, memb_dim, na.rm = na.rm) } - ## Mean bias + ## (Mean) Bias bias <- Apply(data = list(exp, obs), target_dims = time_dim, - fun = .Bias, + fun = .Bias, na.rm = na.rm, + absolute = absolute, + time_mean = time_mean, ncores = ncores)$output1 - ## Return the mean bias - bias <- MeanDims(bias, time_dim, na.rm = na.rm) return(bias) } -.Bias <- function(exp, obs) { +.Bias <- function(exp, obs, na.rm = FALSE, absolute = FALSE, time_mean = TRUE) { + bias <- exp - obs + + if (isTRUE(absolute)){ + bias <- abs(bias) + } + + if (isTRUE(time_mean)){ + bias <- mean(bias, na.rm = na.rm) + } + return(bias) } diff --git a/R/BiasSS.R b/R/BiasSS.R index 87edfdd..8e5da98 100644 --- a/R/BiasSS.R +++ b/R/BiasSS.R @@ -1,6 +1,6 @@ -#'Compute the Mean Bias Skill Score +#'Compute the Absolute Mean Bias Skill Score #' -#'The Mean Bias Skill Score is based on the Mean Error (Wilks, 2011) between the +#'The Absolute Mean Bias Skill Score is based on the Absolute Mean Error (Wilks, 2011) between the #'ensemble mean forecast and the observations. It measures the accuracy of the #'forecast in comparison with a reference forecast to assess whether the #'forecast presents an improvement or a worsening with respect to that @@ -9,7 +9,7 @@ #'forecast, while negative values indicate that it has a lower skill. Examples #'of reference forecasts are the climatological forecast (average of the #'observations), a previous model version, or another model. It is computed as -#'\code{BiasSS = 1 - Bias_exp / Bias_ref}. The statistical significance is +#'\code{AbsBiasSS = 1 - AbsBias_exp / AbsBias_ref}. The statistical significance is #'obtained based on a Random Walk test at the 95% confidence level (DelSole and #'Tippett, 2016). #' @@ -50,16 +50,16 @@ #'DelSole and Tippett, 2016; https://doi.org/10.1175/MWR-D-15-0218.1 #' #'@examples -#'exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) -#'ref <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) -#'obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, year = 50)) -#'biasSS1 <- BiasSS(exp = exp, obs = obs, ref = ref, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) -#'biasSS2 <- BiasSS(exp = exp, obs = obs, ref = NULL, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) +#'exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, sdate = 50)) +#'ref <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, sdate = 50)) +#'obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, sdate = 50)) +#'biasSS1 <- AbsBiasSS(exp = exp, obs = obs, ref = ref, memb_dim = 'member') +#'biasSS2 <- AbsBiasSS(exp = exp, obs = obs, ref = NULL, memb_dim = 'member') #' #'@import multiApply #'@export -BiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, ncores = NULL) { +AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, ncores = NULL) { # Check inputs ## exp, obs, and ref (1) @@ -179,19 +179,19 @@ BiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na if (is.null(ref)) { ss <- multiApply::Apply(data = list(exp, obs), target_dims = time_dim, - fun = .BiasSS, na.rm = na.rm, + fun = .AbsBiasSS, na.rm = na.rm, ref = ref, ncores = ncores) } else { ss <- multiApply::Apply(data = list(exp, obs, ref), target_dims = time_dim, - fun = .BiasSS, na.rm = na.rm, + fun = .AbsBiasSS, na.rm = na.rm, ncores = ncores) } return(ss) } -.BiasSS <- function(exp, obs, ref = NULL, na.rm = FALSE) { +.AbsBiasSS <- function(exp, obs, ref = NULL, na.rm = FALSE) { # exp and obs: [sdate] # ref: [sdate] or NULL @@ -209,17 +209,17 @@ BiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na } ## Bias of the exp - bias_exp <- .Bias(exp = exp, obs = obs) + bias_exp <- .Bias(exp = exp, obs = obs, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) ## Bias of the ref if (is.null(ref)) { ## Climatological forecast ref <- mean(obs, na.rm = na.rm) } - bias_ref <- .Bias(exp = ref, obs = obs) + bias_ref <- .Bias(exp = ref, obs = obs, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) ## Skill score and significance ss <- list() - ss$biasSS <- 1 - mean(abs(bias_exp), na.rm = na.rm) / mean(abs(bias_ref), na.rm = na.rm) + ss$biasSS <- 1 - bias_exp / bias_ref ss$sign <- .RandomWalkTest(skill_A = bias_exp, skill_B = bias_ref)$signif return(ss) -- GitLab From eb376236ce9f8269a87f8bec364b0e5192f0f07d Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Tue, 6 Sep 2022 13:38:02 +0200 Subject: [PATCH 100/140] renamed to AbsBiasSS --- R/{BiasSS.R => AbsBiasSS.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename R/{BiasSS.R => AbsBiasSS.R} (100%) diff --git a/R/BiasSS.R b/R/AbsBiasSS.R similarity index 100% rename from R/BiasSS.R rename to R/AbsBiasSS.R -- GitLab From b1e37dc4dc0d2a93d672da93b88df7accdca403f Mon Sep 17 00:00:00 2001 From: aho Date: Tue, 6 Sep 2022 15:01:28 +0200 Subject: [PATCH 101/140] Add bigmemory:: to describe() to avoid overwritten by testthat::describe --- R/Load.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/Load.R b/R/Load.R index e188299..c5d4111 100644 --- a/R/Load.R +++ b/R/Load.R @@ -1866,12 +1866,12 @@ Load <- function(var, exp = NULL, obs = NULL, sdates, nmember = NULL, if (!is.null(dim_exp) && (length(unlist(dim_exp)) == length(dim_exp)) && !anyNA(unlist(dim_exp)) && !any(unlist(dim_exp) == 0)) { var_exp <- big.matrix(nrow = prod(unlist(dim_exp)), ncol = 1) - pointer_var_exp <- describe(var_exp) + pointer_var_exp <- bigmemory::describe(var_exp) } if (!is.null(dim_obs) && (length(unlist(dim_obs)) == length(dim_obs)) && !anyNA(unlist(dim_obs)) && !any(unlist(dim_obs) == 0)) { var_obs <- big.matrix(nrow = prod(unlist(dim_obs)), ncol = 1) - pointer_var_obs <- describe(var_obs) + pointer_var_obs <- bigmemory::describe(var_obs) } if (is.null(nprocs)) { nprocs <- detectCores() -- GitLab From c8831856f732e703237bd013fffdff29f2bfe319 Mon Sep 17 00:00:00 2001 From: aho Date: Tue, 6 Sep 2022 15:48:47 +0200 Subject: [PATCH 102/140] Improve condition statement --- R/Load.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/Load.R b/R/Load.R index c5d4111..cca99bf 100644 --- a/R/Load.R +++ b/R/Load.R @@ -1545,7 +1545,7 @@ Load <- function(var, exp = NULL, obs = NULL, sdates, nmember = NULL, # If there are no experiments to load we need to choose a number of time steps # to load from observational datasets. We load from the first start date to # the current date. - if (is.null(exp) || dims == 0) { + if (is.null(exp) || identical(dims, 0)) { if (is.null(leadtimemax)) { diff <- Sys.time() - as.POSIXct(sdates[1], format = '%Y%m%d', tz = "UTC") if (diff > 0) { -- GitLab From 2d33532bf37b68c6701f6d74ce4565dcb4c8f87b Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 7 Sep 2022 17:14:52 +0200 Subject: [PATCH 103/140] Added dat_dim param, test-Bias and documentation --- NAMESPACE | 2 +- R/Bias.R | 103 ++++++++++++------ man/{BiasSS.Rd => AbsBiasSS.Rd} | 24 ++--- man/Bias.Rd | 33 ++++-- man/s2dv-package.Rd | 10 +- man/sampleDepthData.Rd | 6 +- man/sampleMap.Rd | 6 +- man/sampleTimeSeries.Rd | 6 +- tests/testthat/test-Bias.R | 182 ++++++++++++++++++++++++++++++++ 9 files changed, 300 insertions(+), 72 deletions(-) rename man/{BiasSS.Rd => AbsBiasSS.Rd} (81%) create mode 100644 tests/testthat/test-Bias.R diff --git a/NAMESPACE b/NAMESPACE index 6b21447..33fd850 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,11 +2,11 @@ export(ACC) export(AMV) +export(AbsBiasSS) export(AnimateMap) export(Ano) export(Ano_CrossValid) export(Bias) -export(BiasSS) export(BrierScore) export(CDORemap) export(Clim) diff --git a/R/Bias.R b/R/Bias.R index 316efc8..f7d6e31 100644 --- a/R/Bias.R +++ b/R/Bias.R @@ -13,21 +13,26 @@ #' 'dat_dim'. #'@param time_dim A character string indicating the name of the time dimension. #' The default value is 'sdate'. +#'@param dat_dim A character string indicating the name of dataset dimension. +#' The length of this dimension can be different between 'exp' and 'obs'. +#' The default value is NULL. #'@param memb_dim A character string indicating the name of the member dimension -#' to compute the ensemble mean; it should be set to NULL if the parameter 'exp' -#' is already the ensemble mean. The default value is NULL. +#' to compute the ensemble mean; it should be set to NULL if the parameter +#' 'exp' is already the ensemble mean. The default value is NULL. #'@param na.rm A logical value indicating if NAs should be removed (TRUE) or #' kept (FALSE) for computation. The default value is FALSE. -#'@param absolute A logical value indicating whether to compute the absolute bias. -#' The default value is FALSE. +#'@param absolute A logical value indicating whether to compute the absolute +#' bias. The default value is FALSE. #'@param time_dim A logical value indicating whether to compute the temporal #' mean of the bias. The default value is TRUE. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' #'@return -#'A numerical array of bias with dimensions of 'exp' except 'time_dim' (if time_mean = T) -#'and 'memb_dim' dimensions. +#'A numerical array of bias with dimensions nexp, nobs and the rest dimensions +#'of 'exp' except 'time_dim' (if time_mean = T). nexp is the number of +#'experiment (i.e., 'dat_dim' in exp), and nobs is the number of observation +#'(i.e., 'dat_dim' in obs). If dat_dim is NULL, nexp and nobs are omitted. #' #'@references #'Wilks, 2011; https://doi.org/10.1016/B978-0-12-385022-5.00008-7 @@ -40,7 +45,7 @@ #'@import multiApply #'@importFrom ClimProjDiags Subset #'@export -Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, +Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, dat_dim = NULL, na.rm = FALSE, absolute = FALSE, time_mean = TRUE, ncores = NULL) { # Check inputs @@ -55,7 +60,7 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, } ## time_dim if (!is.character(time_dim) | length(time_dim) != 1) - stop('Parameter "time_dim" must be a character string.') + stop("Parameter 'time_dim' must be a character string.") if (!time_dim %in% names(dim(exp)) | !time_dim %in% names(dim(obs))) { stop("Parameter 'time_dim' is not found in 'exp' or 'obs' dimension.") } @@ -77,29 +82,29 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, } } ## dat_dim - # if (!is.null(dat_dim)) { - # if (!is.character(dat_dim) | length(dat_dim) > 1) { - # stop("Parameter 'dat_dim' must be a character string.") - # } - # if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - # stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", - # " Set it as NULL if there is no dataset dimension.") - # } - # } + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + " Set it as NULL if there is no dataset dimension.") + } + } ## exp and obs (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) if (!is.null(memb_dim)) { name_exp <- name_exp[-which(name_exp == memb_dim)] } - # if (!is.null(dat_dim)) { - # name_exp <- name_exp[-which(name_exp == dat_dim)] - # name_obs <- name_obs[-which(name_obs == dat_dim)] - # } + if (!is.null(dat_dim)) { + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] + } if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions except 'memb_dim'")) # and 'dat_dim'.")) + "all dimensions except 'memb_dim' and 'dat_dim'.")) } ## na.rm if (!is.logical(na.rm) | length(na.rm) > 1) { @@ -130,8 +135,10 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, ## (Mean) Bias bias <- Apply(data = list(exp, obs), - target_dims = time_dim, - fun = .Bias, na.rm = na.rm, + target_dims = c(time_dim, dat_dim), + fun = .Bias, + dat_dim = dat_dim, + na.rm = na.rm, absolute = absolute, time_mean = time_mean, ncores = ncores)$output1 @@ -139,17 +146,43 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, return(bias) } -.Bias <- function(exp, obs, na.rm = FALSE, absolute = FALSE, time_mean = TRUE) { - - bias <- exp - obs - - if (isTRUE(absolute)){ - bias <- abs(bias) - } - - if (isTRUE(time_mean)){ - bias <- mean(bias, na.rm = na.rm) + +.Bias <- function(exp, obs, dat_dim = NULL, na.rm = FALSE, absolute = FALSE, time_mean = TRUE) { + + if (is.null(dat_dim)) { + bias <- exp - obs + + if (isTRUE(absolute)){ + bias <- abs(bias) + } + + if (isTRUE(time_mean)){ + bias <- mean(bias, na.rm = na.rm) + } + + } else { + nexp <- as.numeric(dim(exp)[dat_dim]) + nobs <- as.numeric(dim(obs)[dat_dim]) + bias <- array(dim = c(dim(exp)[1], nexp = nexp, nobs = nobs)) + + for (i in 1:nexp) { + for (j in 1:nobs) { + exp_data <- exp[ , i] + obs_data <- obs[ , j] + if (is.null(dim(exp_data))) dim(exp_data) <- c(dim(exp)[1]) + if (is.null(dim(obs_data))) dim(obs_data) <- c(dim(obs)[1]) + bias[ , i, j] <- exp_data - obs_data + } + } + + if (isTRUE(absolute)){ + bias <- abs(bias) + } + + if (isTRUE(time_mean)){ + bias <- apply(bias, c(2,3), mean, na.rm = na.rm) + } } - + return(bias) } diff --git a/man/BiasSS.Rd b/man/AbsBiasSS.Rd similarity index 81% rename from man/BiasSS.Rd rename to man/AbsBiasSS.Rd index bce496e..494982d 100644 --- a/man/BiasSS.Rd +++ b/man/AbsBiasSS.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/BiasSS.R -\name{BiasSS} -\alias{BiasSS} -\title{Compute the Mean Bias Skill Score} +% Please edit documentation in R/AbsBiasSS.R +\name{AbsBiasSS} +\alias{AbsBiasSS} +\title{Compute the Absolute Mean Bias Skill Score} \usage{ -BiasSS( +AbsBiasSS( exp, obs, ref = NULL, @@ -54,7 +54,7 @@ computation. The default value is NULL.} } } \description{ -The Mean Bias Skill Score is based on the Mean Error (Wilks, 2011) between the +The Absolute Mean Bias Skill Score is based on the Absolute Mean Error (Wilks, 2011) between the ensemble mean forecast and the observations. It measures the accuracy of the forecast in comparison with a reference forecast to assess whether the forecast presents an improvement or a worsening with respect to that @@ -63,16 +63,16 @@ Positive values indicate that the forecast has higher skill than the reference forecast, while negative values indicate that it has a lower skill. Examples of reference forecasts are the climatological forecast (average of the observations), a previous model version, or another model. It is computed as -\code{BiasSS = 1 - Bias_exp / Bias_ref}. The statistical significance is +\code{AbsBiasSS = 1 - AbsBias_exp / AbsBias_ref}. The statistical significance is obtained based on a Random Walk test at the 95% confidence level (DelSole and Tippett, 2016). } \examples{ -exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) -ref <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) -obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, year = 50)) -biasSS1 <- BiasSS(exp = exp, obs = obs, ref = ref, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) -biasSS2 <- BiasSS(exp = exp, obs = obs, ref = NULL, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) +exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, sdate = 50)) +ref <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, sdate = 50)) +obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, sdate = 50)) +biasSS1 <- AbsBiasSS(exp = exp, obs = obs, ref = ref, memb_dim = 'member') +biasSS2 <- AbsBiasSS(exp = exp, obs = obs, ref = NULL, memb_dim = 'member') } \references{ diff --git a/man/Bias.Rd b/man/Bias.Rd index a58d263..fb0fafc 100644 --- a/man/Bias.Rd +++ b/man/Bias.Rd @@ -9,7 +9,10 @@ Bias( obs, time_dim = "sdate", memb_dim = NULL, + dat_dim = NULL, na.rm = FALSE, + absolute = FALSE, + time_mean = TRUE, ncores = NULL ) } @@ -21,35 +24,43 @@ dimension.} dimension. The dimensions must be the same as 'exp' except 'memb_dim' and 'dat_dim'.} -\item{time_dim}{A character string indicating the name of the time dimension. -The default value is 'sdate'.} +\item{time_dim}{A logical value indicating whether to compute the temporal +mean of the bias. The default value is TRUE.} \item{memb_dim}{A character string indicating the name of the member dimension -to compute the ensemble mean; it should be set to NULL if the parameter 'exp' -is already the ensemble mean. The default value is NULL.} +to compute the ensemble mean; it should be set to NULL if the parameter +'exp' is already the ensemble mean. The default value is NULL.} + +\item{dat_dim}{A character string indicating the name of dataset dimension. +The length of this dimension can be different between 'exp' and 'obs'. +The default value is NULL.} \item{na.rm}{A logical value indicating if NAs should be removed (TRUE) or kept (FALSE) for computation. The default value is FALSE.} +\item{absolute}{A logical value indicating whether to compute the absolute +bias. The default value is FALSE.} + \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} } \value{ -A numerical array of bias with dimensions of 'exp' except 'time_dim' and -'memb_dim' dimensions. +A numerical array of bias with dimensions nexp, nobs and the rest dimensions +of 'exp' except 'time_dim' (if time_mean = T). nexp is the number of +experiment (i.e., 'dat_dim' in exp), and nobs is the number of observation +(i.e., 'dat_dim' in obs). If dat_dim is NULL, nexp and nobs are omitted. } \description{ The Mean Bias or Mean Error (Wilks, 2011) is defined as the mean difference between the ensemble mean forecast and the observations. It is a deterministic metric. Positive values indicate that the forecasts are on average too high and negative values indicate that the forecasts are on average too low. -However, it gives no information about the typical magnitude of individual -forecast errors. +It also allows to compute the Absolute Mean Bias. } \examples{ -exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, year = 50)) -obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, year = 50)) -bias <- Bias(exp = exp, obs = obs, time_dim = 'year', memb_dim = 'member', na.rm = FALSE, ncores = 1) +exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, sdate = 50)) +obs <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, sdate = 50)) +bias <- Bias(exp = exp, obs = obs, memb_dim = 'member') } \references{ diff --git a/man/s2dv-package.Rd b/man/s2dv-package.Rd index 3c98a95..ffb6783 100644 --- a/man/s2dv-package.Rd +++ b/man/s2dv-package.Rd @@ -6,7 +6,15 @@ \alias{s2dv-package} \title{s2dv: A Set of Common Tools for Seasonal to Decadal Verification} \description{ -The advanced version of package 's2dverification'. It is intended for 'seasonal to decadal' (s2d) climate forecast verification, but it can also be used in other kinds of forecasts or general climate analysis. This package is specially designed for the comparison between the experimental and observational datasets. The functionality of the included functions covers from data retrieval, data post-processing, skill scores against observation, to visualization. Compared to 's2dverification', 's2dv' is more compatible with the package 'startR', able to use multiple cores for computation and handle multi-dimensional arrays with a higher flexibility. +The advanced version of package 's2dverification'. It is + intended for 'seasonal to decadal' (s2d) climate forecast verification, but + it can also be used in other kinds of forecasts or general climate analysis. + This package is specially designed for the comparison between the experimental + and observational datasets. The functionality of the included functions covers + from data retrieval, data post-processing, skill scores against observation, + to visualization. Compared to 's2dverification', 's2dv' is more compatible + with the package 'startR', able to use multiple cores for computation and + handle multi-dimensional arrays with a higher flexibility. } \references{ \url{https://earth.bsc.es/gitlab/es/s2dv/} diff --git a/man/sampleDepthData.Rd b/man/sampleDepthData.Rd index 47e2f1b..77e4a7a 100644 --- a/man/sampleDepthData.Rd +++ b/man/sampleDepthData.Rd @@ -5,8 +5,7 @@ \alias{sampleDepthData} \title{Sample of Experimental Data for Forecast Verification In Function Of Latitudes And Depths} -\format{ -The data set provides with a variable named 'sampleDepthData'.\cr\cr +\format{The data set provides with a variable named 'sampleDepthData'.\cr\cr sampleDepthData$exp is an array that contains the experimental data and the dimension meanings and values are:\cr @@ -19,8 +18,7 @@ but in this sample is not defined (NULL).\cr\cr sampleDepthData$depths is an array with the 7 longitudes covered by the data.\cr\cr -sampleDepthData$lat is an array with the 21 latitudes covered by the data.\cr\cr -} +sampleDepthData$lat is an array with the 21 latitudes covered by the data.\cr\cr} \usage{ data(sampleDepthData) } diff --git a/man/sampleMap.Rd b/man/sampleMap.Rd index e4ec5a5..eaf8aa5 100644 --- a/man/sampleMap.Rd +++ b/man/sampleMap.Rd @@ -4,8 +4,7 @@ \name{sampleMap} \alias{sampleMap} \title{Sample Of Observational And Experimental Data For Forecast Verification In Function Of Longitudes And Latitudes} -\format{ -The data set provides with a variable named 'sampleMap'.\cr\cr +\format{The data set provides with a variable named 'sampleMap'.\cr\cr sampleMap$mod is an array that contains the experimental data and the dimension meanings and values are:\cr c(# of experimental datasets, # of members, # of starting dates, # of lead-times, # of latitudes, # of longitudes)\cr @@ -17,8 +16,7 @@ sampleMap$obs is an array that contains the observational data and the dimension sampleMap$lat is an array with the 2 latitudes covered by the data (see examples on Load() for details on why such low resolution).\cr\cr - sampleMap$lon is an array with the 3 longitudes covered by the data (see examples on Load() for details on why such low resolution). -} + sampleMap$lon is an array with the 3 longitudes covered by the data (see examples on Load() for details on why such low resolution).} \usage{ data(sampleMap) } diff --git a/man/sampleTimeSeries.Rd b/man/sampleTimeSeries.Rd index 7f058e2..05a8e79 100644 --- a/man/sampleTimeSeries.Rd +++ b/man/sampleTimeSeries.Rd @@ -4,8 +4,7 @@ \name{sampleTimeSeries} \alias{sampleTimeSeries} \title{Sample Of Observational And Experimental Data For Forecast Verification As Area Averages} -\format{ -The data set provides with a variable named 'sampleTimeSeries'.\cr\cr +\format{The data set provides with a variable named 'sampleTimeSeries'.\cr\cr sampleTimeSeries$mod is an array that contains the experimental data and the dimension meanings and values are:\cr c(# of experimental datasets, # of members, # of starting dates, # of lead-times)\cr @@ -17,8 +16,7 @@ sampleTimeSeries$obs is an array that contains the observational data and the di sampleTimeSeries$lat is an array with the 2 latitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution).\cr\cr -sampleTimeSeries$lon is an array with the 3 longitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution). -} +sampleTimeSeries$lon is an array with the 3 longitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution).} \usage{ data(sampleTimeSeries) } diff --git a/tests/testthat/test-Bias.R b/tests/testthat/test-Bias.R new file mode 100644 index 0000000..537625b --- /dev/null +++ b/tests/testthat/test-Bias.R @@ -0,0 +1,182 @@ +context("s2dv::Bias tests") + +############################################## + +# dat1 +set.seed(1) +exp1 <- array(rnorm(20), dim = c(sdate = 10, lat = 2)) +set.seed(2) +obs1 <- array(rnorm(20), dim = c(sdate = 10, lat = 2)) + +# dat2 +set.seed(1) +exp2 <- array(rnorm(40), dim = c(member = 2, sdate = 10, lat = 2)) +set.seed(2) +obs2 <- array(rnorm(20), dim = c(member = 1, sdate = 10, lat = 2)) + +# dat3 +set.seed(1) +exp3 <- array(rnorm(80), dim = c(member = 2, sdate = 10, lat = 2, dataset = 2)) +set.seed(2) +obs3 <- array(rnorm(60), dim = c(sdate = 10, lat = 2, dataset = 3)) + +############################################## +test_that("1. Input checks", { + # exp and obs (1) + expect_error( + Bias(c()), + "Parameter 'exp' must be a numeric array." + ) + expect_error( + Bias(exp1, c()), + "Parameter 'obs' must be a numeric array." + ) + expect_error( + Bias(exp1, array(rnorm(20), dim = c(10, lat = 2))), + "Parameter 'exp' and 'obs' must have dimension names." + ) + # time_dim + expect_error( + Bias(exp1, obs1, time_dim = 1), + "Parameter 'time_dim' must be a character string." + ) + expect_error( + Bias(exp1, obs1, time_dim = 'time'), + "Parameter 'time_dim' is not found in 'exp' or 'obs' dimension." + ) + # memb_dim + expect_error( + Bias(exp1, obs1, memb_dim = TRUE), + "Parameter 'memb_dim' must be a character string." + ) + expect_error( + Bias(exp1, obs1, memb_dim = 'memb'), + "Parameter 'memb_dim' is not found in 'exp' dimension." + ) + expect_error( + Bias(exp2, array(rnorm(20), dim = c(member = 3, sdate = 10, lat = 2)), memb_dim = 'member'), + "Not implemented for observations with members ('obs' can have 'memb_dim', but it should be of length = 1).", fixed = TRUE + ) + # dat_dim + expect_error( + Bias(exp1, obs1, dat_dim = TRUE, ), + "Parameter 'dat_dim' must be a character string." + ) + expect_error( + Bias(exp1, obs1, dat_dim = 'dat_dim'), + "Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension. Set it as NULL if there is no dataset dimension." + ) + # exp, ref, and obs (2) + expect_error( + Bias(exp1, array(1:9, dim = c(sdate = 9))), + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." + ) + # na.rm + expect_error( + Bias(exp2, obs2, memb_dim = 'member', na.rm = 1.5), + "Parameter 'na.rm' must be one logical value." + ) + # absolute + expect_error( + Bias(exp2, obs2, memb_dim = 'member', ncores = 1.5), + "Parameter 'ncores' must be either NULL or a positive integer." + ) + # time_mean + expect_error( + Bias(exp2, obs2, memb_dim = 'member', time_mean = 1.5), + "Parameter 'time_mean' must be one logical value." + ) + # ncores + expect_error( + Bias(exp2, obs2, memb_dim = 'member', ncores = 1.5), + "Parameter 'ncores' must be either NULL or a positive integer." + ) + +}) + +############################################## +test_that("2. Output checks: dat1", { + + expect_equal( + dim(Bias(exp1, obs1)), + c(lat = 2) + ) + expect_equal( + dim(Bias(exp1, obs1, time_mean = FALSE)), + c(sdate = 10, lat = 2) + ) + expect_equal( + as.vector(Bias(exp1, obs1)), + c(-0.07894886, 0.06907455), + tolerance = 0.0001 + ) + expect_equal( + as.vector(Bias(exp1, obs1, absolute = TRUE)), + c(0.9557288, 0.8169118), + tolerance = 0.0001 + ) + expect_equal( + as.vector(Bias(exp1, obs1, time_mean = FALSE, na.rm = TRUE))[1:5], + c(0.27046074, -0.00120586, -2.42347394, 2.72565648, 0.40975953), + tolerance = 0.0001 + ) + +}) + +############################################## +test_that("3. Output checks: dat2", { + + expect_equal( + dim(Bias(exp2, obs2, memb_dim = 'member')), + c(lat = 2) + ) + expect_equal( + dim(Bias(exp2, obs2, memb_dim = 'member', time_mean = FALSE)), + c(sdate = 10, lat = 2) + ) + expect_equal( + as.vector(Bias(exp2, obs2, memb_dim = 'member')), + c(-0.02062777, -0.18624194), + tolerance = 0.0001 + ) + expect_equal( + as.vector(Bias(exp2, obs2, memb_dim = 'member', time_mean = FALSE)[1:2,1:2]), + c(0.6755093, 0.1949769, 0.4329061, -1.9391461), + tolerance = 0.0001 + ) + +}) + +############################################## +test_that("4. Output checks: dat3", { + + expect_equal( + dim(Bias(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset')), + c(nexp = 2, nobs = 3, lat = 2) + ) + expect_equal( + dim(Bias(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset', time_mean = FALSE)), + c(sdate = 10, nexp = 2, nobs = 3, lat = 2) + ) + expect_equal( + as.vector(Bias(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset'))[5:10], + c(0.23519286, 0.18346575, -0.18624194, -0.07803352, 0.28918537, 0.39739379), + tolerance = 0.0001 + ) + expect_equal( + as.vector(Bias(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset', absolute = TRUE, time_mean = FALSE))[5:10], + c(0.2154482, 0.8183919, 2.1259250, 0.7796967, 1.5206510, 0.8463483), + tolerance = 0.0001 + ) + expect_equal( + as.vector(Bias(exp2, obs2, memb_dim = 'member')), + as.vector(Bias(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset')[1,1,]) + ) + expect_equal( + as.vector(Bias(exp2, obs2, memb_dim = 'member', time_mean = FALSE)), + as.vector(Bias(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset', time_mean = FALSE)[ ,1,1,]) + ) + +}) + + -- GitLab From 54d862b56d08893262023101f798666ccc7becca Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Fri, 9 Sep 2022 17:11:44 +0200 Subject: [PATCH 104/140] Added dat_dim parameter and test file --- R/AbsBiasSS.R | 238 +++++++++++++++++++++---------- R/Bias.R | 5 +- man/AbsBiasSS.Rd | 29 ++-- tests/testthat/test-AbsBiasSS.R | 240 ++++++++++++++++++++++++++++++++ 4 files changed, 424 insertions(+), 88 deletions(-) create mode 100644 tests/testthat/test-AbsBiasSS.R diff --git a/R/AbsBiasSS.R b/R/AbsBiasSS.R index 8e5da98..4585c8d 100644 --- a/R/AbsBiasSS.R +++ b/R/AbsBiasSS.R @@ -1,17 +1,17 @@ #'Compute the Absolute Mean Bias Skill Score #' -#'The Absolute Mean Bias Skill Score is based on the Absolute Mean Error (Wilks, 2011) between the -#'ensemble mean forecast and the observations. It measures the accuracy of the -#'forecast in comparison with a reference forecast to assess whether the -#'forecast presents an improvement or a worsening with respect to that -#'reference. The Mean Bias Skill Score ranges between minus infinite and 1. +#'The Absolute Mean Bias Skill Score is based on the Absolute Mean Error (Wilks, +#' 2011) between the ensemble mean forecast and the observations. It measures +#'the accuracy of the forecast in comparison with a reference forecast to assess +#'whether the forecast presents an improvement or a worsening with respect to +#'that reference. The Mean Bias Skill Score ranges between minus infinite and 1. #'Positive values indicate that the forecast has higher skill than the reference #'forecast, while negative values indicate that it has a lower skill. Examples #'of reference forecasts are the climatological forecast (average of the #'observations), a previous model version, or another model. It is computed as -#'\code{AbsBiasSS = 1 - AbsBias_exp / AbsBias_ref}. The statistical significance is -#'obtained based on a Random Walk test at the 95% confidence level (DelSole and -#'Tippett, 2016). +#'\code{AbsBiasSS = 1 - AbsBias_exp / AbsBias_ref}. The statistical significance +#'is obtained based on a Random Walk test at the 95% confidence level (DelSole +#'and Tippett, 2016). #' #'@param exp A named numerical array of the forecast with at least time #' dimension. @@ -30,6 +30,9 @@ #'@param memb_dim A character string indicating the name of the member dimension #' to compute the ensemble mean; it should be set to NULL if the parameter 'exp' #' and 'ref' are already the ensemble mean. The default value is NULL. +#'@param dat_dim A character string indicating the name of dataset dimension. +#' The length of this dimension can be different between 'exp' and 'obs'. +#' The default value is NULL. #'@param na.rm A logical value indicating if NAs should be removed (TRUE) or #' kept (FALSE) for computation. The default value is FALSE. #'@param ncores An integer indicating the number of cores to use for parallel @@ -37,12 +40,14 @@ #' #'@return #'\item{$biasSS}{ -#' A numerical array of BiasSS with dimensions the dimensions of -#' 'exp' except 'time_dim' and 'memb_dim' dimensions. +#' A numerical array of BiasSS with dimensions nexp, nobs and the rest +#' dimensions of 'exp' except 'time_dim' and 'memb_dim'. #'} #'\item{$sign}{ #' A logical array of the statistical significance of the BiasSS -#' with the same dimensions as $biasSS. +#' with the same dimensions as $biasSS. nexp is the number of +#' experiment (i.e., 'dat_dim' in exp), and nobs is the number of observation +#' (i.e., 'dat_dim' in obs). If dat_dim is NULL, nexp and nobs are omitted. #'} #' #'@references @@ -58,8 +63,7 @@ #' #'@import multiApply #'@export - -AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, na.rm = FALSE, ncores = NULL) { +AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, dat_dim = NULL, na.rm = FALSE, ncores = NULL) { # Check inputs ## exp, obs, and ref (1) @@ -108,49 +112,49 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, } } ## dat_dim - # if (!is.null(dat_dim)) { - # if (!is.character(dat_dim) | length(dat_dim) > 1) { - # stop("Parameter 'dat_dim' must be a character string.") - # } - # if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { - # stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", - # " Set it as NULL if there is no dataset dimension.") - # } - # } + if (!is.null(dat_dim)) { + if (!is.character(dat_dim) | length(dat_dim) > 1) { + stop("Parameter 'dat_dim' must be a character string.") + } + if (!dat_dim %in% names(dim(exp)) | !dat_dim %in% names(dim(obs))) { + stop("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.", + " Set it as NULL if there is no dataset dimension.") + } + } ## exp, obs, and ref (2) name_exp <- sort(names(dim(exp))) name_obs <- sort(names(dim(obs))) if (!is.null(memb_dim)) { name_exp <- name_exp[-which(name_exp == memb_dim)] } - # if (!is.null(dat_dim)) { - # name_exp <- name_exp[-which(name_exp == dat_dim)] - # name_obs <- name_obs[-which(name_obs == dat_dim)] - # } + if (!is.null(dat_dim)) { + name_exp <- name_exp[-which(name_exp == dat_dim)] + name_obs <- name_obs[-which(name_obs == dat_dim)] + } if (!identical(length(name_exp), length(name_obs)) | !identical(dim(exp)[name_exp], dim(obs)[name_obs])) { stop(paste0("Parameter 'exp' and 'obs' must have same length of ", - "all dimensions except 'memb_dim'")) # and 'dat_dim'.")) + "all dimensions except 'memb_dim' and 'dat_dim'.")) } if (!is.null(ref)) { name_ref <- sort(names(dim(ref))) - if (memb_dim %in% name_ref) { + if (!is.null(memb_dim) && memb_dim %in% name_ref) { name_ref <- name_ref[-which(name_ref == memb_dim)] } - # if (!is.null(dat_dim)) { - # if (dat_dim %in% name_ref) { - # if (!identical(dim(exp)[dat_dim], dim(ref)[dat_dim])) { - # stop(paste0("If parameter 'ref' has dataset dimension, it must be", - # " equal to dataset dimension of 'exp'.")) - # } - # name_ref <- name_ref[-which(name_ref == dat_dim)] - # } - # } + if (!is.null(dat_dim)) { + if (dat_dim %in% name_ref) { + if (!identical(dim(exp)[dat_dim], dim(ref)[dat_dim])) { + stop(paste0("If parameter 'ref' has dataset dimension, it must be", + " equal to dataset dimension of 'exp'.")) + } + name_ref <- name_ref[-which(name_ref == dat_dim)] + } + } if (!identical(length(name_exp), length(name_ref)) | !identical(dim(exp)[name_exp], dim(ref)[name_ref])) { stop(paste0("Parameter 'exp' and 'ref' must have the same length of ", - "all dimensions except 'memb_dim'")) #, - # " and 'dat_dim' if there is only one reference dataset.")) + "all dimensions except 'memb_dim' and 'dat_dim' if there is ", + "only one reference dataset.")) } } ## na.rm @@ -176,51 +180,135 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, } ## Mean bias skill score - if (is.null(ref)) { - ss <- multiApply::Apply(data = list(exp, obs), - target_dims = time_dim, - fun = .AbsBiasSS, na.rm = na.rm, - ref = ref, ncores = ncores) - + if (!is.null(ref)) { # use "ref" as reference forecast + if (!is.null(dat_dim) && dat_dim %in% names(dim(ref))) { + target_dims_ref <- c(time_dim, dat_dim) + } else { + target_dims_ref <- c(time_dim) + } + data <- list(exp = exp, obs = obs, ref = ref) + target_dims = list(exp = c(time_dim, dat_dim), + obs = c(time_dim, dat_dim), + ref = target_dims_ref) } else { - ss <- multiApply::Apply(data = list(exp, obs, ref), - target_dims = time_dim, - fun = .AbsBiasSS, na.rm = na.rm, - ncores = ncores) + data <- list(exp = exp, obs = obs) + target_dims = list(exp = c(time_dim, dat_dim), + obs = c(time_dim, dat_dim)) } - return(ss) + + output <- Apply(data, + target_dims = target_dims, + fun = .AbsBiasSS, + time_dim = time_dim, + dat_dim = dat_dim, + na.rm = na.rm, + ncores = ncores) + + return(output) } -.AbsBiasSS <- function(exp, obs, ref = NULL, na.rm = FALSE) { - # exp and obs: [sdate] - # ref: [sdate] or NULL +.AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', dat_dim = NULL, na.rm = FALSE) { - if (isTRUE(na.rm)) { - if (is.null(ref)) { - good_values <- !is.na(exp) & !is.na(obs) - exp <- exp[good_values] - obs <- obs[good_values] - } else { - good_values <- !is.na(exp) & !is.na(ref) & !is.na(obs) - exp <- exp[good_values] - ref <- ref[good_values] - obs <- obs[good_values] + # exp and obs: [sdate (dat_dim)] + # ref: [sdate (dat_dim)] or NULL + + if (is.null(dat_dim)) { + if (isTRUE(na.rm)) { + if (is.null(ref)) { + good_values <- !is.na(exp) & !is.na(obs) + exp <- exp[good_values] + obs <- obs[good_values] + } else { + good_values <- !is.na(exp) & !is.na(ref) & !is.na(obs) + exp <- exp[good_values] + ref <- ref[good_values] + obs <- obs[good_values] + } + } + } else { + nexp <- as.numeric(dim(exp)[dat_dim]) + nobs <- as.numeric(dim(obs)[dat_dim]) + if (isTRUE(na.rm)) { + if (is.null(ref)) { + for (i in 1:nexp) { + for (j in 1:nobs) { + good_values <- !is.na(exp[ , i]) & !is.na(obs[ , j]) + exp[ , i] <- exp[ , i][good_values] + obs[ , j] <- obs[ , j][good_values] + } + } + } else if (length(dim(ref)) == 1) { + for (i in 1:nexp) { + for (j in 1:nobs) { + good_values <- !is.na(exp[ , i]) & !is.na(ref) & !is.na(obs[ , j]) + exp[ , i] <- exp[ , i][good_values] + obs[ , j] <- obs[ , j][good_values] + ref <- ref[good_values] + } + } + } else { + for (i in 1:nexp) { + for (j in 1:nobs) { + good_values <- !is.na(exp[ , i]) & !is.na(ref[ , i]) & !is.na(obs[ , j]) + exp[ , i] <- exp[ , i][good_values] + obs[ , j] <- obs[ , j][good_values] + ref[ , i] <- ref[ , i][good_values] + } + } + } } } - ## Bias of the exp - bias_exp <- .Bias(exp = exp, obs = obs, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + ## Bias of the exp + bias_exp <- .Bias(exp = exp, obs = obs, dat_dim = dat_dim, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + ## Bias of the ref - if (is.null(ref)) { ## Climatological forecast - ref <- mean(obs, na.rm = na.rm) + if (is.null(dat_dim)) { + if (is.null(ref)) { + ref <- mean(obs, na.rm = na.rm) + } + bias_ref <- .Bias(exp = ref, obs = obs, dat_dim = dat_dim, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + } else { + if (is.null(ref)) { + ref <- MeanDims(obs, time_dim, na.rm = na.rm) + bias_ref <- array(dim = c(nobs = nobs)) + for (j in 1:nobs) { + bias_ref[j] <- .Bias(exp = ref[j], obs = obs[ , j], dat_dim = NULL, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + } + } else if (length(dim(ref)) == 1) { + bias_ref <- array(dim = c(nobs = nobs)) + for (j in 1:nobs) { + bias_ref[j] <- .Bias(exp = ref, obs = obs[ , j], dat_dim = NULL, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + } + } else { + bias_ref <- .Bias(exp = ref, obs = obs, dat_dim = dat_dim, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + } } - bias_ref <- .Bias(exp = ref, obs = obs, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) - + ## Skill score and significance - ss <- list() - ss$biasSS <- 1 - bias_exp / bias_ref - ss$sign <- .RandomWalkTest(skill_A = bias_exp, skill_B = bias_ref)$signif - - return(ss) -} + if (is.null(dat_dim)) { + biasSS <- 1 - bias_exp / bias_ref + sign <- .RandomWalkTest(skill_A = bias_exp, skill_B = bias_ref)$signif + } else { + biasSS <- array(dim = c(nexp = nexp, nobs = nobs)) + sign <- array(dim = c(nexp = nexp, nobs = nobs)) + if (length(dim(bias_ref)) == 1) { + for (i in 1:nexp) { + for (j in 1:nobs) { + biasSS[i, j] <- 1 - bias_exp[i, j] / bias_ref[j] + sign[i, j] <- .RandomWalkTest(skill_A = bias_exp[i, j], skill_B = bias_ref[j])$signif + } + } + } else { + for (i in 1:nexp) { + for (j in 1:nobs) { + biasSS[i, j] <- 1 - bias_exp[i, j] / bias_ref[i, j] + sign[i, j] <- .RandomWalkTest(skill_A = bias_exp[i, j], skill_B = bias_ref[i, j])$signif + } + } + } + } + + return(list(biasSS = biasSS, sign = sign)) +} \ No newline at end of file diff --git a/R/Bias.R b/R/Bias.R index f7d6e31..e438115 100644 --- a/R/Bias.R +++ b/R/Bias.R @@ -137,6 +137,7 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, dat_dim = NULL, bias <- Apply(data = list(exp, obs), target_dims = c(time_dim, dat_dim), fun = .Bias, + time_dim = time_dim, dat_dim = dat_dim, na.rm = na.rm, absolute = absolute, @@ -147,7 +148,7 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, dat_dim = NULL, } -.Bias <- function(exp, obs, dat_dim = NULL, na.rm = FALSE, absolute = FALSE, time_mean = TRUE) { +.Bias <- function(exp, obs, time_dim = 'sdate', dat_dim = NULL, na.rm = FALSE, absolute = FALSE, time_mean = TRUE) { if (is.null(dat_dim)) { bias <- exp - obs @@ -180,7 +181,7 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, dat_dim = NULL, } if (isTRUE(time_mean)){ - bias <- apply(bias, c(2,3), mean, na.rm = na.rm) + bias <- MeanDims(bias, time_dim, na.rm = na.rm) } } diff --git a/man/AbsBiasSS.Rd b/man/AbsBiasSS.Rd index 494982d..89fab8a 100644 --- a/man/AbsBiasSS.Rd +++ b/man/AbsBiasSS.Rd @@ -10,6 +10,7 @@ AbsBiasSS( ref = NULL, time_dim = "sdate", memb_dim = NULL, + dat_dim = NULL, na.rm = FALSE, ncores = NULL ) @@ -37,6 +38,10 @@ The default value is 'sdate'.} to compute the ensemble mean; it should be set to NULL if the parameter 'exp' and 'ref' are already the ensemble mean. The default value is NULL.} +\item{dat_dim}{A character string indicating the name of dataset dimension. +The length of this dimension can be different between 'exp' and 'obs'. +The default value is NULL.} + \item{na.rm}{A logical value indicating if NAs should be removed (TRUE) or kept (FALSE) for computation. The default value is FALSE.} @@ -45,27 +50,29 @@ computation. The default value is NULL.} } \value{ \item{$biasSS}{ - A numerical array of BiasSS with dimensions the dimensions of - 'exp' except 'time_dim' and 'memb_dim' dimensions. + A numerical array of BiasSS with dimensions nexp, nobs and the rest + dimensions of 'exp' except 'time_dim' and 'memb_dim'. } \item{$sign}{ A logical array of the statistical significance of the BiasSS - with the same dimensions as $biasSS. + with the same dimensions as $biasSS. nexp is the number of + experiment (i.e., 'dat_dim' in exp), and nobs is the number of observation + (i.e., 'dat_dim' in obs). If dat_dim is NULL, nexp and nobs are omitted. } } \description{ -The Absolute Mean Bias Skill Score is based on the Absolute Mean Error (Wilks, 2011) between the -ensemble mean forecast and the observations. It measures the accuracy of the -forecast in comparison with a reference forecast to assess whether the -forecast presents an improvement or a worsening with respect to that -reference. The Mean Bias Skill Score ranges between minus infinite and 1. +The Absolute Mean Bias Skill Score is based on the Absolute Mean Error (Wilks, +2011) between the ensemble mean forecast and the observations. It measures +the accuracy of the forecast in comparison with a reference forecast to assess +whether the forecast presents an improvement or a worsening with respect to +that reference. The Mean Bias Skill Score ranges between minus infinite and 1. Positive values indicate that the forecast has higher skill than the reference forecast, while negative values indicate that it has a lower skill. Examples of reference forecasts are the climatological forecast (average of the observations), a previous model version, or another model. It is computed as -\code{AbsBiasSS = 1 - AbsBias_exp / AbsBias_ref}. The statistical significance is -obtained based on a Random Walk test at the 95% confidence level (DelSole and -Tippett, 2016). +\code{AbsBiasSS = 1 - AbsBias_exp / AbsBias_ref}. The statistical significance +is obtained based on a Random Walk test at the 95% confidence level (DelSole +and Tippett, 2016). } \examples{ exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, sdate = 50)) diff --git a/tests/testthat/test-AbsBiasSS.R b/tests/testthat/test-AbsBiasSS.R new file mode 100644 index 0000000..2db29b5 --- /dev/null +++ b/tests/testthat/test-AbsBiasSS.R @@ -0,0 +1,240 @@ +context("s2dv::AbsBiasSS tests") + +############################################## + +# dat1 +set.seed(1) +exp1 <- array(rnorm(20), dim = c(sdate = 10, lat = 2)) +set.seed(2) +obs1 <- array(rnorm(20), dim = c(sdate = 10, lat = 2)) +set.seed(3) +ref1 <- array(rnorm(20), dim = c(sdate = 10, lat = 2)) + +# dat2 +set.seed(1) +exp2 <- array(rnorm(60), dim = c(member = 2, sdate = 10, lat = 3)) +set.seed(2) +obs2 <- array(rnorm(30), dim = c(member = 1, sdate = 10, lat = 3)) +set.seed(3) +ref2 <- array(rnorm(60), dim = c(member = 2, sdate = 10, lat = 3)) + +# dat3 +set.seed(1) +exp3 <- array(rnorm(80), dim = c(member = 2, sdate = 10, lat = 2, dataset = 2)) +set.seed(2) +obs3 <- array(rnorm(60), dim = c(sdate = 10, lat = 2, dataset = 3)) +set.seed(3) +ref3 <- array(rnorm(20), dim = c(sdate = 10, lat = 2)) + +# dat4 +set.seed(1) +exp4 <- array(rnorm(80), dim = c(member = 2, sdate = 10, lat = 2, dataset = 2)) +set.seed(2) +obs4 <- array(rnorm(60), dim = c(sdate = 10, lat = 2, dataset = 3)) +set.seed(3) +ref4 <- array(rnorm(40), dim = c(sdate = 10, lat = 2, dataset = 2)) + +############################################## +test_that("1. Input checks", { + # exp and obs (1) + expect_error( + AbsBiasSS(c()), + "Parameter 'exp' must be a numeric array." + ) + expect_error( + AbsBiasSS(exp1, c()), + "Parameter 'obs' must be a numeric array." + ) + expect_error( + AbsBiasSS(exp1, array(rnorm(20), dim = c(10, lat = 2))), + "Parameter 'exp' and 'obs' must have dimension names." + ) + expect_error( + AbsBiasSS(exp1, array(rnorm(20), dim = c(10, lat = 2))), + "Parameter 'exp' and 'obs' must have dimension names." + ) + expect_error( + AbsBiasSS(exp1, obs1, ref = 'ref'), + "Parameter 'ref' must be a numeric array." + ) + expect_error( + AbsBiasSS(exp1, obs1, ref = array(rnorm(20), dim = c(10, lat = 2))), + "Parameter 'ref' must have dimension names." + ) + # time_dim + expect_error( + AbsBiasSS(exp1, obs1, time_dim = 1), + "Parameter 'time_dim' must be a character string." + ) + expect_error( + AbsBiasSS(exp1, obs1, time_dim = 'time'), + "Parameter 'time_dim' is not found in 'exp' or 'obs' dimension." + ) + expect_error( + AbsBiasSS(exp1, obs1, ref = array(rnorm(20), dim = c(lon = 10, lat = 2)), time_dim = 'time'), + "Parameter 'time_dim' is not found in 'exp' or 'obs' dimension." + ) + # memb_dim + expect_error( + AbsBiasSS(exp1, obs1, memb_dim = TRUE), + "Parameter 'memb_dim' must be a character string." + ) + expect_error( + AbsBiasSS(exp1, obs1, memb_dim = 'memb'), + "Parameter 'memb_dim' is not found in 'exp' dimension." + ) + expect_error( + AbsBiasSS(exp2, array(rnorm(20), dim = c(member = 3, sdate = 10, lat = 2)), memb_dim = 'member'), + "Not implemented for observations with members ('obs' can have 'memb_dim', but it should be of length = 1).", fixed = TRUE + ) + # dat_dim + expect_error( + AbsBiasSS(exp1, obs1, dat_dim = TRUE, ), + "Parameter 'dat_dim' must be a character string." + ) + expect_error( + AbsBiasSS(exp1, obs1, dat_dim = 'dat_dim'), + "Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension. Set it as NULL if there is no dataset dimension." + ) + # exp, ref, and obs (2) + expect_error( + AbsBiasSS(exp1, array(1:9, dim = c(sdate = 9))), + "Parameter 'exp' and 'obs' must have same length of all dimensions except 'memb_dim' and 'dat_dim'." + ) + expect_error( + AbsBiasSS(exp3, obs3, ref = obs3, dat_dim = 'dataset', memb_dim = 'member'), + "If parameter 'ref' has dataset dimension, it must be equal to dataset dimension of 'exp'." + ) + expect_error( + AbsBiasSS(exp1, obs1, ref = ref2), + "Parameter 'exp' and 'ref' must have the same length of all dimensions except 'memb_dim' and 'dat_dim' if there is only one reference dataset." + ) + # na.rm + expect_error( + AbsBiasSS(exp2, obs2, memb_dim = 'member', na.rm = 1.5), + "Parameter 'na.rm' must be one logical value." + ) + # ncores + expect_error( + AbsBiasSS(exp2, obs2, memb_dim = 'member', ncores = 1.5), + "Parameter 'ncores' must be either NULL or a positive integer." + ) + +}) + +############################################## +test_that("2. Output checks: dat1", { + + expect_equal( + dim(AbsBiasSS(exp1, obs1)$biasSS), + c(lat = 2) + ) + expect_equal( + dim(AbsBiasSS(exp1, obs1, ref1)$biasSS), + c(lat = 2) + ) + expect_equal( + as.vector(AbsBiasSS(exp1, obs1)$biasSS), + c(-0.3103594, 0.0772921), + tolerance = 0.0001 + ) + expect_equal( + as.vector(AbsBiasSS(exp1, obs1, ref1)$biasSS), + c(-0.07871642, 0.28868904), + tolerance = 0.0001 + ) + expect_equal( + as.vector(AbsBiasSS(exp1, obs1)$sign), + c(FALSE, FALSE), + tolerance = 0.0001 + ) + +}) + +############################################## +test_that("3. Output checks: dat2", { + + expect_equal( + dim(AbsBiasSS(exp2, obs2, memb_dim = 'member')$biasSS), + c(lat = 3) + ) + expect_equal( + dim(AbsBiasSS(exp2, obs2, ref2, memb_dim = 'member')$biasSS), + c(lat = 3) + ) + expect_equal( + as.vector(AbsBiasSS(exp2, obs2, memb_dim = 'member')$biasSS), + c(-0.4743706, -0.2884077, -0.4064404), + tolerance = 0.0001 + ) + expect_equal( + as.vector(AbsBiasSS(exp2, obs2, ref2, memb_dim = 'member')$biasSS), + c(-0.07319869, 0.06277502, -0.17321998), + tolerance = 0.0001 + ) + +}) + +############################################## +test_that("4. Output checks: dat3", { + + expect_equal( + dim(AbsBiasSS(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset')$biasSS), + c(nexp = 2, nobs = 3, lat = 2) + ) + expect_equal( + dim(AbsBiasSS(exp3, obs3, ref3, memb_dim = 'member', dat_dim = 'dataset')$biasSS), + c(nexp = 2, nobs = 3, lat = 2) + ) + expect_equal( + as.vector(AbsBiasSS(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset')$biasSS)[5:10], + c(-0.09794912, -0.11814710, -0.28840768, 0.02117376, -0.31282805, -0.08754112), + tolerance = 0.0001 + ) + expect_equal( + as.vector(AbsBiasSS(exp3, obs3, ref3, memb_dim = 'member', dat_dim = 'dataset')$biasSS)[5:10], + c(0.264602089, 0.251073637, 0.006772886, 0.245427687, 0.168998894, 0.311602249), + tolerance = 0.0001 + ) + expect_equal( + as.vector(AbsBiasSS(exp2, obs2, memb_dim = 'member')$biasSS)[1:2], + as.vector(AbsBiasSS(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset')$biasSS[1,1,]) + ) + expect_equal( + as.vector(AbsBiasSS(exp3, obs3, ref3, memb_dim = 'member', dat_dim = 'dataset', na.rm = TRUE)$biasSS)[1:5], + c(-0.21373395, -0.34444526, 0.11039962, 0.05523797, 0.26460209) + ) + +}) +############################################## +test_that("5. Output checks: dat4", { + + expect_equal( + dim(AbsBiasSS(exp4, obs4, memb_dim = 'member', dat_dim = 'dataset')$biasSS), + c(nexp = 2, nobs = 3, lat = 2) + ) + expect_equal( + dim(AbsBiasSS(exp4, obs4, ref4, memb_dim = 'member', dat_dim = 'dataset')$biasSS), + c(nexp = 2, nobs = 3, lat = 2) + ) + expect_equal( + as.vector(AbsBiasSS(exp4, obs4, memb_dim = 'member', dat_dim = 'dataset', na.rm = TRUE)$biasSS)[5:10], + c(-0.09794912, -0.11814710, -0.28840768, 0.02117376, -0.31282805, -0.08754112), + tolerance = 0.0001 + ) + expect_equal( + as.vector(AbsBiasSS(exp4, obs4, ref4, memb_dim = 'member', dat_dim = 'dataset')$biasSS)[5:10], + c(0.264602089, 0.133379718, 0.006772886, 0.242372951, 0.168998894, 0.272767238), + tolerance = 0.0001 + ) + expect_equal( + as.vector(AbsBiasSS(exp2, obs2, memb_dim = 'member')$biasSS)[1:2], + as.vector(AbsBiasSS(exp4, obs4, memb_dim = 'member', dat_dim = 'dataset')$biasSS[1,1,]) + ) + expect_equal( + as.vector(AbsBiasSS(exp4, obs4, ref4, memb_dim = 'member', dat_dim = 'dataset', na.rm = TRUE)$biasSS)[1:5], + c(-0.213733950, -0.214240924, 0.110399615, -0.009733463, 0.264602089) + ) + +}) + -- GitLab From 69d533f01cbe24576e8128926d4664a4bc5f0998 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Mon, 12 Sep 2022 09:49:39 +0200 Subject: [PATCH 105/140] Add sub_toptitle_size parameter about subtitle size in PlotLayout --- R/PlotLayout.R | 9 ++++++++- man/PlotLayout.Rd | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/R/PlotLayout.R b/R/PlotLayout.R index 742478e..f59b832 100644 --- a/R/PlotLayout.R +++ b/R/PlotLayout.R @@ -216,6 +216,7 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, title_scale = 1, title_margin_scale = 1, title_left_shift_scale = 1, subtitle_scale = 1, subtitle_margin_scale = 1, + sub_toptitle_size = 1, brks = NULL, cols = NULL, drawleg = 'S', titles = NULL, subsampleg = NULL, bar_limits = NULL, triangle_ends = NULL, col_inf = NULL, col_sup = NULL, @@ -392,6 +393,11 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, stop("Parameter 'subtite_margin_scale' must be numeric.") } + # Check sub_toptitle_size + if (!is.numeric(sub_toptitle_size)) { + stop("Parameter 'sub_toptitle_size' must be numeric.") + } + # Check titles if (!all(sapply(titles, is.character))) { stop("Parameter 'titles' must be a vector of character strings.") @@ -665,7 +671,8 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, fun_args <- c(fun_args, list(brks = colorbar$brks, cols = colorbar$cols, col_inf = colorbar$col_inf, col_sup = colorbar$col_sup, - drawleg = FALSE)) + drawleg = FALSE, + title_scale = sub_toptitle_size)) } do.call(fun[[array_number]], fun_args) plot_number <<- plot_number + 1 diff --git a/man/PlotLayout.Rd b/man/PlotLayout.Rd index f8e7bae..36f84d8 100644 --- a/man/PlotLayout.Rd +++ b/man/PlotLayout.Rd @@ -21,6 +21,7 @@ PlotLayout( title_left_shift_scale = 1, subtitle_scale = 1, subtitle_margin_scale = 1, + sub_toptitle_size = 1, brks = NULL, cols = NULL, drawleg = "S", -- GitLab From d7f4a7db07fa82703398844f3e5cf8444e99be39 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 14 Sep 2022 10:11:20 +0200 Subject: [PATCH 106/140] Change subplot_title_scale and added subplot_margin_scale to PlotLayout --- R/PlotLayout.R | 20 +++++++++++++++----- man/PlotLayout.Rd | 9 ++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/R/PlotLayout.R b/R/PlotLayout.R index f59b832..0b693a1 100644 --- a/R/PlotLayout.R +++ b/R/PlotLayout.R @@ -101,6 +101,10 @@ #' (specified in 'row_titles' and 'col_titles'). Takes 1 by default. #'@param subtitle_margin_scale Scale factor for the margins surrounding the #' subtitles. Takes 1 by default. +#'@param subplot_titles_scale Scale factor for the subplots top titles. Takes +#' 1 by default. +#'@param subplot_margin_scale Scale factor for the margins surrounding the +#' subplots, with the format c(y1, x1, y2, x2). Defaults to rep(1, 4). #'@param units Title at the top of the colour bar, most commonly the units of #' the variable provided in parameter 'var'. #'@param brks,cols,bar_limits,triangle_ends Usually only providing 'brks' is @@ -216,7 +220,7 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, title_scale = 1, title_margin_scale = 1, title_left_shift_scale = 1, subtitle_scale = 1, subtitle_margin_scale = 1, - sub_toptitle_size = 1, + subplot_titles_scale = 1, subplot_margin_scale = rep(1, 4), brks = NULL, cols = NULL, drawleg = 'S', titles = NULL, subsampleg = NULL, bar_limits = NULL, triangle_ends = NULL, col_inf = NULL, col_sup = NULL, @@ -393,9 +397,14 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, stop("Parameter 'subtite_margin_scale' must be numeric.") } - # Check sub_toptitle_size - if (!is.numeric(sub_toptitle_size)) { - stop("Parameter 'sub_toptitle_size' must be numeric.") + # Check subplot_titles_scale + if (!is.numeric(subplot_titles_scale)) { + stop("Parameter 'subplot_titles_scale' must be numeric.") + } + + # subplot_margin_scale + if (!is.numeric(subplot_margin_scale) || length(subplot_margin_scale) != 4) { + stop("Parameter 'subplot_margin_scale' must be a numeric vector with 4 elements.") } # Check titles @@ -672,7 +681,8 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, col_inf = colorbar$col_inf, col_sup = colorbar$col_sup, drawleg = FALSE, - title_scale = sub_toptitle_size)) + title_scale = subplot_titles_scale, + margin_scale = subplot_margin_scale)) } do.call(fun[[array_number]], fun_args) plot_number <<- plot_number + 1 diff --git a/man/PlotLayout.Rd b/man/PlotLayout.Rd index 36f84d8..f634cfc 100644 --- a/man/PlotLayout.Rd +++ b/man/PlotLayout.Rd @@ -21,7 +21,8 @@ PlotLayout( title_left_shift_scale = 1, subtitle_scale = 1, subtitle_margin_scale = 1, - sub_toptitle_size = 1, + subplot_titles_scale = 1, + subplot_margin_scale = rep(1, 4), brks = NULL, cols = NULL, drawleg = "S", @@ -153,6 +154,12 @@ be disregarded if no 'row_titles' are provided.} \item{subtitle_margin_scale}{Scale factor for the margins surrounding the subtitles. Takes 1 by default.} +\item{subplot_titles_scale}{Scale factor for the subplots top titles. Takes +1 by default.} + +\item{subplot_margin_scale}{Scale factor for the margins surrounding the +subplots, with the format c(y1, x1, y2, x2). Defaults to rep(1, 4).} + \item{brks, cols, bar_limits, triangle_ends}{Usually only providing 'brks' is enough to generate the desired colour bar. These parameters allow to define n breaks that define n - 1 intervals to classify each of the values -- GitLab From 62b19dbd7ce5343bbe9b4be8f20b9defc9acf79d Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 14 Sep 2022 12:39:51 +0200 Subject: [PATCH 107/140] Deleted subplots_margin_scale parameter and separated PlotSection --- R/PlotLayout.R | 18 +++++++----------- man/PlotLayout.Rd | 4 ---- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/R/PlotLayout.R b/R/PlotLayout.R index 0b693a1..00ba674 100644 --- a/R/PlotLayout.R +++ b/R/PlotLayout.R @@ -103,8 +103,6 @@ #' subtitles. Takes 1 by default. #'@param subplot_titles_scale Scale factor for the subplots top titles. Takes #' 1 by default. -#'@param subplot_margin_scale Scale factor for the margins surrounding the -#' subplots, with the format c(y1, x1, y2, x2). Defaults to rep(1, 4). #'@param units Title at the top of the colour bar, most commonly the units of #' the variable provided in parameter 'var'. #'@param brks,cols,bar_limits,triangle_ends Usually only providing 'brks' is @@ -220,7 +218,7 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, title_scale = 1, title_margin_scale = 1, title_left_shift_scale = 1, subtitle_scale = 1, subtitle_margin_scale = 1, - subplot_titles_scale = 1, subplot_margin_scale = rep(1, 4), + subplot_titles_scale = 1, brks = NULL, cols = NULL, drawleg = 'S', titles = NULL, subsampleg = NULL, bar_limits = NULL, triangle_ends = NULL, col_inf = NULL, col_sup = NULL, @@ -402,11 +400,6 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, stop("Parameter 'subplot_titles_scale' must be numeric.") } - # subplot_margin_scale - if (!is.numeric(subplot_margin_scale) || length(subplot_margin_scale) != 4) { - stop("Parameter 'subplot_margin_scale' must be a numeric vector with 4 elements.") - } - # Check titles if (!all(sapply(titles, is.character))) { stop("Parameter 'titles' must be a vector of character strings.") @@ -676,13 +669,16 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, fun_args <- c(list(y, toptitle = titles[plot_number]), list(...), special_args[[array_number]]) funct <- fun[[array_number]] - if (fun[[array_number]] %in% c('PlotEquiMap', 'PlotStereoMap', 'PlotSection')) { + if (fun[[array_number]] %in% c('PlotEquiMap', 'PlotStereoMap')) { fun_args <- c(fun_args, list(brks = colorbar$brks, cols = colorbar$cols, col_inf = colorbar$col_inf, col_sup = colorbar$col_sup, drawleg = FALSE, - title_scale = subplot_titles_scale, - margin_scale = subplot_margin_scale)) + title_scale = subplot_titles_scale)) + } else if (fun[[array_number]] == c('PlotSection')) { + fun_args <- c(fun_args, list(brks = colorbar$brks, cols = colorbar$cols, + drawleg = FALSE)) + } do.call(fun[[array_number]], fun_args) plot_number <<- plot_number + 1 diff --git a/man/PlotLayout.Rd b/man/PlotLayout.Rd index f634cfc..2a053ad 100644 --- a/man/PlotLayout.Rd +++ b/man/PlotLayout.Rd @@ -22,7 +22,6 @@ PlotLayout( subtitle_scale = 1, subtitle_margin_scale = 1, subplot_titles_scale = 1, - subplot_margin_scale = rep(1, 4), brks = NULL, cols = NULL, drawleg = "S", @@ -157,9 +156,6 @@ subtitles. Takes 1 by default.} \item{subplot_titles_scale}{Scale factor for the subplots top titles. Takes 1 by default.} -\item{subplot_margin_scale}{Scale factor for the margins surrounding the -subplots, with the format c(y1, x1, y2, x2). Defaults to rep(1, 4).} - \item{brks, cols, bar_limits, triangle_ends}{Usually only providing 'brks' is enough to generate the desired colour bar. These parameters allow to define n breaks that define n - 1 intervals to classify each of the values -- GitLab From e4cd6852d6c0ed54619621890759e9925873673d Mon Sep 17 00:00:00 2001 From: aho Date: Wed, 14 Sep 2022 17:33:50 +0200 Subject: [PATCH 108/140] Improve document and code; add essential unit test --- R/AbsBiasSS.R | 49 +++++++++++++---------- R/Bias.R | 32 +++++++-------- man/AbsBiasSS.Rd | 3 +- man/Bias.Rd | 17 +++++--- tests/testthat/test-AbsBiasSS.R | 71 +++++++++++++++++++++++++++++++++ tests/testthat/test-Bias.R | 44 ++++++++++++++++++++ 6 files changed, 173 insertions(+), 43 deletions(-) diff --git a/R/AbsBiasSS.R b/R/AbsBiasSS.R index 4585c8d..d01b09e 100644 --- a/R/AbsBiasSS.R +++ b/R/AbsBiasSS.R @@ -11,7 +11,8 @@ #'observations), a previous model version, or another model. It is computed as #'\code{AbsBiasSS = 1 - AbsBias_exp / AbsBias_ref}. The statistical significance #'is obtained based on a Random Walk test at the 95% confidence level (DelSole -#'and Tippett, 2016). +#'and Tippett, 2016). If there is more than one dataset, the result will be +#'computed for each pair of exp and obs data. #' #'@param exp A named numerical array of the forecast with at least time #' dimension. @@ -63,7 +64,8 @@ #' #'@import multiApply #'@export -AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, dat_dim = NULL, na.rm = FALSE, ncores = NULL) { +AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, + dat_dim = NULL, na.rm = FALSE, ncores = NULL) { # Check inputs ## exp, obs, and ref (1) @@ -209,8 +211,8 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, .AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', dat_dim = NULL, na.rm = FALSE) { - # exp and obs: [sdate (dat_dim)] - # ref: [sdate (dat_dim)] or NULL + # exp and obs: [sdate, (dat_dim)] + # ref: [sdate, (dat_dim)] or NULL if (is.null(dat_dim)) { if (isTRUE(na.rm)) { @@ -232,27 +234,27 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, if (is.null(ref)) { for (i in 1:nexp) { for (j in 1:nobs) { - good_values <- !is.na(exp[ , i]) & !is.na(obs[ , j]) - exp[ , i] <- exp[ , i][good_values] - obs[ , j] <- obs[ , j][good_values] + good_values <- !is.na(exp[, i]) & !is.na(obs[, j]) + exp[, i] <- exp[, i][good_values] + obs[, j] <- obs[, j][good_values] } } - } else if (length(dim(ref)) == 1) { + } else if (length(dim(ref)) == 1) { #ref: [sdate] for (i in 1:nexp) { for (j in 1:nobs) { - good_values <- !is.na(exp[ , i]) & !is.na(ref) & !is.na(obs[ , j]) - exp[ , i] <- exp[ , i][good_values] - obs[ , j] <- obs[ , j][good_values] + good_values <- !is.na(exp[, i]) & !is.na(ref) & !is.na(obs[, j]) + exp[, i] <- exp[, i][good_values] + obs[, j] <- obs[, j][good_values] ref <- ref[good_values] } } } else { for (i in 1:nexp) { for (j in 1:nobs) { - good_values <- !is.na(exp[ , i]) & !is.na(ref[ , i]) & !is.na(obs[ , j]) - exp[ , i] <- exp[ , i][good_values] - obs[ , j] <- obs[ , j][good_values] - ref[ , i] <- ref[ , i][good_values] + good_values <- !is.na(exp[, i]) & !is.na(ref[, i]) & !is.na(obs[, j]) + exp[, i] <- exp[, i][good_values] + obs[, j] <- obs[, j][good_values] + ref[, i] <- ref[, i][good_values] } } } @@ -269,20 +271,26 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, ref <- mean(obs, na.rm = na.rm) } bias_ref <- .Bias(exp = ref, obs = obs, dat_dim = dat_dim, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + # bias_ref should be a number + } else { if (is.null(ref)) { ref <- MeanDims(obs, time_dim, na.rm = na.rm) + # ref should be [dat] bias_ref <- array(dim = c(nobs = nobs)) for (j in 1:nobs) { - bias_ref[j] <- .Bias(exp = ref[j], obs = obs[ , j], dat_dim = NULL, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + bias_ref[j] <- .Bias(exp = ref[j], obs = obs[, j], dat_dim = NULL, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) } - } else if (length(dim(ref)) == 1) { + # bias_ref should be [nobs] + } else if (length(dim(ref)) == 1) { # ref: [sdate] bias_ref <- array(dim = c(nobs = nobs)) for (j in 1:nobs) { - bias_ref[j] <- .Bias(exp = ref, obs = obs[ , j], dat_dim = NULL, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + bias_ref[j] <- .Bias(exp = ref, obs = obs[, j], dat_dim = NULL, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) } + # bias_ref should be [nobs] } else { bias_ref <- .Bias(exp = ref, obs = obs, dat_dim = dat_dim, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + # bias_ref should be [nexp, nobs] } } @@ -290,10 +298,11 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, if (is.null(dat_dim)) { biasSS <- 1 - bias_exp / bias_ref sign <- .RandomWalkTest(skill_A = bias_exp, skill_B = bias_ref)$signif + } else { biasSS <- array(dim = c(nexp = nexp, nobs = nobs)) sign <- array(dim = c(nexp = nexp, nobs = nobs)) - if (length(dim(bias_ref)) == 1) { + if (length(dim(bias_ref)) == 1) { # ref: [sdate] for (i in 1:nexp) { for (j in 1:nobs) { biasSS[i, j] <- 1 - bias_exp[i, j] / bias_ref[j] @@ -311,4 +320,4 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, } return(list(biasSS = biasSS, sign = sign)) -} \ No newline at end of file +} diff --git a/R/Bias.R b/R/Bias.R index e438115..0319a0f 100644 --- a/R/Bias.R +++ b/R/Bias.R @@ -4,7 +4,9 @@ #'between the ensemble mean forecast and the observations. It is a deterministic #'metric. Positive values indicate that the forecasts are on average too high #'and negative values indicate that the forecasts are on average too low. -#'It also allows to compute the Absolute Mean Bias. +#'It also allows to compute the Absolute Mean Bias or bias without temporal +#'mean. If there is more than one dataset, the result will be computed for each +#'pair of exp and obs data. #' #'@param exp A named numerical array of the forecast with at least time #' dimension. @@ -23,15 +25,15 @@ #' kept (FALSE) for computation. The default value is FALSE. #'@param absolute A logical value indicating whether to compute the absolute #' bias. The default value is FALSE. -#'@param time_dim A logical value indicating whether to compute the temporal +#'@param time_mean A logical value indicating whether to compute the temporal #' mean of the bias. The default value is TRUE. #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' #'@return -#'A numerical array of bias with dimensions nexp, nobs and the rest dimensions -#'of 'exp' except 'time_dim' (if time_mean = T). nexp is the number of -#'experiment (i.e., 'dat_dim' in exp), and nobs is the number of observation +#'A numerical array of bias with dimensions c(nexp, nobs, the rest dimensions of +#''exp' except 'time_dim' (if time_mean = T) and 'memb_dim'). nexp is the number +#'of experiment (i.e., 'dat_dim' in exp), and nobs is the number of observation #'(i.e., 'dat_dim' in obs). If dat_dim is NULL, nexp and nobs are omitted. #' #'@references @@ -148,39 +150,37 @@ Bias <- function(exp, obs, time_dim = 'sdate', memb_dim = NULL, dat_dim = NULL, } -.Bias <- function(exp, obs, time_dim = 'sdate', dat_dim = NULL, na.rm = FALSE, absolute = FALSE, time_mean = TRUE) { +.Bias <- function(exp, obs, time_dim = 'sdate', dat_dim = NULL, na.rm = FALSE, + absolute = FALSE, time_mean = TRUE) { + # exp and obs: [sdate, (dat)] if (is.null(dat_dim)) { bias <- exp - obs - if (isTRUE(absolute)){ + if (isTRUE(absolute)) { bias <- abs(bias) } - if (isTRUE(time_mean)){ + if (isTRUE(time_mean)) { bias <- mean(bias, na.rm = na.rm) } } else { nexp <- as.numeric(dim(exp)[dat_dim]) nobs <- as.numeric(dim(obs)[dat_dim]) - bias <- array(dim = c(dim(exp)[1], nexp = nexp, nobs = nobs)) + bias <- array(dim = c(dim(exp)[time_dim], nexp = nexp, nobs = nobs)) for (i in 1:nexp) { for (j in 1:nobs) { - exp_data <- exp[ , i] - obs_data <- obs[ , j] - if (is.null(dim(exp_data))) dim(exp_data) <- c(dim(exp)[1]) - if (is.null(dim(obs_data))) dim(obs_data) <- c(dim(obs)[1]) - bias[ , i, j] <- exp_data - obs_data + bias[, i, j] <- exp[, i] - obs[, j] } } - if (isTRUE(absolute)){ + if (isTRUE(absolute)) { bias <- abs(bias) } - if (isTRUE(time_mean)){ + if (isTRUE(time_mean)) { bias <- MeanDims(bias, time_dim, na.rm = na.rm) } } diff --git a/man/AbsBiasSS.Rd b/man/AbsBiasSS.Rd index 89fab8a..029101d 100644 --- a/man/AbsBiasSS.Rd +++ b/man/AbsBiasSS.Rd @@ -72,7 +72,8 @@ of reference forecasts are the climatological forecast (average of the observations), a previous model version, or another model. It is computed as \code{AbsBiasSS = 1 - AbsBias_exp / AbsBias_ref}. The statistical significance is obtained based on a Random Walk test at the 95% confidence level (DelSole -and Tippett, 2016). +and Tippett, 2016). If there is more than one dataset, the result will be +computed for each pair of exp and obs data. } \examples{ exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, sdate = 50)) diff --git a/man/Bias.Rd b/man/Bias.Rd index fb0fafc..2a02f2d 100644 --- a/man/Bias.Rd +++ b/man/Bias.Rd @@ -24,8 +24,8 @@ dimension.} dimension. The dimensions must be the same as 'exp' except 'memb_dim' and 'dat_dim'.} -\item{time_dim}{A logical value indicating whether to compute the temporal -mean of the bias. The default value is TRUE.} +\item{time_dim}{A character string indicating the name of the time dimension. +The default value is 'sdate'.} \item{memb_dim}{A character string indicating the name of the member dimension to compute the ensemble mean; it should be set to NULL if the parameter @@ -41,13 +41,16 @@ kept (FALSE) for computation. The default value is FALSE.} \item{absolute}{A logical value indicating whether to compute the absolute bias. The default value is FALSE.} +\item{time_mean}{A logical value indicating whether to compute the temporal +mean of the bias. The default value is TRUE.} + \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} } \value{ -A numerical array of bias with dimensions nexp, nobs and the rest dimensions -of 'exp' except 'time_dim' (if time_mean = T). nexp is the number of -experiment (i.e., 'dat_dim' in exp), and nobs is the number of observation +A numerical array of bias with dimensions c(nexp, nobs, the rest dimensions of +'exp' except 'time_dim' (if time_mean = T) and 'memb_dim'). nexp is the number +of experiment (i.e., 'dat_dim' in exp), and nobs is the number of observation (i.e., 'dat_dim' in obs). If dat_dim is NULL, nexp and nobs are omitted. } \description{ @@ -55,7 +58,9 @@ The Mean Bias or Mean Error (Wilks, 2011) is defined as the mean difference between the ensemble mean forecast and the observations. It is a deterministic metric. Positive values indicate that the forecasts are on average too high and negative values indicate that the forecasts are on average too low. -It also allows to compute the Absolute Mean Bias. +It also allows to compute the Absolute Mean Bias or bias without temporal +mean. If there is more than one dataset, the result will be computed for each +pair of exp and obs data. } \examples{ exp <- array(rnorm(1000), dim = c(dat = 1, lat = 3, lon = 5, member = 10, sdate = 50)) diff --git a/tests/testthat/test-AbsBiasSS.R b/tests/testthat/test-AbsBiasSS.R index 2db29b5..6728726 100644 --- a/tests/testthat/test-AbsBiasSS.R +++ b/tests/testthat/test-AbsBiasSS.R @@ -34,6 +34,18 @@ obs4 <- array(rnorm(60), dim = c(sdate = 10, lat = 2, dataset = 3)) set.seed(3) ref4 <- array(rnorm(40), dim = c(sdate = 10, lat = 2, dataset = 2)) +# dat5 +set.seed(1) +exp5 <- array(rnorm(10), dim = c(sdate = 10)) +exp5_1 <- array(exp5, dim = c(sdate = 10, dataset = 1)) +set.seed(2) +obs5 <- array(rnorm(10), dim = c(sdate = 10)) +obs5_1 <- array(obs5, dim = c(sdate = 10, dataset = 1)) +obs5_2 <- obs5_1 +obs5_2[c(1,3)] <- NA +set.seed(3) +ref5 <- array(rnorm(10), dim = c(sdate = 10)) + ############################################## test_that("1. Input checks", { # exp and obs (1) @@ -129,10 +141,18 @@ test_that("2. Output checks: dat1", { dim(AbsBiasSS(exp1, obs1)$biasSS), c(lat = 2) ) + expect_equal( + dim(AbsBiasSS(exp1, obs1)$sign), + c(lat = 2) + ) expect_equal( dim(AbsBiasSS(exp1, obs1, ref1)$biasSS), c(lat = 2) ) + expect_equal( + dim(AbsBiasSS(exp1, obs1, ref1)$sign), + c(lat = 2) + ) expect_equal( as.vector(AbsBiasSS(exp1, obs1)$biasSS), c(-0.3103594, 0.0772921), @@ -238,3 +258,54 @@ test_that("5. Output checks: dat4", { }) +############################################## +test_that("6. Output checks: dat5", { + expect_equal( + dim(AbsBiasSS(exp5, obs5)$biasSS), + NULL + ) + expect_equal( + dim(AbsBiasSS(exp5, obs5)$sign), + NULL + ) + expect_equal( + as.vector(AbsBiasSS(exp5, obs5)$biasSS), + -0.3103594, + tolerance = 0.0001 + ) + expect_equal( + as.vector(AbsBiasSS(exp5, obs5)$sign), + FALSE + ) + expect_equal( + as.vector(AbsBiasSS(exp5, obs5, ref5)$biasSS), + -0.07871642, + tolerance = 0.0001 + ) + #5_1 + expect_equal( + dim(AbsBiasSS(exp5_1, obs5_1, dat_dim = 'dataset')$biasSS), + c(nexp = 1, nobs = 1) + ) + expect_equal( + dim(AbsBiasSS(exp5_1, obs5_1, dat_dim = 'dataset')$sign), + c(nexp = 1, nobs = 1) + ) + expect_equal( + as.vector(AbsBiasSS(exp5_1, obs5_1, dat_dim = 'dataset')$biasSS), + as.vector(AbsBiasSS(exp5, obs5)$biasSS) + ) + expect_equal( + as.vector(AbsBiasSS(exp5_1, obs5_1, ref = ref5, dat_dim = 'dataset')$biasSS), + as.vector(AbsBiasSS(exp5, obs5, ref = ref5)$biasSS) + ) +# #5_2: NA test +# expect_equal( +# as.vector(AbsBiasSS(exp5_1, obs5_2, dat_dim = 'dataset')$biasSS), +# NA +# ) +# expect_equal( +# as.vector(AbsBiasSS(exp5_1, obs5_2, dat_dim = 'dataset', na.rm = T)$biasSS), +# +# ) +}) diff --git a/tests/testthat/test-Bias.R b/tests/testthat/test-Bias.R index 537625b..1d81817 100644 --- a/tests/testthat/test-Bias.R +++ b/tests/testthat/test-Bias.R @@ -20,6 +20,14 @@ exp3 <- array(rnorm(80), dim = c(member = 2, sdate = 10, lat = 2, dataset = 2)) set.seed(2) obs3 <- array(rnorm(60), dim = c(sdate = 10, lat = 2, dataset = 3)) +# dat4 +set.seed(1) +exp4 <- array(rnorm(10), dim = c(sdate = 10)) +exp4_1 <- array(exp4, dim = c(sdate = 10, dataset = 1)) +set.seed(2) +obs4 <- array(rnorm(10), dim = c(sdate = 10)) +obs4_1 <- array(obs4, dim = c(sdate = 10, dataset = 1)) + ############################################## test_that("1. Input checks", { # exp and obs (1) @@ -179,4 +187,40 @@ test_that("4. Output checks: dat3", { }) +############################################## +test_that("5. Output checks: dat4", { + expect_equal( + dim(Bias(exp4, obs4)), + NULL + ) + expect_equal( + dim(Bias(exp4, obs4, time_mean = F)), + c(sdate = 10) + ) + expect_equal( + as.vector(Bias(exp4, obs4, time_mean = F)), + as.vector(exp4 - obs4) + ) + expect_equal( + as.vector(Bias(exp4, obs4, time_mean = F, absolute = T)), + abs(as.vector(exp4 - obs4)) + ) + expect_equal( + as.vector(Bias(exp4, obs4, absolute = T)), + mean(abs(as.vector(exp4 - obs4))) + ) + + expect_equal( + dim(Bias(exp4_1, obs4_1, dat_dim = 'dataset')), + c(nexp = 1, nobs = 1) + ) + expect_equal( + dim(Bias(exp4_1, obs4_1, dat_dim = 'dataset', time_mean = F)), + c(sdate = 10, nexp = 1, nobs = 1) + ) + expect_equal( + as.vector(Bias(exp4_1, obs4_1, dat_dim = 'dataset', time_mean = F)), + as.vector(Bias(exp4, obs4, time_mean = F)) + ) +}) -- GitLab From 785080990af0f97b8665411895b04b660fabc9e2 Mon Sep 17 00:00:00 2001 From: aho Date: Wed, 14 Sep 2022 17:34:01 +0200 Subject: [PATCH 109/140] Reformatting --- man/s2dv-package.Rd | 10 +--------- man/sampleDepthData.Rd | 6 ++++-- man/sampleMap.Rd | 6 ++++-- man/sampleTimeSeries.Rd | 6 ++++-- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/man/s2dv-package.Rd b/man/s2dv-package.Rd index ffb6783..3c98a95 100644 --- a/man/s2dv-package.Rd +++ b/man/s2dv-package.Rd @@ -6,15 +6,7 @@ \alias{s2dv-package} \title{s2dv: A Set of Common Tools for Seasonal to Decadal Verification} \description{ -The advanced version of package 's2dverification'. It is - intended for 'seasonal to decadal' (s2d) climate forecast verification, but - it can also be used in other kinds of forecasts or general climate analysis. - This package is specially designed for the comparison between the experimental - and observational datasets. The functionality of the included functions covers - from data retrieval, data post-processing, skill scores against observation, - to visualization. Compared to 's2dverification', 's2dv' is more compatible - with the package 'startR', able to use multiple cores for computation and - handle multi-dimensional arrays with a higher flexibility. +The advanced version of package 's2dverification'. It is intended for 'seasonal to decadal' (s2d) climate forecast verification, but it can also be used in other kinds of forecasts or general climate analysis. This package is specially designed for the comparison between the experimental and observational datasets. The functionality of the included functions covers from data retrieval, data post-processing, skill scores against observation, to visualization. Compared to 's2dverification', 's2dv' is more compatible with the package 'startR', able to use multiple cores for computation and handle multi-dimensional arrays with a higher flexibility. } \references{ \url{https://earth.bsc.es/gitlab/es/s2dv/} diff --git a/man/sampleDepthData.Rd b/man/sampleDepthData.Rd index 77e4a7a..47e2f1b 100644 --- a/man/sampleDepthData.Rd +++ b/man/sampleDepthData.Rd @@ -5,7 +5,8 @@ \alias{sampleDepthData} \title{Sample of Experimental Data for Forecast Verification In Function Of Latitudes And Depths} -\format{The data set provides with a variable named 'sampleDepthData'.\cr\cr +\format{ +The data set provides with a variable named 'sampleDepthData'.\cr\cr sampleDepthData$exp is an array that contains the experimental data and the dimension meanings and values are:\cr @@ -18,7 +19,8 @@ but in this sample is not defined (NULL).\cr\cr sampleDepthData$depths is an array with the 7 longitudes covered by the data.\cr\cr -sampleDepthData$lat is an array with the 21 latitudes covered by the data.\cr\cr} +sampleDepthData$lat is an array with the 21 latitudes covered by the data.\cr\cr +} \usage{ data(sampleDepthData) } diff --git a/man/sampleMap.Rd b/man/sampleMap.Rd index eaf8aa5..e4ec5a5 100644 --- a/man/sampleMap.Rd +++ b/man/sampleMap.Rd @@ -4,7 +4,8 @@ \name{sampleMap} \alias{sampleMap} \title{Sample Of Observational And Experimental Data For Forecast Verification In Function Of Longitudes And Latitudes} -\format{The data set provides with a variable named 'sampleMap'.\cr\cr +\format{ +The data set provides with a variable named 'sampleMap'.\cr\cr sampleMap$mod is an array that contains the experimental data and the dimension meanings and values are:\cr c(# of experimental datasets, # of members, # of starting dates, # of lead-times, # of latitudes, # of longitudes)\cr @@ -16,7 +17,8 @@ sampleMap$obs is an array that contains the observational data and the dimension sampleMap$lat is an array with the 2 latitudes covered by the data (see examples on Load() for details on why such low resolution).\cr\cr - sampleMap$lon is an array with the 3 longitudes covered by the data (see examples on Load() for details on why such low resolution).} + sampleMap$lon is an array with the 3 longitudes covered by the data (see examples on Load() for details on why such low resolution). +} \usage{ data(sampleMap) } diff --git a/man/sampleTimeSeries.Rd b/man/sampleTimeSeries.Rd index 05a8e79..7f058e2 100644 --- a/man/sampleTimeSeries.Rd +++ b/man/sampleTimeSeries.Rd @@ -4,7 +4,8 @@ \name{sampleTimeSeries} \alias{sampleTimeSeries} \title{Sample Of Observational And Experimental Data For Forecast Verification As Area Averages} -\format{The data set provides with a variable named 'sampleTimeSeries'.\cr\cr +\format{ +The data set provides with a variable named 'sampleTimeSeries'.\cr\cr sampleTimeSeries$mod is an array that contains the experimental data and the dimension meanings and values are:\cr c(# of experimental datasets, # of members, # of starting dates, # of lead-times)\cr @@ -16,7 +17,8 @@ sampleTimeSeries$obs is an array that contains the observational data and the di sampleTimeSeries$lat is an array with the 2 latitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution).\cr\cr -sampleTimeSeries$lon is an array with the 3 longitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution).} +sampleTimeSeries$lon is an array with the 3 longitudes covered by the data that was area averaged to calculate the time series (see examples on Load() for details on why such low resolution). +} \usage{ data(sampleTimeSeries) } -- GitLab From 768e785696c1dde4ccffe6c44532b3add4976693 Mon Sep 17 00:00:00 2001 From: aho Date: Wed, 14 Sep 2022 17:42:37 +0200 Subject: [PATCH 110/140] Add unit test for NA --- tests/testthat/test-Bias.R | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-Bias.R b/tests/testthat/test-Bias.R index 1d81817..842ecc2 100644 --- a/tests/testthat/test-Bias.R +++ b/tests/testthat/test-Bias.R @@ -27,6 +27,8 @@ exp4_1 <- array(exp4, dim = c(sdate = 10, dataset = 1)) set.seed(2) obs4 <- array(rnorm(10), dim = c(sdate = 10)) obs4_1 <- array(obs4, dim = c(sdate = 10, dataset = 1)) +obs4_2 <- obs4_1 +obs4_2[c(1, 3)] <- NA ############################################## test_that("1. Input checks", { @@ -222,5 +224,17 @@ test_that("5. Output checks: dat4", { as.vector(Bias(exp4_1, obs4_1, dat_dim = 'dataset', time_mean = F)), as.vector(Bias(exp4, obs4, time_mean = F)) ) - + # 4_2: NA + expect_equal( + as.vector(Bias(exp4_1, obs4_2, dat_dim = 'dataset')), + as.numeric(NA) + ) + expect_equal( + as.vector(Bias(exp4_1, obs4_2, dat_dim = 'dataset', time_mean = F))[c(1, 3)], + as.numeric(c(NA, NA)) + ) + expect_equal( + as.vector(Bias(exp4_1, obs4_2, dat_dim = 'dataset', time_mean = F))[c(2, 4:10)], + as.vector(Bias(exp4, obs4, time_mean = F))[c(2, 4:10)] + ) }) -- GitLab From 2de37faf3afbe9dd8178d9084b656548b449e94f Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 15 Sep 2022 10:54:02 +0200 Subject: [PATCH 111/140] Correct code for na.rm = T --- R/AbsBiasSS.R | 45 +-------------------------------- tests/testthat/test-AbsBiasSS.R | 18 ++++++------- 2 files changed, 10 insertions(+), 53 deletions(-) diff --git a/R/AbsBiasSS.R b/R/AbsBiasSS.R index d01b09e..effadb6 100644 --- a/R/AbsBiasSS.R +++ b/R/AbsBiasSS.R @@ -214,54 +214,11 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, # exp and obs: [sdate, (dat_dim)] # ref: [sdate, (dat_dim)] or NULL - if (is.null(dat_dim)) { - if (isTRUE(na.rm)) { - if (is.null(ref)) { - good_values <- !is.na(exp) & !is.na(obs) - exp <- exp[good_values] - obs <- obs[good_values] - } else { - good_values <- !is.na(exp) & !is.na(ref) & !is.na(obs) - exp <- exp[good_values] - ref <- ref[good_values] - obs <- obs[good_values] - } - } - } else { + if (!is.null(dat_dim)) { nexp <- as.numeric(dim(exp)[dat_dim]) nobs <- as.numeric(dim(obs)[dat_dim]) - if (isTRUE(na.rm)) { - if (is.null(ref)) { - for (i in 1:nexp) { - for (j in 1:nobs) { - good_values <- !is.na(exp[, i]) & !is.na(obs[, j]) - exp[, i] <- exp[, i][good_values] - obs[, j] <- obs[, j][good_values] - } - } - } else if (length(dim(ref)) == 1) { #ref: [sdate] - for (i in 1:nexp) { - for (j in 1:nobs) { - good_values <- !is.na(exp[, i]) & !is.na(ref) & !is.na(obs[, j]) - exp[, i] <- exp[, i][good_values] - obs[, j] <- obs[, j][good_values] - ref <- ref[good_values] - } - } - } else { - for (i in 1:nexp) { - for (j in 1:nobs) { - good_values <- !is.na(exp[, i]) & !is.na(ref[, i]) & !is.na(obs[, j]) - exp[, i] <- exp[, i][good_values] - obs[, j] <- obs[, j][good_values] - ref[, i] <- ref[, i][good_values] - } - } - } - } } - ## Bias of the exp bias_exp <- .Bias(exp = exp, obs = obs, dat_dim = dat_dim, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) diff --git a/tests/testthat/test-AbsBiasSS.R b/tests/testthat/test-AbsBiasSS.R index 6728726..4d67f4f 100644 --- a/tests/testthat/test-AbsBiasSS.R +++ b/tests/testthat/test-AbsBiasSS.R @@ -299,13 +299,13 @@ test_that("6. Output checks: dat5", { as.vector(AbsBiasSS(exp5_1, obs5_1, ref = ref5, dat_dim = 'dataset')$biasSS), as.vector(AbsBiasSS(exp5, obs5, ref = ref5)$biasSS) ) -# #5_2: NA test -# expect_equal( -# as.vector(AbsBiasSS(exp5_1, obs5_2, dat_dim = 'dataset')$biasSS), -# NA -# ) -# expect_equal( -# as.vector(AbsBiasSS(exp5_1, obs5_2, dat_dim = 'dataset', na.rm = T)$biasSS), -# -# ) + #5_2: NA test + expect_equal( + as.vector(AbsBiasSS(exp5_1, obs5_2, dat_dim = 'dataset')$biasSS), + as.numeric(NA) + ) + expect_equal( + as.vector(AbsBiasSS(exp5_1, obs5_2, dat_dim = 'dataset', na.rm = T)$biasSS), + c(-0.4636772) + ) }) -- GitLab From 0f01db04200bbacfcaebbfbd63aa2c9fff583cde Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 15 Sep 2022 15:56:04 +0200 Subject: [PATCH 112/140] Correct case na.rm = T for all possibilities --- R/AbsBiasSS.R | 102 +++++++++++++++----------------- tests/testthat/test-AbsBiasSS.R | 10 +++- 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/R/AbsBiasSS.R b/R/AbsBiasSS.R index effadb6..b5dbe77 100644 --- a/R/AbsBiasSS.R +++ b/R/AbsBiasSS.R @@ -201,7 +201,6 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, output <- Apply(data, target_dims = target_dims, fun = .AbsBiasSS, - time_dim = time_dim, dat_dim = dat_dim, na.rm = na.rm, ncores = ncores) @@ -209,72 +208,69 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, return(output) } -.AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', dat_dim = NULL, na.rm = FALSE) { +.AbsBiasSS <- function(exp, obs, ref = NULL, dat_dim = NULL, na.rm = FALSE) { + # exp and obs: [sdate] + # ref: [sdate] or NULL - # exp and obs: [sdate, (dat_dim)] - # ref: [sdate, (dat_dim)] or NULL - - if (!is.null(dat_dim)) { - nexp <- as.numeric(dim(exp)[dat_dim]) - nobs <- as.numeric(dim(obs)[dat_dim]) - } - - ## Bias of the exp - bias_exp <- .Bias(exp = exp, obs = obs, dat_dim = dat_dim, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) - - ## Bias of the ref if (is.null(dat_dim)) { - if (is.null(ref)) { - ref <- mean(obs, na.rm = na.rm) + nexp <- 1 + nobs <- 1 + exp <- InsertDim(exp, posdim = 2, lendim = 1, name = 'dataset') + obs <- InsertDim(obs, posdim = 2, lendim = 1, name = 'dataset') + if (!is.null(ref)) { + ref <- InsertDim(ref, posdim = 2, lendim = 1, name = 'dataset') } - bias_ref <- .Bias(exp = ref, obs = obs, dat_dim = dat_dim, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) - # bias_ref should be a number - } else { - if (is.null(ref)) { - ref <- MeanDims(obs, time_dim, na.rm = na.rm) - # ref should be [dat] - bias_ref <- array(dim = c(nobs = nobs)) - for (j in 1:nobs) { - bias_ref[j] <- .Bias(exp = ref[j], obs = obs[, j], dat_dim = NULL, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) - } - # bias_ref should be [nobs] - } else if (length(dim(ref)) == 1) { # ref: [sdate] - bias_ref <- array(dim = c(nobs = nobs)) - for (j in 1:nobs) { - bias_ref[j] <- .Bias(exp = ref, obs = obs[, j], dat_dim = NULL, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) - } - # bias_ref should be [nobs] + nexp <- as.numeric(dim(exp)[dat_dim]) + nobs <- as.numeric(dim(obs)[dat_dim]) + if (length(dim(ref)) == 1) { # ref: [sdate] + ref_dat_dim <- FALSE } else { - bias_ref <- .Bias(exp = ref, obs = obs, dat_dim = dat_dim, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) - # bias_ref should be [nexp, nobs] + ref_dat_dim <- TRUE } } - ## Skill score and significance - if (is.null(dat_dim)) { - biasSS <- 1 - bias_exp / bias_ref - sign <- .RandomWalkTest(skill_A = bias_exp, skill_B = bias_ref)$signif + biasSS <- array(dim = c(nexp = nexp, nobs = nobs)) + sign <- array(dim = c(nexp = nexp, nobs = nobs)) - } else { - biasSS <- array(dim = c(nexp = nexp, nobs = nobs)) - sign <- array(dim = c(nexp = nexp, nobs = nobs)) - if (length(dim(bias_ref)) == 1) { # ref: [sdate] - for (i in 1:nexp) { - for (j in 1:nobs) { - biasSS[i, j] <- 1 - bias_exp[i, j] / bias_ref[j] - sign[i, j] <- .RandomWalkTest(skill_A = bias_exp[i, j], skill_B = bias_ref[j])$signif - } - } + for (i in 1:nexp) { + exp_data <- exp[, i] + if (isTRUE(ref_dat_dim)) { + ref_data <- ref[, i] } else { - for (i in 1:nexp) { - for (j in 1:nobs) { - biasSS[i, j] <- 1 - bias_exp[i, j] / bias_ref[i, j] - sign[i, j] <- .RandomWalkTest(skill_A = bias_exp[i, j], skill_B = bias_ref[i, j])$signif + ref_data <- ref + } + for (j in 1:nobs) { + obs_data <- obs[, j] + if (isTRUE(na.rm)) { + if (is.null(ref)) { + good_values <- !is.na(exp_data) & !is.na(obs_data) + exp_data <- exp_data[good_values] + obs_data <- obs_data[good_values] + } else { + good_values <- !is.na(exp_data) & !is.na(ref_data) & !is.na(obs_data) + exp_data <- exp_data[good_values] + ref_data <- ref_data[good_values] + obs_data <- obs_data[good_values] } } + ## Bias of the exp + bias_exp <- .Bias(exp = exp_data, obs = obs_data, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + ## Bias of the ref + if (is.null(ref)) { ## Climatological forecast + ref_data <- mean(obs_data, na.rm = na.rm) + } + bias_ref <- .Bias(exp = ref_data, obs = obs_data, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + biasSS[i, j] <- 1 - bias_exp / bias_ref + sign[i, j] <- .RandomWalkTest(skill_A = bias_exp, skill_B = bias_ref)$signif } } + if (is.null(dat_dim)) { + dim(biasSS) <- NULL + dim(sign) <- NULL + } + + return(list(biasSS = biasSS, sign = sign)) } diff --git a/tests/testthat/test-AbsBiasSS.R b/tests/testthat/test-AbsBiasSS.R index 4d67f4f..a1fff9c 100644 --- a/tests/testthat/test-AbsBiasSS.R +++ b/tests/testthat/test-AbsBiasSS.R @@ -38,6 +38,8 @@ ref4 <- array(rnorm(40), dim = c(sdate = 10, lat = 2, dataset = 2)) set.seed(1) exp5 <- array(rnorm(10), dim = c(sdate = 10)) exp5_1 <- array(exp5, dim = c(sdate = 10, dataset = 1)) +exp5_2 <- exp5_1 +exp5_2[c(1,1)] <- NA set.seed(2) obs5 <- array(rnorm(10), dim = c(sdate = 10)) obs5_1 <- array(obs5, dim = c(sdate = 10, dataset = 1)) @@ -306,6 +308,12 @@ test_that("6. Output checks: dat5", { ) expect_equal( as.vector(AbsBiasSS(exp5_1, obs5_2, dat_dim = 'dataset', na.rm = T)$biasSS), - c(-0.4636772) + c(-0.4636772), + tolerance = 0.0001 + ) + expect_equal( + as.vector(AbsBiasSS(exp5_2, obs5_2, ref = ref, dat_dim = 'dataset', na.rm = T)$biasSS), + c(-0.2542055), + tolerance = 0.0001 ) }) -- GitLab From 82c582393e763407199f0eacbf7c816e71987932 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 15 Sep 2022 16:14:05 +0200 Subject: [PATCH 113/140] Fix pipeline --- R/AbsBiasSS.R | 2 ++ tests/testthat/test-AbsBiasSS.R | 30 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/R/AbsBiasSS.R b/R/AbsBiasSS.R index b5dbe77..7c9e06d 100644 --- a/R/AbsBiasSS.R +++ b/R/AbsBiasSS.R @@ -220,6 +220,7 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, if (!is.null(ref)) { ref <- InsertDim(ref, posdim = 2, lendim = 1, name = 'dataset') } + ref_dat_dim <- FALSE } else { nexp <- as.numeric(dim(exp)[dat_dim]) nobs <- as.numeric(dim(obs)[dat_dim]) @@ -261,6 +262,7 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, ref_data <- mean(obs_data, na.rm = na.rm) } bias_ref <- .Bias(exp = ref_data, obs = obs_data, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + ## Skill score and significance biasSS[i, j] <- 1 - bias_exp / bias_ref sign[i, j] <- .RandomWalkTest(skill_A = bias_exp, skill_B = bias_ref)$signif } diff --git a/tests/testthat/test-AbsBiasSS.R b/tests/testthat/test-AbsBiasSS.R index a1fff9c..cfeeb09 100644 --- a/tests/testthat/test-AbsBiasSS.R +++ b/tests/testthat/test-AbsBiasSS.R @@ -301,19 +301,19 @@ test_that("6. Output checks: dat5", { as.vector(AbsBiasSS(exp5_1, obs5_1, ref = ref5, dat_dim = 'dataset')$biasSS), as.vector(AbsBiasSS(exp5, obs5, ref = ref5)$biasSS) ) - #5_2: NA test - expect_equal( - as.vector(AbsBiasSS(exp5_1, obs5_2, dat_dim = 'dataset')$biasSS), - as.numeric(NA) - ) - expect_equal( - as.vector(AbsBiasSS(exp5_1, obs5_2, dat_dim = 'dataset', na.rm = T)$biasSS), - c(-0.4636772), - tolerance = 0.0001 - ) - expect_equal( - as.vector(AbsBiasSS(exp5_2, obs5_2, ref = ref, dat_dim = 'dataset', na.rm = T)$biasSS), - c(-0.2542055), - tolerance = 0.0001 - ) + #5_2: NA test + expect_equal( + as.vector(AbsBiasSS(exp5_1, obs5_2, dat_dim = 'dataset')$biasSS), + as.numeric(NA) + ) + expect_equal( + as.vector(AbsBiasSS(exp5_1, obs5_2, dat_dim = 'dataset', na.rm = T)$biasSS), + c(-0.4636772), + tolerance = 0.0001 + ) + expect_equal( + as.vector(AbsBiasSS(exp5_2, obs5_2, ref = ref5, dat_dim = 'dataset', na.rm = T)$biasSS), + c(0.08069355), + tolerance = 0.0001 + ) }) -- GitLab From d3a5928cd63ed91dd3d4193af73f8fa56707ed89 Mon Sep 17 00:00:00 2001 From: aho Date: Fri, 16 Sep 2022 18:40:33 +0200 Subject: [PATCH 114/140] Development for PlotMostLikelyQuantileMap. Layout is designed for individual colorbar. --- R/PlotLayout.R | 54 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/R/PlotLayout.R b/R/PlotLayout.R index 742478e..665daf8 100644 --- a/R/PlotLayout.R +++ b/R/PlotLayout.R @@ -545,7 +545,54 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, fsu <- figure_size_units <- 10 # unitless widths <- rep(fsu, ncol) heights <- rep(fsu, nrow) - n_figures <- nrow * ncol + # Useless +# n_figures <- nrow * ncol + + if (all(fun == 'PlotMostLikelyQuantileMap')) { + # Adjust layout + if (!'cat_dim' %in% names(list(...))) { + cat_dim <- 'bin' #default + } else { + cat_dim <- list(...)$cat_dim + } + nmap <- dim(var[[1]])[cat_dim] + + unit_layout <- matrix(c(rep(1, nmap),2:(nmap + 1)), 2, nmap, byrow = TRUE) + real_layout <- mat_layout[1, 1] + unit_layout - 1 + + if (layout_by_rows) { + if (ncol > 1) { + for (i_col in 2:ncol) { + real_layout <- cbind(real_layout, real_layout + nmap + 1) + } + } + if (nrow > 1) { + for (i_row in 2:nrow) { + real_layout <- rbind(real_layout, real_layout + ((nmap + 1) * ncol)) + } + } + } else { + if (nrow > 1) { + for (i_row in 2:nrow) { + real_layout <- rbind(real_layout, real_layout + nmap + 1) + } + } + if (ncol > 1) { + for (i_col in 2:ncol) { + real_layout <- cbind(real_layout, real_layout + ((nmap + 1) * nrow)) + } + } + } + mat_layout <- real_layout + + # Adjust widths and heights + ## The current widths and heights should be matrix(10, nrow, ncol) + ## unit_width is default 1 + widths <- rep(widths, nmap) + unit_height <- c(6, 1.5) / 7.5 * 10 # 6 for figure, 1.5 for colorbar in PlotMostLikelyQuantileMap + heights <- rep(unit_height, length(heights)) + } + if (length(row_titles) > 0) { mat_layout <- cbind(rep(0, dim(mat_layout)[1]), mat_layout) widths <- c(((subtitle_cex + subtitle_margin / 2) * cs / device_size[1]) * ncol * fsu, widths) @@ -568,7 +615,8 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, mat_layout <- cbind(mat_layout, rep(1, dim(mat_layout)[1])) widths <- c(widths, round(bar_scale * 3 * ncol)) } - n_figures <- n_figures + 1 + # Useless +# n_figures <- n_figures + 1 } if (toptitle != '') { mat_layout <- rbind(rep(0, dim(mat_layout)[2]), mat_layout) @@ -660,7 +708,7 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, # Do the plot fun_args <- c(list(y, toptitle = titles[plot_number]), list(...), special_args[[array_number]]) - funct <- fun[[array_number]] +# funct <- fun[[array_number]] if (fun[[array_number]] %in% c('PlotEquiMap', 'PlotStereoMap', 'PlotSection')) { fun_args <- c(fun_args, list(brks = colorbar$brks, cols = colorbar$cols, col_inf = colorbar$col_inf, -- GitLab From 49a2d17c004dbe9f5bab91affa84b9c20911aed2 Mon Sep 17 00:00:00 2001 From: aho Date: Mon, 19 Sep 2022 09:40:50 +0200 Subject: [PATCH 115/140] Trivial format and comment adjustment --- R/AbsBiasSS.R | 6 ++++-- tests/testthat/test-AbsBiasSS.R | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/R/AbsBiasSS.R b/R/AbsBiasSS.R index 7c9e06d..0838f25 100644 --- a/R/AbsBiasSS.R +++ b/R/AbsBiasSS.R @@ -209,9 +209,10 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, } .AbsBiasSS <- function(exp, obs, ref = NULL, dat_dim = NULL, na.rm = FALSE) { - # exp and obs: [sdate] - # ref: [sdate] or NULL + # exp and obs: [sdate, (dat_dim)] + # ref: [sdate, (dat_dim)] or NULL + # Adjust exp, obs, ref to have dat_dim temporarily if (is.null(dat_dim)) { nexp <- 1 nobs <- 1 @@ -243,6 +244,7 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, } for (j in 1:nobs) { obs_data <- obs[, j] + if (isTRUE(na.rm)) { if (is.null(ref)) { good_values <- !is.na(exp_data) & !is.na(obs_data) diff --git a/tests/testthat/test-AbsBiasSS.R b/tests/testthat/test-AbsBiasSS.R index cfeeb09..b2e70f3 100644 --- a/tests/testthat/test-AbsBiasSS.R +++ b/tests/testthat/test-AbsBiasSS.R @@ -39,12 +39,12 @@ set.seed(1) exp5 <- array(rnorm(10), dim = c(sdate = 10)) exp5_1 <- array(exp5, dim = c(sdate = 10, dataset = 1)) exp5_2 <- exp5_1 -exp5_2[c(1,1)] <- NA +exp5_2[1] <- NA set.seed(2) obs5 <- array(rnorm(10), dim = c(sdate = 10)) obs5_1 <- array(obs5, dim = c(sdate = 10, dataset = 1)) obs5_2 <- obs5_1 -obs5_2[c(1,3)] <- NA +obs5_2[c(1, 3)] <- NA set.seed(3) ref5 <- array(rnorm(10), dim = c(sdate = 10)) -- GitLab From 450df59b98edd314c37dfda315357d3dbcfc55d0 Mon Sep 17 00:00:00 2001 From: aho Date: Tue, 20 Sep 2022 12:04:15 +0200 Subject: [PATCH 116/140] PlotMostLikelyQuantileMap works with PlotLayout (only drawleg = 'S'); colorbar is shared. --- R/PlotLayout.R | 133 +++++++++++++++++++++++++------------------------ 1 file changed, 67 insertions(+), 66 deletions(-) diff --git a/R/PlotLayout.R b/R/PlotLayout.R index 665daf8..e5ae980 100644 --- a/R/PlotLayout.R +++ b/R/PlotLayout.R @@ -540,7 +540,18 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, title_margin <- 0.5 * title_cex * title_margin_scale subtitle_cex <- 1.5 * subtitle_scale subtitle_margin <- 0.5 * sqrt(nrow * ncol) * subtitle_cex * subtitle_margin_scale - mat_layout <- 1:(nrow * ncol) + ifelse(drawleg != FALSE, 1, 0) + mat_layout <- 1:(nrow * ncol) + if (drawleg != FALSE) { + if (fun == 'PlotMostLikelyQuantileMap') { #multi_colorbar + multi_colorbar <- TRUE + cat_dim <- list(...)$cat_dim + nmap <- as.numeric(dim(var[[1]])[cat_dim]) + mat_layout <- mat_layout + nmap + } else { + multi_colorbar <- FALSE + mat_layout <- mat_layout + 1 + } + } mat_layout <- matrix(mat_layout, nrow, ncol, byrow = layout_by_rows) fsu <- figure_size_units <- 10 # unitless widths <- rep(fsu, ncol) @@ -548,65 +559,23 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, # Useless # n_figures <- nrow * ncol - if (all(fun == 'PlotMostLikelyQuantileMap')) { - # Adjust layout - if (!'cat_dim' %in% names(list(...))) { - cat_dim <- 'bin' #default - } else { - cat_dim <- list(...)$cat_dim - } - nmap <- dim(var[[1]])[cat_dim] - - unit_layout <- matrix(c(rep(1, nmap),2:(nmap + 1)), 2, nmap, byrow = TRUE) - real_layout <- mat_layout[1, 1] + unit_layout - 1 - - if (layout_by_rows) { - if (ncol > 1) { - for (i_col in 2:ncol) { - real_layout <- cbind(real_layout, real_layout + nmap + 1) - } - } - if (nrow > 1) { - for (i_row in 2:nrow) { - real_layout <- rbind(real_layout, real_layout + ((nmap + 1) * ncol)) - } - } - } else { - if (nrow > 1) { - for (i_row in 2:nrow) { - real_layout <- rbind(real_layout, real_layout + nmap + 1) - } - } - if (ncol > 1) { - for (i_col in 2:ncol) { - real_layout <- cbind(real_layout, real_layout + ((nmap + 1) * nrow)) - } - } - } - mat_layout <- real_layout - - # Adjust widths and heights - ## The current widths and heights should be matrix(10, nrow, ncol) - ## unit_width is default 1 - widths <- rep(widths, nmap) - unit_height <- c(6, 1.5) / 7.5 * 10 # 6 for figure, 1.5 for colorbar in PlotMostLikelyQuantileMap - heights <- rep(unit_height, length(heights)) - } - - if (length(row_titles) > 0) { - mat_layout <- cbind(rep(0, dim(mat_layout)[1]), mat_layout) - widths <- c(((subtitle_cex + subtitle_margin / 2) * cs / device_size[1]) * ncol * fsu, widths) - } - if (length(col_titles) > 0) { - mat_layout <- rbind(rep(0, dim(mat_layout)[2]), mat_layout) - heights <- c(((subtitle_cex + subtitle_margin) * cs / device_size[2]) * nrow * fsu, heights) - } if (drawleg != FALSE) { if (drawleg == 'N') { mat_layout <- rbind(rep(1, dim(mat_layout)[2]), mat_layout) heights <- c(round(bar_scale * 2 * nrow), heights) } else if (drawleg == 'S') { - mat_layout <- rbind(mat_layout, rep(1, dim(mat_layout)[2])) + if (multi_colorbar) { + new_mat_layout <- c() + for (i_col in 1:ncol) { + new_mat_layout <- c(new_mat_layout, rep(mat_layout[, i_col], nmap)) + } + new_mat_layout <- matrix(new_mat_layout, nrow, nmap * ncol) + colorbar_row <- rep(1:nmap, each = ncol) + mat_layout <- rbind(new_mat_layout, as.numeric(colorbar_row)) + widths <- rep(widths, nmap) + } else { + mat_layout <- rbind(mat_layout, rep(1, dim(mat_layout)[2])) + } heights <- c(heights, round(bar_scale * 2 * nrow)) } else if (drawleg == 'W') { mat_layout <- cbind(rep(1, dim(mat_layout)[1]), mat_layout) @@ -618,6 +587,17 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, # Useless # n_figures <- n_figures + 1 } + + # row and col titles + if (length(row_titles) > 0) { + mat_layout <- cbind(rep(0, dim(mat_layout)[1]), mat_layout) + widths <- c(((subtitle_cex + subtitle_margin / 2) * cs / device_size[1]) * ncol * fsu, widths) + } + if (length(col_titles) > 0) { + mat_layout <- rbind(rep(0, dim(mat_layout)[2]), mat_layout) + heights <- c(((subtitle_cex + subtitle_margin) * cs / device_size[2]) * nrow * fsu, heights) + } + # toptitle if (toptitle != '') { mat_layout <- rbind(rep(0, dim(mat_layout)[2]), mat_layout) heights <- c(((title_cex + title_margin) * cs / device_size[2]) * nrow * fsu, heights) @@ -630,13 +610,32 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, bar_extra_margin[2] <- bar_extra_margin[2] + (subtitle_cex + subtitle_margin / 2) * bar_left_shift_scale } - ColorBar(colorbar$brks, colorbar$cols, vertical, subsampleg, - bar_limits, var_limits, - triangle_ends = triangle_ends, col_inf = colorbar$col_inf, - col_sup = colorbar$col_sup, color_fun, plot = TRUE, draw_bar_ticks, - draw_separators, triangle_ends_scale, bar_extra_labels, - units, units_scale, bar_label_scale, bar_tick_scale, - bar_extra_margin, bar_label_digits) + + if (multi_colorbar) { # multiple colorbar + if (!is.null(list(...)$bar_titles)) { + bar_titles <- list(...)$bar_titles + } else { + bar_titles <- NULL + } + multi_ColorBar(nmap = nmap, + brks = brks, cols = cols, vertical = vertical, subsampleg = subsampleg, + bar_limits = bar_limits, var_limits = var_limits, + triangle_ends = triangle_ends, plot = TRUE, + draw_separators = draw_separators, + bar_titles = bar_titles, title_scale = units_scale, + label_scale = bar_label_scale, extra_margin = bar_extra_margin) + + } else { # one colorbar + ColorBar(brks = colorbar$brks, cols = colorbar$cols, vertical = vertical, subsampleg = subsampleg, + bar_limits = bar_limits, var_limits = var_limits, + triangle_ends = triangle_ends, col_inf = colorbar$col_inf, + col_sup = colorbar$col_sup, color_fun = color_fun, plot = TRUE, draw_ticks = draw_bar_ticks, + draw_separators = draw_separators, triangle_ends_scale = triangle_ends_scale, + extra_labels = bar_extra_labels, + title = units, title_scale = units_scale, label_scale = bar_label_scale, tick_scale = bar_tick_scale, + extra_margin = bar_extra_margin, label_digits = bar_label_digits) + + } } # Draw titles @@ -705,15 +704,17 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, # For each of the arrays provided in that array apply(x, (1:length(dim(x)))[-plot_dim_indices], function(y) { - # Do the plot - fun_args <- c(list(y, toptitle = titles[plot_number]), list(...), + # Do the plot. colorbar is not drew. + fun_args <- c(list(y, toptitle = titles[plot_number], drawleg = FALSE), list(...), special_args[[array_number]]) # funct <- fun[[array_number]] if (fun[[array_number]] %in% c('PlotEquiMap', 'PlotStereoMap', 'PlotSection')) { fun_args <- c(fun_args, list(brks = colorbar$brks, cols = colorbar$cols, col_inf = colorbar$col_inf, - col_sup = colorbar$col_sup, - drawleg = FALSE)) + col_sup = colorbar$col_sup)) + } else if (fun[[array_number]] %in% 'PlotMostLikelyQuantileMap') { + #TODO: pre-generate colorbar params? like above + fun_args <- c(fun_args, list(brks = brks, cols = cols)) } do.call(fun[[array_number]], fun_args) plot_number <<- plot_number + 1 -- GitLab From 6a11a9ae8bb42a81c53daf23ed95fdee7b9b821c Mon Sep 17 00:00:00 2001 From: aho Date: Tue, 20 Sep 2022 14:51:05 +0200 Subject: [PATCH 117/140] Assign cat_dim default in PlotLayout --- R/PlotLayout.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/PlotLayout.R b/R/PlotLayout.R index e5ae980..ee2c96b 100644 --- a/R/PlotLayout.R +++ b/R/PlotLayout.R @@ -545,6 +545,7 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, if (fun == 'PlotMostLikelyQuantileMap') { #multi_colorbar multi_colorbar <- TRUE cat_dim <- list(...)$cat_dim + if (is.null(cat_dim)) cat_dim <- 'bin' # default nmap <- as.numeric(dim(var[[1]])[cat_dim]) mat_layout <- mat_layout + nmap } else { -- GitLab From dbb6fb41d9810096c0712e9f9ec879a6e9c1c304 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Wed, 21 Sep 2022 18:17:13 +0200 Subject: [PATCH 118/140] Allow input dots dimension to be unordered and also corrected documentation --- R/PlotEquiMap.R | 33 +++++++++++++++++++++------------ man/PlotEquiMap.Rd | 21 ++++++++++++--------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/R/PlotEquiMap.R b/R/PlotEquiMap.R index 6405333..eb87165 100644 --- a/R/PlotEquiMap.R +++ b/R/PlotEquiMap.R @@ -52,8 +52,11 @@ #' colors returned by 'color_fun'. If not available, it takes 'pink' by #' default. 'col_inf' and 'col_sup' will take the value of 'colNA' if not #' specified. See ?ColorBar for a full explanation on 'col_inf' and 'col_sup'. -#'@param color_fun,subsampleg,bar_extra_labels,draw_bar_ticks,draw_separators,triangle_ends_scale,bar_label_digits,bar_label_scale,units_scale,bar_tick_scale,bar_extra_margin Set of parameters to control the visual -#' aspect of the drawn colour bar. See ?ColorBar for a full explanation. +#'@param color_fun, subsampleg, bar_extra_labels, draw_bar_ticks, +#' draw_separators, triangle_ends_scale, bar_label_digits, bar_label_scale, +#' units_scale, bar_tick_scale, bar_extra_margin. Set of parameters to control +#' the visual aspect of the drawn colour bar. See ?ColorBar for a full +#' explanation. #'@param square Logical value to choose either to draw a coloured square for #' each grid cell in 'var' (TRUE; default) or to draw contour lines and fill #' the spaces in between with colours (FALSE). In the latter case, @@ -94,11 +97,11 @@ #' contour labels or not. The default value is TRUE. #'@param contour_label_scale Scale factor for the superimposed labels when #' drawing contour levels. -#'@param dots Array of same dimensions as 'var' or with dimensions -#' c(n, dim(var)), where n is the number of dot/symbol layers to add to the -#' plot. A value of TRUE at a grid cell will draw a dot/symbol on the -#' corresponding square of the plot. By default all layers provided in 'dots' -#' are plotted with dots, but a symbol can be specified for each of the +#'@param dots Array of same dimensions as 'var' with any order or with +#' dimensions c(n, dim(var)), where n is the number of dot/symbol layers to +#' add to the plot. A value of TRUE at a grid cell will draw a dot/symbol on +#' the corresponding square of the plot. By default all layers provided in +#' 'dots' are plotted with dots, but a symbol can be specified for each of the #' layers via the parameter 'dot_symbol'. #'@param dot_symbol Single character/number or vector of characters/numbers #' that correspond to each of the symbol layers specified in parameter 'dots'. @@ -134,8 +137,8 @@ #'@param lab_dist_y A numeric of the distance of the latitude labels to the #' box borders. The default value is NULL and is automatically adjusted by #' the function. -#'@param degree_sym A logical indicating whether to include degree symbol (30° N) -#' or not (30N; default). +#'@param degree_sym A logical indicating whether to include degree symbol +#' (30° N) or not (30N; default). #'@param intylat Interval between latitude ticks on y-axis, in degrees. #' Defaults to 20. #'@param intxlon Interval between latitude ticks on x-axis, in degrees. @@ -352,7 +355,6 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, if (!is.null(varu)) varu <- t(varu) if (!is.null(varv)) varv <- t(varv) if (!is.null(contours)) contours <- t(contours) - if (!is.null(dots)) dots <- aperm(dots, c(1, 3, 2)) dims <- dim(var) } @@ -579,11 +581,18 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, stop("Parameter 'contour_label_scale' must be numeric.") } - # Check dots, dot_symbol and dot_size + # Check dots if (!is.null(dots)) { - if (dim(dots)[2] != dims[1] || dim(dots)[3] != dims[2]) { + if ((dim(dots)[2] != dims[1] || dim(dots)[3] != dims[2]) + && (dim(dots)[3] != dims[1] || dim(dots)[2] != dims[2])) { stop("Parameter 'dots' must have the same number of longitudes and latitudes as 'var'.") + } else if (dim(dots)[2] != dims[1] || dim(dots)[3] != dims[2]) { + dots <- aperm(dots, c(1, 3, 2)) } + } + + # Check dot_symbol and dot_size + if (!is.null(dots)) { if (!is.numeric(dot_symbol) && !is.character(dot_symbol)) { stop("Parameter 'dot_symbol' must be a numeric or character string vector.") } diff --git a/man/PlotEquiMap.Rd b/man/PlotEquiMap.Rd index 47a0d98..e31b991 100644 --- a/man/PlotEquiMap.Rd +++ b/man/PlotEquiMap.Rd @@ -138,8 +138,11 @@ colors returned by 'color_fun'. If not available, it takes 'pink' by default. 'col_inf' and 'col_sup' will take the value of 'colNA' if not specified. See ?ColorBar for a full explanation on 'col_inf' and 'col_sup'.} -\item{color_fun, subsampleg, bar_extra_labels, draw_bar_ticks, draw_separators, triangle_ends_scale, bar_label_digits, bar_label_scale, units_scale, bar_tick_scale, bar_extra_margin}{Set of parameters to control the visual -aspect of the drawn colour bar. See ?ColorBar for a full explanation.} +\item{color_fun, }{subsampleg, bar_extra_labels, draw_bar_ticks, +draw_separators, triangle_ends_scale, bar_label_digits, bar_label_scale, +units_scale, bar_tick_scale, bar_extra_margin. Set of parameters to control +the visual aspect of the drawn colour bar. See ?ColorBar for a full +explanation.} \item{square}{Logical value to choose either to draw a coloured square for each grid cell in 'var' (TRUE; default) or to draw contour lines and fill @@ -198,11 +201,11 @@ contour labels or not. The default value is TRUE.} \item{contour_label_scale}{Scale factor for the superimposed labels when drawing contour levels.} -\item{dots}{Array of same dimensions as 'var' or with dimensions -c(n, dim(var)), where n is the number of dot/symbol layers to add to the -plot. A value of TRUE at a grid cell will draw a dot/symbol on the -corresponding square of the plot. By default all layers provided in 'dots' -are plotted with dots, but a symbol can be specified for each of the +\item{dots}{Array of same dimensions as 'var' with any order or with +dimensions c(n, dim(var)), where n is the number of dot/symbol layers to +add to the plot. A value of TRUE at a grid cell will draw a dot/symbol on +the corresponding square of the plot. By default all layers provided in +'dots' are plotted with dots, but a symbol can be specified for each of the layers via the parameter 'dot_symbol'.} \item{dot_symbol}{Single character/number or vector of characters/numbers @@ -251,8 +254,8 @@ the function.} box borders. The default value is NULL and is automatically adjusted by the function.} -\item{degree_sym}{A logical indicating whether to include degree symbol (30° N) -or not (30N; default).} +\item{degree_sym}{A logical indicating whether to include degree symbol +(30° N) or not (30N; default).} \item{intylat}{Interval between latitude ticks on y-axis, in degrees. Defaults to 20.} -- GitLab From 83a0ea11a746388ab68bb813738b60913bb8d603 Mon Sep 17 00:00:00 2001 From: aho Date: Thu, 22 Sep 2022 12:55:37 +0200 Subject: [PATCH 119/140] Combine two .diff.corr(); correct unit tests --- R/DiffCorr.R | 97 ++++++++++++++-------------------- man/DiffCorr.Rd | 11 ++-- tests/testthat/test-DiffCorr.R | 30 +++++++---- 3 files changed, 65 insertions(+), 73 deletions(-) diff --git a/R/DiffCorr.R b/R/DiffCorr.R index 277e4d2..95996b6 100644 --- a/R/DiffCorr.R +++ b/R/DiffCorr.R @@ -41,9 +41,9 @@ #'@param test.type A character string indicating the type of significance test. #' It can be "two-sided" (to assess whether the skill of "exp" and "ref" are #' significantly different with a z-test on Fisher z-transformed correlation -#' coefficients) or "one-sided" (to assess whether the skill of "exp" is -#' significantly higher than that of "ref" with the test described in -#' Steiger, 1980). The default value is "two-sided". +#' coefficients, Hinkel et al, 1988) or "one-sided" (to assess whether the +#' skill of "exp" is significantly higher than that of "ref" with the test +#' described in Steiger, 1980). The default value is "two-sided". #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -72,7 +72,8 @@ #' exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) #' obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) #' ref <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) -#' res <- DiffCorr(exp, obs, ref, memb_dim = 'member') +#' res_two.sided_sign <- DiffCorr(exp, obs, ref, memb_dim = 'member', alpha = 0.05) +#' res_one.sided_pval <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'one-sided') #' #'@import multiApply #'@export @@ -216,54 +217,46 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', .DiffCorr <- function(exp, obs, ref, N.eff = NA, method = 'pearson', alpha = NULL, handle.na = 'return.na', test.type = 'two.sided') { - .diff.corr_one.sided <- function(exp, obs, ref, method = 'pearson', N.eff = NA, alpha = NULL) { - + .diff.corr <- function(exp, obs, ref, method = 'pearson', N.eff = NA, alpha = NULL, test.type = 'two.sided') { # Correlation difference cor.exp <- cor(x = exp, y = obs, method = method) cor.ref <- cor(x = ref, y = obs, method = method) output <- NULL output$diff.corr <- cor.exp - cor.ref - - # Significance with one-sided test for equality of dependent correlation coefficients (Steiger, 1980) - r12 <- cor.exp - r13 <- cor.ref - r23 <- cor(exp, ref) - if (is.na(N.eff)) { - N.eff <- .Eno(x = obs, na.action = na.pass) ## effective degrees of freedom - } - R <- (1 - r12 * r12 - r13 * r13 - r23 * r23) + 2 * r12 * r13 * r23 - t <- (r12 - r13) * sqrt((N.eff - 1) * (1 + r23) / (2 * ((N.eff - 1) / (N.eff - 3)) * R + 0.25 * (r12 + r13)^2 * (1 - r23)^3)) - p.value <- pt(t, df = N.eff - 3, lower.tail = FALSE) - if (is.null(alpha)) { - output$p.val <- p.value - } else { - output$sign <- ifelse(!is.na(p.value) & p.value <= alpha & output$diff.corr > 0, TRUE, FALSE) - } - return(output) - } - - .diff.corr_two.sided <- function(exp, obs, ref, method = 'pearson', N.eff = NA, alpha = NULL) { - - # Correlation difference - cor.exp <- cor(x = exp, y = obs, method = method) - cor.ref <- cor(x = ref, y = obs, method = method) - output <- NULL - output$diff.corr <- cor.exp - cor.ref - - # Significance with two-sided z-test on Fisher z-transformed correlation coefficients (Hinkel et al, 1988) if (is.na(N.eff)) { N.eff <- .Eno(x = obs, na.action = na.pass) ## effective degrees of freedom } - Z <- abs(0.5*log((1+cor.exp)/(1-cor.exp)) - 0.5*log((1+cor.ref)/(1-cor.ref))) / sqrt(1/(N.eff-3) + 1/(N.eff-3)) - # sign <- Z >= qnorm(p = alpha/2, lower.tail = FALSE) - p.value <- pnorm(q = Z, lower.tail = FALSE) - - if (is.null(alpha)) { - output$p.val <- p.value + + if (test.type == 'one-sided') { + # Significance with one-sided test for equality of dependent correlation coefficients (Steiger, 1980) + r12 <- cor.exp + r13 <- cor.ref + r23 <- cor(exp, ref) + R <- (1 - r12 * r12 - r13 * r13 - r23 * r23) + 2 * r12 * r13 * r23 + t <- (r12 - r13) * sqrt((N.eff - 1) * (1 + r23) / (2 * ((N.eff - 1) / (N.eff - 3)) * R + 0.25 * (r12 + r13)^2 * (1 - r23)^3)) + p.value <- pt(t, df = N.eff - 3, lower.tail = FALSE) + if (is.null(alpha)) { + output$p.val <- p.value + } else { + output$sign <- ifelse(!is.na(p.value) & p.value <= alpha & output$diff.corr > 0, TRUE, FALSE) + } + + } else if (test.type == 'two-sided') { + # Significance with two-sided z-test on Fisher z-transformed correlation coefficients (Hinkel et al, 1988) + Z <- abs(0.5 * log((1 + cor.exp) / (1 - cor.exp)) - 0.5 * log((1 + cor.ref) / (1 - cor.ref))) / sqrt(1/ (N.eff - 3) + 1/(N.eff - 3)) + # sign <- Z >= qnorm(p = alpha/2, lower.tail = FALSE) + p.value <- pnorm(q = Z, lower.tail = FALSE) + + if (is.null(alpha)) { + output$p.val <- p.value + } else { + output$sign <- ifelse(!is.na(p.value) & p.value <= alpha / 2, TRUE, FALSE) + } + } else { - output$sign <- ifelse(!is.na(p.value) & p.value <= alpha/2, TRUE, FALSE) + stop("Parameter 'test.type' is not supported.") } - + return(output) } @@ -278,13 +271,8 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', obs <- obs[!nna] ref <- ref[!nna] - if (identical(test.type,'two-sided')){ - output <- .diff.corr_two.sided(exp = exp, obs = obs, ref = ref, method = method, - N.eff = N.eff, alpha = alpha) - } else if (identical(test.type,'one-sided')){ - output <- .diff.corr_one.sided(exp = exp, obs = obs, ref = ref, method = method, - N.eff = N.eff, alpha = alpha) - } + output <- .diff.corr(exp = exp, obs = obs, ref = ref, method = method, + N.eff = N.eff, alpha = alpha, test.type = test.type) } else if (handle.na == 'return.na') { # Data contain NA, return NAs directly without passing to .diff.corr @@ -296,14 +284,9 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', } } else { ## There is no NA - - if (identical(test.type,'two-sided')){ - output <- .diff.corr_two.sided(exp = exp, obs = obs, ref = ref, method = method, - N.eff = N.eff, alpha = alpha) - } else if (identical(test.type,'one-sided')){ - output <- .diff.corr_one.sided(exp = exp, obs = obs, ref = ref, method = method, - N.eff = N.eff, alpha = alpha) - } + output <- .diff.corr(exp = exp, obs = obs, ref = ref, method = method, + N.eff = N.eff, alpha = alpha, test.type = test.type) } + return(output) } diff --git a/man/DiffCorr.Rd b/man/DiffCorr.Rd index 36b2fb9..e0e5ec3 100644 --- a/man/DiffCorr.Rd +++ b/man/DiffCorr.Rd @@ -59,10 +59,10 @@ NA. The default value is "return.na".} \item{test.type}{A character string indicating the type of significance test. It can be "two-sided" (to assess whether the skill of "exp" and "ref" are -significantly different), "one-sided-higher" (to assess whether the skill of -"exp" is significantly higher than that of "ref"), or "one-sided-lower" (to -assess whether the skill of "exp" is significantly lower than that of -"ref"). The default value is "two-sided".} +significantly different with a z-test on Fisher z-transformed correlation +coefficients, Hinkel et al, 1988) or "one-sided" (to assess whether the +skill of "exp" is significantly higher than that of "ref" with the test +described in Steiger, 1980). The default value is "two-sided".} \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} @@ -98,7 +98,8 @@ the time series (von Storch and Zwiers, 1999). exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) ref <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) -res <- DiffCorr(exp, obs, ref, memb_dim = 'member') +res_two.sided_sign <- DiffCorr(exp, obs, ref, memb_dim = 'member', alpha = 0.05) +res_one.sided_pval <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'one-sided') } \references{ diff --git a/tests/testthat/test-DiffCorr.R b/tests/testthat/test-DiffCorr.R index 60ac7d4..b8c8029 100644 --- a/tests/testthat/test-DiffCorr.R +++ b/tests/testthat/test-DiffCorr.R @@ -20,7 +20,6 @@ set.seed(3) obs2 <- array(rnorm(10), dim = c(sdate = 10)) - ############################################## test_that("1. Input checks", { # exp and obs (1) @@ -92,7 +91,7 @@ test_that("1. Input checks", { # test.type expect_error( DiffCorr(exp2, obs2, ref2, test.type = "two.sided"), - "Parameter 'test.type' must be 'two-sided', 'one-sided-higher' or 'one-sided-lower'." + "Parameter 'test.type' must be 'two-sided' or 'one-sided'." ) # ncores expect_error( @@ -125,7 +124,7 @@ tolerance = 0.0001 ) expect_equal( as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb')$p), -c(0.26166060, 0.15899774, 0.39264452, 0.27959883, 0.34736305, 0.07479832), +c(0.28984782, 0.14395946, 0.41374677, 0.32253784, 0.32677872, 0.03659053), tolerance = 0.0001 ) expect_equal( @@ -138,7 +137,7 @@ c(0.27347087, 0.50556882, 0.08855968, 0.24199701, 0.22935182, 0.88336336), tolerance = 0.0001 ) expect_equal( -as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, test.type = "one-sided-higher")$diff.corr), +as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, test.type = "one-sided")$diff.corr), as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, test.type = "two-sided")$diff.corr) ) expect_equal( @@ -146,11 +145,7 @@ as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05)$sign), rep(FALSE, 6) ) expect_equal( -as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, test.type = "one-sided-higher")$sign), -rep(FALSE, 6) -) -expect_equal( -as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, test.type = "one-sided-lower")$sign), +as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', alpha = 0.05, test.type = "one-sided")$sign), rep(FALSE, 6) ) expect_equal( @@ -160,7 +155,7 @@ tolerance = 0.0001 ) expect_equal( suppressWarnings(as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', method = "spearman")$p)), -c(0.4358970, 0.1341575, 0.3448977, 0.5114262, 0.5264872, 0.0437861), +c(0.44479470, 0.11854232, 0.36667620, 0.49095264, 0.46962814, 0.01714977), tolerance = 0.0001 ) expect_equal( @@ -170,7 +165,7 @@ tolerance = 0.0001 ) expect_equal( as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', N.eff = Neff1)$p), -c(0.27841537, 0.15899774, 0.40096749, 0.27959883, 0.35889690, 0.07479832), +c(0.30406438, 0.14395946, 0.42005522, 0.32253784, 0.33887632, 0.03659053), tolerance = 0.0001 ) @@ -223,10 +218,23 @@ NULL ) expect_equal( DiffCorr(exp2, obs2, ref2)$p, +0.3201317, +tolerance = 0.0001 +) +expect_equal( +DiffCorr(exp2, obs2, ref2, test.type = 'one-sided')$p, 0.6577392, tolerance = 0.0001 ) expect_equal( +DiffCorr(exp2, obs2, ref2, test.type = 'one-sided', alpha = 0.5)$sign, +FALSE +) +expect_equal( +DiffCorr(exp2, obs2, ref2, test.type = 'two-sided', alpha = 0.65)$sign, +TRUE +) +expect_equal( DiffCorr(exp2, obs2, ref2)$diff, -0.2434725, tolerance = 0.0001 -- GitLab From ae8317252f6717b65f2d67aef08e16a85b584472 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Thu, 29 Sep 2022 13:38:17 +0200 Subject: [PATCH 120/140] Allow dots, varu, varv and contour dimensions to be unordered --- R/PlotEquiMap.R | 315 +++++++++++++++++++++++++++++++++------------ man/PlotEquiMap.Rd | 29 +++-- 2 files changed, 254 insertions(+), 90 deletions(-) diff --git a/R/PlotEquiMap.R b/R/PlotEquiMap.R index eb87165..6bfeacb 100644 --- a/R/PlotEquiMap.R +++ b/R/PlotEquiMap.R @@ -16,7 +16,9 @@ #' descending order and latitudes in any order. It can contain NA values #' (coloured with 'colNA'). Arrays with dimensions c(longitude, latitude) #' will also be accepted but 'lon' and 'lat' will be used to disambiguate so -#' this alternative is not appropriate for square arrays. +#' this alternative is not appropriate for square arrays. It is allowed that +#' the positions of the longitudinal and latitudinal coordinate dimensions +#' are interchanged. #'@param lon Numeric vector of longitude locations of the cell centers of the #' grid of 'var', in ascending or descending order (same as 'var'). Expected #' to be regularly spaced, within either of the ranges [-180, 180] or @@ -27,9 +29,11 @@ #' grid of 'var', in any order (same as 'var'). Expected to be from a regular #' rectangular or gaussian grid, within the range [-90, 90]. #'@param varu Array of the zonal component of wind/current/other field with -#' the same dimensions as 'var'. +#' the same dimensions as 'var'. It is allowed that the positions of the +#' longitudinal and latitudinal coordinate dimensions are interchanged. #'@param varv Array of the meridional component of wind/current/other field -#' with the same dimensions as 'var'. +#' with the same dimensions as 'var'. It is allowed that the positions of the +#' longitudinal and latitudinal coordinate dimensions are interchanged. #'@param toptitle Top title of the figure, scalable with parameter #' 'title_scale'. #'@param sizetit Scale factor for the figure top title provided in parameter @@ -52,7 +56,7 @@ #' colors returned by 'color_fun'. If not available, it takes 'pink' by #' default. 'col_inf' and 'col_sup' will take the value of 'colNA' if not #' specified. See ?ColorBar for a full explanation on 'col_inf' and 'col_sup'. -#'@param color_fun, subsampleg, bar_extra_labels, draw_bar_ticks, +#'@param color_fun subsampleg, bar_extra_labels, draw_bar_ticks, #' draw_separators, triangle_ends_scale, bar_label_digits, bar_label_scale, #' units_scale, bar_tick_scale, bar_extra_margin. Set of parameters to control #' the visual aspect of the drawn colour bar. See ?ColorBar for a full @@ -85,6 +89,8 @@ #'@param contours Array of same dimensions as 'var' to be added to the plot #' and displayed with contours. Parameter 'brks2' is required to define the #' magnitude breaks for each contour curve. Disregarded if 'square = FALSE'. +#' It is allowed that the positions of the longitudinal and latitudinal +#' coordinate dimensions are interchanged. #'@param brks2 Vector of magnitude breaks where to draw contour curves for the #' array provided in 'contours' or if 'square = FALSE'. #'@param contour_lwd Line width of the contour curves provided via 'contours' @@ -97,12 +103,13 @@ #' contour labels or not. The default value is TRUE. #'@param contour_label_scale Scale factor for the superimposed labels when #' drawing contour levels. -#'@param dots Array of same dimensions as 'var' with any order or with -#' dimensions c(n, dim(var)), where n is the number of dot/symbol layers to -#' add to the plot. A value of TRUE at a grid cell will draw a dot/symbol on -#' the corresponding square of the plot. By default all layers provided in -#' 'dots' are plotted with dots, but a symbol can be specified for each of the -#' layers via the parameter 'dot_symbol'. +#'@param dots Array of same dimensions as 'var' or with dimensions +#' c(n, dim(var)), where n is the number of dot/symbol layers to add to the +#' plot. A value of TRUE at a grid cell will draw a dot/symbol on the +#' corresponding square of the plot. By default all layers provided in 'dots' +#' are plotted with dots, but a symbol can be specified for each of the +#' layers via the parameter 'dot_symbol'. It is allowed that the positions of +#' the longitudinal and latitudinal coordinate dimensions are interchanged. #'@param dot_symbol Single character/number or vector of characters/numbers #' that correspond to each of the symbol layers specified in parameter 'dots'. #' If a single value is specified, it will be applied to all the layers in @@ -292,90 +299,206 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, fileout <- deviceInfo$files } - # Preliminar check of dots, contours, varu, varv, lon, lat - if (!is.null(dots)) { - if (!is.array(dots) || !(length(dim(dots)) %in% c(2, 3))) { - stop("Parameter 'dots' must be a logical array with two or three dimensions.") - } - if (length(dim(dots)) == 2) { - dim(dots) <- c(1, dim(dots)) - } - } - if (!is.null(contours)) { - if (!is.array(contours) || !(length(dim(contours)) == 2)) { - stop("Parameter 'contours' must be a numerical array with two dimensions.") - } - } - if (!is.null(varu) && !is.null(varv)) { - if (!is.array(varu) || !(length(dim(varu)) == 2)) { - stop("Parameter 'varu' must be a numerical array with two dimensions.") - } - if (!is.array(varv) || !(length(dim(varv)) == 2)) { - stop("Parameter 'varv' must be a numerical array with two dimensions.") - } - } else if (!is.null(varu) || !is.null(varv)) { - stop("Only one of the components 'varu' or 'varv' has been provided. Both must be provided.") - } + # Check lon, lat if (!is.numeric(lon) || !is.numeric(lat)) { stop("Parameters 'lon' and 'lat' must be numeric vectors.") } # Check var + if (is.null(var)) { + stop("Parameter 'var' cannot be NULL.") + } if (!is.array(var)) { stop("Parameter 'var' must be a numeric array.") } if (length(dim(var)) > 2) { - var <- drop(var) - dim(var) <- head(c(dim(var), 1, 1), 2) - } - if (length(dim(var)) > 2) { - stop("Parameter 'var' must be a numeric array with two dimensions. See PlotMultiMap() for multi-pannel maps or AnimateMap() for animated maps.") + if (any(dim(var) == 1)) { + var <- drop(var) + dim(var) <- head(c(dim(var), 1, 1), 2) + .warning("Parameter 'var' has more than 2 dimensions. Dimensions with length 1 have been dropped.") + } else { + stop("Parameter 'var' must be a numeric array with two dimensions. See PlotMultiMap() for multi-pannel maps or AnimateMap() for animated maps.") + } } else if (length(dim(var)) < 2) { stop("Parameter 'var' must be a numeric array with two dimensions.") } - dims <- dim(var) - # Transpose the input matrices because the base plot functions work directly - # with dimensions c(lon, lat). + transpose <- FALSE - if (!is.null(names(dims))) { - if (any(names(dims) %in% .KnownLonNames()) && - any(names(dims) %in% .KnownLatNames())) { - if (which(names(dims) %in% .KnownLonNames()) != 1) { + if (!is.null(names(dim(var)))) { + if (any(names(dim(var)) %in% .KnownLonNames()) && + any(names(dim(var)) %in% .KnownLatNames())) { + lon_dim <- names(dim(var))[names(dim(var)) %in% .KnownLonNames()] + lat_dim <- names(dim(var))[names(dim(var)) %in% .KnownLatNames()] + } else { + names(dim(var)) <- NULL + lat_dim <- NULL + lon_dim <- NULL + .warning("Dimension names of 'var' doesn't correspond to any coordinates names supported by s2dv package.") + } + } else { + lon_dim <- NULL + lat_dim <- NULL + .warning("Parameter 'var' should have dimension names. Coordinates 'lon' and 'lat' have been assigned into the corresponding coordinates dimensions.") + } + + if ((dim(var)[1] == length(lon) && dim(var)[2] == length(lat)) || + (dim(var)[2] == length(lon) && dim(var)[1] == length(lat))) { + if (dim(var)[2] == length(lon) && dim(var)[1] == length(lat)) { + if (length(lon) == length(lat)) { + if (is.null(names(dim(var)))) { + .warning("Parameter 'var' should have dimension names. Coordinates 'lon' and 'lat' have been assigned into the first and second dimensions.") + } else { + if (names(dim(var)[1]) == lat_dim) { + transpose <- TRUE + } + } + } else { transpose <- TRUE - } + } } + } else { + stop("Parameters 'lon' and 'lat' must have as many elements as the number of cells along longitudes and latitudes in the input array 'var'.") } - if (dims[1] != length(lon) || dims[2] != length(lat)) { - if (dims[1] == length(lat) && dims[2] == length(lon)) { - transpose <- TRUE + + if (!is.null(names(dim(var)))) { + if (names(dim(var)[1]) == lon_dim) { + if (transpose) { + stop("Coordinates dimensions of 'var' doesn't correspond to lat or lon.") + } + } else if (names(dim(var)[2]) == lon_dim) { + if (!transpose) { + stop("Coordinates dimensions of 'var' doesn't correspond to lat or lon.") + } } } + + # Transpose the input matrices because the base plot functions work directly + # with dimensions c(lon, lat). + if (transpose) { var <- t(var) - if (!is.null(varu)) varu <- t(varu) - if (!is.null(varv)) varv <- t(varv) - if (!is.null(contours)) contours <- t(contours) - dims <- dim(var) } - # Check lon - if (length(lon) != dims[1]) { - stop("Parameter 'lon' must have as many elements as the number of cells along longitudes in the input array 'var'.") - } + transpose <- FALSE - # Check lat - if (length(lat) != dims[2]) { - stop("Parameter 'lat' must have as many elements as the number of cells along longitudes in the input array 'var'.") - } + names(dim(var)) <- c(lon_dim, lat_dim) + dims <- dim(var) # Check varu and varv if (!is.null(varu) && !is.null(varv)) { - if (dim(varu)[1] != dims[1] || dim(varu)[2] != dims[2]) { - stop("Parameter 'varu' must have same number of longitudes and latitudes as 'var'.") + if (!is.array(varu) || !(length(dim(varu)) == 2)) { + stop("Parameter 'varu' must be a numerical array with two dimensions.") + } + if (!is.array(varv) || !(length(dim(varv)) == 2)) { + stop("Parameter 'varv' must be a numerical array with two dimensions.") + } + } else if (!is.null(varu) || !is.null(varv)) { + stop("Only one of the components 'varu' or 'varv' has been provided. Both must be provided.") + } + + if (!is.null(varu) && !is.null(varv)) { + if (!all(dim(varu) %in% dim(varv)) || !all(names(dim(varv)) %in% names(dim(varu)))) { + stop("Parameter 'varu' and 'varv' must have equal dimensions and dimension names.") + } else if (any(dim(varu) != dim(varv)) || any(names(dim(varv)) != names(dim(varu)))) { + varv <- t(varv) + names(dim(varv)) <- names(dim(varu)) + } + + if (is.null(lon_dim)) { + names(dim(varu)) <- NULL + names(dim(varv)) <- NULL + } else { + if (!is.null(names(dim(varu)))) { + if (!(lon_dim %in% names(dim(varu)) && lat_dim %in% names(dim(varu)))) { + stop("Parameters 'varu' and 'varv' must have same dimension names as 'var'.") + } else if (dim(varu)[lon_dim] != dim(var)[lon_dim] || dim(varu)[lat_dim] != dim(var)[lat_dim]) { + stop("Parameters 'varu' and 'varv' must have same dimensions as 'var'.") + } + } else { + .warning("Parameters 'varu' and 'varv' should have dimension names. Coordinates 'lon' and 'lat' have been assigned into the corresponding coordinates dimensions.") + } } - if (dim(varv)[1] != dims[1] || dim(varv)[2] != dims[2]) { - stop("Parameter 'varv' must have same number of longitudes and latitudes as 'var'.") + + + if ((dim(varu)[1] == dims[1] && dim(varu)[2] == dims[2]) || + (dim(varu)[2] == dims[1] && dim(varu)[1] == dims[2])) { + if (dim(varu)[2] == dims[1] && dim(varu)[1] == dims[2]) { + if (length(lon) == length(lat)) { + if (is.null(names(dim(varu)))) { + .warning("Parameters 'varu' and 'varv' should have dimension names. Coordinates 'lon' and 'lat' have been assigned into the first and second dimensions.") + } else { + if (names(dim(varu)[1]) == lat_dim) { + transpose <- TRUE + } + } + } else { + transpose <- TRUE + } + } + } else { + stop("Parameters 'lon' and 'lat' must have as many elements as the number of cells along longitudes and latitudes in the input array 'varu' and 'varv'.") } + + if (transpose) { + varu <- t(varu) + varv <- t(varv) + } + + transpose <- FALSE + + } + + # Check contours + if (!is.null(contours)) { + if (!is.array(contours) || !(length(dim(contours)) == 2)) { + stop("Parameter 'contours' must be a numerical array with two dimensions.") + } + } + + + if (!is.null(contours)) { + + if (is.null(lon_dim)) { + names(dim(contours)) <- NULL + } else { + if (!is.null(names(dim(contours)))) { + if (!(lon_dim %in% names(dim(contours)) && lat_dim %in% names(dim(contours)))) { + stop("Parameters 'contours' must have same dimension names as 'var'.") + } else if (dim(contours)[lon_dim] != dim(var)[lon_dim] || dim(contours)[lat_dim] != dim(var)[lat_dim]) { + stop("Parameters 'contours' must have same dimensions as 'var'.") + } + } else { + .warning("Parameters 'contours' should have dimension names. Coordinates 'lon' and 'lat' have been assigned into the corresponding coordinates dimensions.") + } + } + + + transpose <- FALSE + if ((dim(contours)[1] == dims[1] && dim(contours)[2] == dims[2]) || + (dim(contours)[2] == dims[1] && dim(contours)[1] == dims[2])) { + if (dim(contours)[2] == dims[1] && dim(contours)[1] == dims[2]) { + if (length(lon) == length(lat)) { + if (is.null(names(dim(contours)))) { + .warning("Parameter 'contours' should have dimension names. Coordinates 'lon' and 'lat' have been assigned into the first and second dimensions.") + } else { + if (names(dim(contours)[1]) == lat_dim) { + transpose <- TRUE + } + } + } else { + transpose <- TRUE + } + } + } else { + stop("Parameters 'lon' and 'lat' must have as many elements as the number of cells along longitudes and latitudes in the input array 'contours'.") + } + + if (transpose) { + contours <- t(contours) + } + + transpose <- FALSE + } # Check toptitle @@ -535,13 +658,6 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, stop("Parameter 'shapefile_color' must be a valid colour identifier.") } - # Check contours - if (!is.null(contours)) { - if (dim(contours)[1] != dims[1] || dim(contours)[2] != dims[2]) { - stop("Parameter 'contours' must have the same number of longitudes and latitudes as 'var'.") - } - } - # Check brks2 if (is.null(brks2)) { if (is.null(contours)) { @@ -583,12 +699,53 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, # Check dots if (!is.null(dots)) { - if ((dim(dots)[2] != dims[1] || dim(dots)[3] != dims[2]) - && (dim(dots)[3] != dims[1] || dim(dots)[2] != dims[2])) { - stop("Parameter 'dots' must have the same number of longitudes and latitudes as 'var'.") - } else if (dim(dots)[2] != dims[1] || dim(dots)[3] != dims[2]) { + if (!is.array(dots) || !(length(dim(dots)) %in% c(2, 3))) { + stop("Parameter 'dots' must be a logical array with two or three dimensions.") + } + if (length(dim(dots)) == 2) { + dim(dots) <- c(1, dim(dots)) + } + + if (is.null(lon_dim)) { + names(dim(dots)) <- NULL + } else { + if (!is.null(names(dim(dots)))) { + if (!(lon_dim %in% names(dim(dots)) && lat_dim %in% names(dim(dots)))) { + stop("Parameters 'dots' must have same dimension names as 'var'.") + } else if (dim(dots)[lon_dim] != dim(var)[lon_dim] || dim(dots)[lat_dim] != dim(var)[lat_dim]) { + stop("Parameters 'dots' must have same dimensions as 'var'.") + } + } else { + .warning("Parameters 'dots' should have dimension names. Coordinates 'lon' and 'lat' have been assigned into the corresponding coordinates dimensions.") + } + } + + transpose <- FALSE + if ((dim(dots)[2] == dims[1] && dim(dots)[3] == dims[2]) || + (dim(dots)[3] == dims[1] && dim(dots)[2] == dims[2])) { + if (dim(dots)[3] == dims[1] && dim(dots)[2] == dims[2]) { + if (length(lon) == length(lat)) { + if (is.null(names(dim(dots)))) { + .warning("Parameter 'dots' should have dimension names. Coordinates 'lon' and 'lat' have been assigned into the first and second dimensions.") + } else { + if (names(dim(dots)[2]) == lat_dim) { + transpose <- TRUE + } + } + } else { + transpose <- TRUE + } + } + } else { + stop("Parameter 'dots' must have same number of longitudes and latitudes as 'var'.") + } + + if (transpose) { dots <- aperm(dots, c(1, 3, 2)) } + + transpose <- FALSE + } # Check dot_symbol and dot_size @@ -800,7 +957,7 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, ypos <- seq(latmin, latmax, intylat) + ylatshft if (length(ypos) != length(ylabels)) { stop(paste0("Parameter 'ylabels' must have the same length as the latitude ", - "vector spaced by 'intylat' (length = ", length(ypos), ").")) + "vector spaced by 'intylat' (length = ", length(ypos), ").")) } ylabs <- ylabels } else { @@ -821,7 +978,7 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, xpos <- seq(lonmin, lonmax, intxlon) + xlonshft if (length(xpos) != length(xlabels)) { stop(paste0("Parameter 'xlabels' must have the same length as the longitude ", - "vector spaced by 'intxlon' (length = ", length(xpos), ").")) + "vector spaced by 'intxlon' (length = ", length(xpos), ").")) } xlabs <- xlabels } else { diff --git a/man/PlotEquiMap.Rd b/man/PlotEquiMap.Rd index e31b991..4c1f819 100644 --- a/man/PlotEquiMap.Rd +++ b/man/PlotEquiMap.Rd @@ -92,7 +92,9 @@ dimensions: c(latitude, longitude). Longitudes can be in ascending or descending order and latitudes in any order. It can contain NA values (coloured with 'colNA'). Arrays with dimensions c(longitude, latitude) will also be accepted but 'lon' and 'lat' will be used to disambiguate so -this alternative is not appropriate for square arrays.} +this alternative is not appropriate for square arrays. It is allowed that +the positions of the longitudinal and latitudinal coordinate dimensions +are interchanged.} \item{lon}{Numeric vector of longitude locations of the cell centers of the grid of 'var', in ascending or descending order (same as 'var'). Expected @@ -106,10 +108,12 @@ grid of 'var', in any order (same as 'var'). Expected to be from a regular rectangular or gaussian grid, within the range [-90, 90].} \item{varu}{Array of the zonal component of wind/current/other field with -the same dimensions as 'var'.} +the same dimensions as 'var'. It is allowed that the positions of the +longitudinal and latitudinal coordinate dimensions are interchanged.} \item{varv}{Array of the meridional component of wind/current/other field -with the same dimensions as 'var'.} +with the same dimensions as 'var'. It is allowed that the positions of the +longitudinal and latitudinal coordinate dimensions are interchanged.} \item{toptitle}{Top title of the figure, scalable with parameter 'title_scale'.} @@ -138,7 +142,7 @@ colors returned by 'color_fun'. If not available, it takes 'pink' by default. 'col_inf' and 'col_sup' will take the value of 'colNA' if not specified. See ?ColorBar for a full explanation on 'col_inf' and 'col_sup'.} -\item{color_fun, }{subsampleg, bar_extra_labels, draw_bar_ticks, +\item{color_fun}{subsampleg, bar_extra_labels, draw_bar_ticks, draw_separators, triangle_ends_scale, bar_label_digits, bar_label_scale, units_scale, bar_tick_scale, bar_extra_margin. Set of parameters to control the visual aspect of the drawn colour bar. See ?ColorBar for a full @@ -181,7 +185,9 @@ location of the shape. The default value is NULL.} \item{contours}{Array of same dimensions as 'var' to be added to the plot and displayed with contours. Parameter 'brks2' is required to define the -magnitude breaks for each contour curve. Disregarded if 'square = FALSE'.} +magnitude breaks for each contour curve. Disregarded if 'square = FALSE'. +It is allowed that the positions of the longitudinal and latitudinal +coordinate dimensions are interchanged.} \item{brks2}{Vector of magnitude breaks where to draw contour curves for the array provided in 'contours' or if 'square = FALSE'.} @@ -201,12 +207,13 @@ contour labels or not. The default value is TRUE.} \item{contour_label_scale}{Scale factor for the superimposed labels when drawing contour levels.} -\item{dots}{Array of same dimensions as 'var' with any order or with -dimensions c(n, dim(var)), where n is the number of dot/symbol layers to -add to the plot. A value of TRUE at a grid cell will draw a dot/symbol on -the corresponding square of the plot. By default all layers provided in -'dots' are plotted with dots, but a symbol can be specified for each of the -layers via the parameter 'dot_symbol'.} +\item{dots}{Array of same dimensions as 'var' or with dimensions +c(n, dim(var)), where n is the number of dot/symbol layers to add to the +plot. A value of TRUE at a grid cell will draw a dot/symbol on the +corresponding square of the plot. By default all layers provided in 'dots' +are plotted with dots, but a symbol can be specified for each of the +layers via the parameter 'dot_symbol'. It is allowed that the positions of +the longitudinal and latitudinal coordinate dimensions are interchanged.} \item{dot_symbol}{Single character/number or vector of characters/numbers that correspond to each of the symbol layers specified in parameter 'dots'. -- GitLab From 1898cfe8cabc8b60c055e3266e0b02c08a99fcc3 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Fri, 7 Oct 2022 14:47:33 +0200 Subject: [PATCH 121/140] Correct var checks and documentation fix --- R/PlotEquiMap.R | 22 +++++++++------------- man/PlotEquiMap.Rd | 16 +++++++++++----- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/R/PlotEquiMap.R b/R/PlotEquiMap.R index 6bfeacb..c87aed4 100644 --- a/R/PlotEquiMap.R +++ b/R/PlotEquiMap.R @@ -56,11 +56,15 @@ #' colors returned by 'color_fun'. If not available, it takes 'pink' by #' default. 'col_inf' and 'col_sup' will take the value of 'colNA' if not #' specified. See ?ColorBar for a full explanation on 'col_inf' and 'col_sup'. -#'@param color_fun subsampleg, bar_extra_labels, draw_bar_ticks, -#' draw_separators, triangle_ends_scale, bar_label_digits, bar_label_scale, -#' units_scale, bar_tick_scale, bar_extra_margin. Set of parameters to control -#' the visual aspect of the drawn colour bar. See ?ColorBar for a full -#' explanation. +#'@param color_fun,subsampleg,bar_extra_labels,draw_bar_ticks Set of +#' parameters to control the visual aspect of the drawn colour bar +#' (1/3). See ?ColorBar for a full explanation. +#'@param draw_separators,triangle_ends_scale,bar_label_digits Set of +#' parameters to control the visual aspect of the drawn colour bar +#' (2/3). See ?ColorBar for a full explanation. +#'@param bar_label_scale,units_scale,bar_tick_scale,bar_extra_margin Set of +#' parameters to control the visual aspect of the drawn colour bar (3/3). +#' See ?ColorBar for a full explanation. #'@param square Logical value to choose either to draw a coloured square for #' each grid cell in 'var' (TRUE; default) or to draw contour lines and fill #' the spaces in between with colours (FALSE). In the latter case, @@ -312,14 +316,6 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, stop("Parameter 'var' must be a numeric array.") } if (length(dim(var)) > 2) { - if (any(dim(var) == 1)) { - var <- drop(var) - dim(var) <- head(c(dim(var), 1, 1), 2) - .warning("Parameter 'var' has more than 2 dimensions. Dimensions with length 1 have been dropped.") - } else { - stop("Parameter 'var' must be a numeric array with two dimensions. See PlotMultiMap() for multi-pannel maps or AnimateMap() for animated maps.") - } - } else if (length(dim(var)) < 2) { stop("Parameter 'var' must be a numeric array with two dimensions.") } diff --git a/man/PlotEquiMap.Rd b/man/PlotEquiMap.Rd index 4c1f819..19ff838 100644 --- a/man/PlotEquiMap.Rd +++ b/man/PlotEquiMap.Rd @@ -142,11 +142,9 @@ colors returned by 'color_fun'. If not available, it takes 'pink' by default. 'col_inf' and 'col_sup' will take the value of 'colNA' if not specified. See ?ColorBar for a full explanation on 'col_inf' and 'col_sup'.} -\item{color_fun}{subsampleg, bar_extra_labels, draw_bar_ticks, -draw_separators, triangle_ends_scale, bar_label_digits, bar_label_scale, -units_scale, bar_tick_scale, bar_extra_margin. Set of parameters to control -the visual aspect of the drawn colour bar. See ?ColorBar for a full -explanation.} +\item{color_fun, subsampleg, bar_extra_labels, draw_bar_ticks}{Set of +parameters to control the visual aspect of the drawn colour bar +(1/3). See ?ColorBar for a full explanation.} \item{square}{Logical value to choose either to draw a coloured square for each grid cell in 'var' (TRUE; default) or to draw contour lines and fill @@ -296,6 +294,14 @@ and latitude axes.} TRUE. It is not possible to plot the colour bar if 'add = TRUE'. Use ColorBar() and the return values of PlotEquiMap() instead.} +\item{draw_separators, triangle_ends_scale, bar_label_digits}{Set of +parameters to control the visual aspect of the drawn colour bar +(2/3). See ?ColorBar for a full explanation.} + +\item{bar_label_scale, units_scale, bar_tick_scale, bar_extra_margin}{Set of +parameters to control the visual aspect of the drawn colour bar (3/3). +See ?ColorBar for a full explanation.} + \item{boxlim}{Limits of a box to be added to the plot, in degrees: c(x1, y1, x2, y2). A list with multiple box specifications can also be provided.} -- GitLab From 1bf1021c82840a53e3ecf5e496dce05b66dbbb8d Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Mon, 10 Oct 2022 17:30:46 +0200 Subject: [PATCH 122/140] Correct var checks for dim of lenght 1 --- R/PlotEquiMap.R | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/R/PlotEquiMap.R b/R/PlotEquiMap.R index c87aed4..1a61f5a 100644 --- a/R/PlotEquiMap.R +++ b/R/PlotEquiMap.R @@ -307,6 +307,9 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, if (!is.numeric(lon) || !is.numeric(lat)) { stop("Parameters 'lon' and 'lat' must be numeric vectors.") } + if (length(lon) == 1 || length(lat) == 1) { + stop("Parameters 'lon' and 'lat' must have length larger than 1.") + } # Check var if (is.null(var)) { @@ -315,9 +318,6 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, if (!is.array(var)) { stop("Parameter 'var' must be a numeric array.") } - if (length(dim(var)) > 2) { - stop("Parameter 'var' must be a numeric array with two dimensions.") - } transpose <- FALSE if (!is.null(names(dim(var)))) { @@ -337,6 +337,19 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, .warning("Parameter 'var' should have dimension names. Coordinates 'lon' and 'lat' have been assigned into the corresponding coordinates dimensions.") } + if (length(dim(var)) > 2) { + if (!is.null(lon_dim) & !is.null(lat_dim)) { + dimnames <- names(dim(var)) + dim(var) <- dim(var)[which((dimnames == lon_dim | dimnames == lat_dim | dim(var) != 1))] + } else { + var <- drop(var) + } + } + + if (length(dim(var)) != 2) { + stop("Parameter 'var' must be a numeric array with two dimensions.") + } + if ((dim(var)[1] == length(lon) && dim(var)[2] == length(lat)) || (dim(var)[2] == length(lon) && dim(var)[1] == length(lat))) { if (dim(var)[2] == length(lon) && dim(var)[1] == length(lat)) { -- GitLab From d4d5c0e46bee11a0e45ab73f4c40fbe1edcdd263 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Mon, 10 Oct 2022 17:54:40 +0200 Subject: [PATCH 123/140] Add condition for lon and lat 1 --- R/PlotEquiMap.R | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/R/PlotEquiMap.R b/R/PlotEquiMap.R index 1a61f5a..994604c 100644 --- a/R/PlotEquiMap.R +++ b/R/PlotEquiMap.R @@ -307,7 +307,7 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, if (!is.numeric(lon) || !is.numeric(lat)) { stop("Parameters 'lon' and 'lat' must be numeric vectors.") } - if (length(lon) == 1 || length(lat) == 1) { + if ((length(lon) == 1 || length(lat) == 1) && is.null(userArgs$usr)) { stop("Parameters 'lon' and 'lat' must have length larger than 1.") } @@ -342,7 +342,13 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, dimnames <- names(dim(var)) dim(var) <- dim(var)[which((dimnames == lon_dim | dimnames == lat_dim | dim(var) != 1))] } else { - var <- drop(var) + if (all(dim(var) == 1)) { + dim(var) <- c(1, 1) + } else if (length(dim(var)[which(dim(var) > 1)]) == 2) { + var <- drop(var) + } else if (length(dim(var)[which(dim(var) > 1)]) == 1) { + dim(var) <- c(dim(var)[which(dim(var) > 1)], 1) + } } } -- GitLab From 02b36ce941e336da49b5885c05ffce162410a86b Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Tue, 11 Oct 2022 12:39:05 +0200 Subject: [PATCH 124/140] Delete restriction to avoid error --- R/PlotEquiMap.R | 3 --- 1 file changed, 3 deletions(-) diff --git a/R/PlotEquiMap.R b/R/PlotEquiMap.R index 994604c..c06f7bb 100644 --- a/R/PlotEquiMap.R +++ b/R/PlotEquiMap.R @@ -307,9 +307,6 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, if (!is.numeric(lon) || !is.numeric(lat)) { stop("Parameters 'lon' and 'lat' must be numeric vectors.") } - if ((length(lon) == 1 || length(lat) == 1) && is.null(userArgs$usr)) { - stop("Parameters 'lon' and 'lat' must have length larger than 1.") - } # Check var if (is.null(var)) { -- GitLab From 2369358026689133be52eed48e679d9ba4ac4f0d Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Tue, 11 Oct 2022 16:24:22 +0200 Subject: [PATCH 125/140] two-sided test with Steiger (1980) --- R/DiffCorr.R | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/R/DiffCorr.R b/R/DiffCorr.R index 95996b6..0fec699 100644 --- a/R/DiffCorr.R +++ b/R/DiffCorr.R @@ -7,7 +7,7 @@ #'correlation differences is computed with a one-sided or two-sided test for #'equality of dependent correlation coefficients (Steiger, 1980; Siegert et al., #'2017) using effective degrees of freedom to account for the autocorrelation of -#'the time series (von Storch and Zwiers, 1999). +#'the time series (Zwiers and von Storch, 1995). #' #'@param exp A named numerical array of the forecast data with at least time #' dimension. @@ -40,10 +40,9 @@ #' NA. The default value is "return.na". #'@param test.type A character string indicating the type of significance test. #' It can be "two-sided" (to assess whether the skill of "exp" and "ref" are -#' significantly different with a z-test on Fisher z-transformed correlation -#' coefficients, Hinkel et al, 1988) or "one-sided" (to assess whether the -#' skill of "exp" is significantly higher than that of "ref" with the test -#' described in Steiger, 1980). The default value is "two-sided". +#' significantly different) or "one-sided" (to assess whether the +#' skill of "exp" is significantly higher than that of "ref") following Steiger +#' (1980). The default value is "two-sided". #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -66,7 +65,7 @@ #'@references #'Steiger, 1980; https://content.apa.org/doi/10.1037/0033-2909.87.2.245 #'Siegert et al., 2017; https://doi.org/10.1175/MWR-D-16-0037.1 -#'von Storch and Zwiers, 1999; https://doi.org/10.1017/CBO9780511612336 +#'Zwiers and von Storch, 1995; https://doi.org/10.1175/1520-0442(1995)008%3C0336:TSCIAI%3E2.0.CO;2 #' #'@examples #' exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) @@ -227,14 +226,20 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', N.eff <- .Eno(x = obs, na.action = na.pass) ## effective degrees of freedom } + # Significance with one-sided or two-sided test for equality of dependent correlation coefficients (Steiger, 1980) + r12 <- cor.exp + r13 <- cor.ref + r23 <- cor(exp, ref) + R <- (1 - r12 * r12 - r13 * r13 - r23 * r23) + 2 * r12 * r13 * r23 + t <- (r12 - r13) * sqrt((N.eff - 1) * (1 + r23) / (2 * ((N.eff - 1) / (N.eff - 3)) * R + 0.25 * (r12 + r13)^2 * (1 - r23)^3)) + if (test.type == 'one-sided') { - # Significance with one-sided test for equality of dependent correlation coefficients (Steiger, 1980) - r12 <- cor.exp - r13 <- cor.ref - r23 <- cor(exp, ref) - R <- (1 - r12 * r12 - r13 * r13 - r23 * r23) + 2 * r12 * r13 * r23 - t <- (r12 - r13) * sqrt((N.eff - 1) * (1 + r23) / (2 * ((N.eff - 1) / (N.eff - 3)) * R + 0.25 * (r12 + r13)^2 * (1 - r23)^3)) + + ## H0: the skill of exp is not higher than that of ref + ## H1: the skill of exp is higher than that of ref + p.value <- pt(t, df = N.eff - 3, lower.tail = FALSE) + if (is.null(alpha)) { output$p.val <- p.value } else { @@ -242,10 +247,11 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', } } else if (test.type == 'two-sided') { - # Significance with two-sided z-test on Fisher z-transformed correlation coefficients (Hinkel et al, 1988) - Z <- abs(0.5 * log((1 + cor.exp) / (1 - cor.exp)) - 0.5 * log((1 + cor.ref) / (1 - cor.ref))) / sqrt(1/ (N.eff - 3) + 1/(N.eff - 3)) - # sign <- Z >= qnorm(p = alpha/2, lower.tail = FALSE) - p.value <- pnorm(q = Z, lower.tail = FALSE) + + ## H0: the skill difference of exp and ref is zero + ## H1: the skill difference of exp and ref is different from zero + + p.value <- pt(abs(t), df = N.eff - 3, lower.tail = FALSE) if (is.null(alpha)) { output$p.val <- p.value -- GitLab From 2830084f975e6b900380d789b017589e35ce195f Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Tue, 11 Oct 2022 16:30:10 +0200 Subject: [PATCH 126/140] modified tests --- R/DiffCorr.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/DiffCorr.R b/R/DiffCorr.R index 0fec699..270853f 100644 --- a/R/DiffCorr.R +++ b/R/DiffCorr.R @@ -71,8 +71,8 @@ #' exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) #' obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) #' ref <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) -#' res_two.sided_sign <- DiffCorr(exp, obs, ref, memb_dim = 'member', alpha = 0.05) -#' res_one.sided_pval <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'one-sided') +#' res_two.sided_sign <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'two-sided', alpha = 0.05) +#' res_one.sided_pval <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'one-sided', alpha = 0.05) #' #'@import multiApply #'@export -- GitLab From 233f2aa4a209afcb7c335b0a1d508b376a790aa5 Mon Sep 17 00:00:00 2001 From: aho Date: Tue, 11 Oct 2022 20:01:16 +0200 Subject: [PATCH 127/140] Modify ProbsColorBar --- R/PlotLayout.R | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/R/PlotLayout.R b/R/PlotLayout.R index 12df9f8..a5d2aed 100644 --- a/R/PlotLayout.R +++ b/R/PlotLayout.R @@ -550,11 +550,13 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, subtitle_margin <- 0.5 * sqrt(nrow * ncol) * subtitle_cex * subtitle_margin_scale mat_layout <- 1:(nrow * ncol) if (drawleg != FALSE) { - if (fun == 'PlotMostLikelyQuantileMap') { #multi_colorbar + if (all(fun %in% 'PlotMostLikelyQuantileMap')) { #multi_colorbar multi_colorbar <- TRUE cat_dim <- list(...)$cat_dim if (is.null(cat_dim)) cat_dim <- 'bin' # default nmap <- as.numeric(dim(var[[1]])[cat_dim]) + minimum_value <- ceiling(1 / nmap * 10 * 1.1) * 10 + display_range = c(minimum_value, 100) mat_layout <- mat_layout + nmap } else { multi_colorbar <- FALSE @@ -626,13 +628,13 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, } else { bar_titles <- NULL } - multi_ColorBar(nmap = nmap, - brks = brks, cols = cols, vertical = vertical, subsampleg = subsampleg, - bar_limits = bar_limits, var_limits = var_limits, - triangle_ends = triangle_ends, plot = TRUE, - draw_separators = draw_separators, - bar_titles = bar_titles, title_scale = units_scale, - label_scale = bar_label_scale, extra_margin = bar_extra_margin) + ProbsColorBar(nmap = nmap, display_range = display_range, + brks = brks, cols = cols, vertical = vertical, subsampleg = subsampleg, + bar_limits = bar_limits, var_limits = var_limits, + triangle_ends = triangle_ends, plot = TRUE, + draw_separators = draw_separators, + bar_titles = bar_titles, title_scale = units_scale, + label_scale = bar_label_scale, extra_margin = bar_extra_margin) } else { # one colorbar ColorBar(brks = colorbar$brks, cols = colorbar$cols, vertical = vertical, subsampleg = subsampleg, -- GitLab From 1406c07f5a79c8b9ef0ae19af20382f0c8f13a5a Mon Sep 17 00:00:00 2001 From: aho Date: Tue, 11 Oct 2022 21:08:21 +0200 Subject: [PATCH 128/140] Change colorbar function name --- R/PlotLayout.R | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/R/PlotLayout.R b/R/PlotLayout.R index a5d2aed..5a0ca9f 100644 --- a/R/PlotLayout.R +++ b/R/PlotLayout.R @@ -628,13 +628,13 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, } else { bar_titles <- NULL } - ProbsColorBar(nmap = nmap, display_range = display_range, - brks = brks, cols = cols, vertical = vertical, subsampleg = subsampleg, - bar_limits = bar_limits, var_limits = var_limits, - triangle_ends = triangle_ends, plot = TRUE, - draw_separators = draw_separators, - bar_titles = bar_titles, title_scale = units_scale, - label_scale = bar_label_scale, extra_margin = bar_extra_margin) + CatColorBar(nmap = nmap, + brks = brks, cols = cols, vertical = vertical, subsampleg = subsampleg, + bar_limits = display_range, var_limits = var_limits, + triangle_ends = triangle_ends, plot = TRUE, + draw_separators = draw_separators, + bar_titles = bar_titles, title_scale = units_scale, + label_scale = bar_label_scale, extra_margin = bar_extra_margin) } else { # one colorbar ColorBar(brks = colorbar$brks, cols = colorbar$cols, vertical = vertical, subsampleg = subsampleg, -- GitLab From f124d48d3d862abd7b6f687ec64a8dba4a23fb8d Mon Sep 17 00:00:00 2001 From: aho Date: Thu, 13 Oct 2022 13:19:22 +0200 Subject: [PATCH 129/140] Update DiffCorr.Rd and correct unit test --- R/DiffCorr.R | 10 +++++----- man/DiffCorr.Rd | 15 +++++++-------- tests/testthat/test-DiffCorr.R | 34 +++++++++++++++++++++++++--------- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/R/DiffCorr.R b/R/DiffCorr.R index 270853f..a1fbae3 100644 --- a/R/DiffCorr.R +++ b/R/DiffCorr.R @@ -40,9 +40,9 @@ #' NA. The default value is "return.na". #'@param test.type A character string indicating the type of significance test. #' It can be "two-sided" (to assess whether the skill of "exp" and "ref" are -#' significantly different) or "one-sided" (to assess whether the -#' skill of "exp" is significantly higher than that of "ref") following Steiger -#' (1980). The default value is "two-sided". +#' significantly different) or "one-sided" (to assess whether the skill of +#' "exp" is significantly higher than that of "ref") following Steiger (1980). +#' The default value is "two-sided". #'@param ncores An integer indicating the number of cores to use for parallel #' computation. The default value is NULL. #' @@ -65,14 +65,14 @@ #'@references #'Steiger, 1980; https://content.apa.org/doi/10.1037/0033-2909.87.2.245 #'Siegert et al., 2017; https://doi.org/10.1175/MWR-D-16-0037.1 -#'Zwiers and von Storch, 1995; https://doi.org/10.1175/1520-0442(1995)008%3C0336:TSCIAI%3E2.0.CO;2 +#'Zwiers and von Storch, 1995; https://doi.org/10.1175/1520-0442(1995)008<0336:TSCIAI>2.0.CO;2 #' #'@examples #' exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) #' obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) #' ref <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) #' res_two.sided_sign <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'two-sided', alpha = 0.05) -#' res_one.sided_pval <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'one-sided', alpha = 0.05) +#' res_one.sided_pval <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'one-sided', alpha = NULL) #' #'@import multiApply #'@export diff --git a/man/DiffCorr.Rd b/man/DiffCorr.Rd index e0e5ec3..e907d3b 100644 --- a/man/DiffCorr.Rd +++ b/man/DiffCorr.Rd @@ -59,10 +59,9 @@ NA. The default value is "return.na".} \item{test.type}{A character string indicating the type of significance test. It can be "two-sided" (to assess whether the skill of "exp" and "ref" are -significantly different with a z-test on Fisher z-transformed correlation -coefficients, Hinkel et al, 1988) or "one-sided" (to assess whether the -skill of "exp" is significantly higher than that of "ref" with the test -described in Steiger, 1980). The default value is "two-sided".} +significantly different) or "one-sided" (to assess whether the skill of +"exp" is significantly higher than that of "ref") following Steiger (1980). +The default value is "two-sided".} \item{ncores}{An integer indicating the number of cores to use for parallel computation. The default value is NULL.} @@ -92,18 +91,18 @@ the reference forecast is more skillful. The statistical significance of the correlation differences is computed with a one-sided or two-sided test for equality of dependent correlation coefficients (Steiger, 1980; Siegert et al., 2017) using effective degrees of freedom to account for the autocorrelation of -the time series (von Storch and Zwiers, 1999). +the time series (Zwiers and von Storch, 1995). } \examples{ exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) ref <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) -res_two.sided_sign <- DiffCorr(exp, obs, ref, memb_dim = 'member', alpha = 0.05) -res_one.sided_pval <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'one-sided') +res_two.sided_sign <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'two-sided', alpha = 0.05) +res_one.sided_pval <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'one-sided', alpha = NULL) } \references{ Steiger, 1980; https://content.apa.org/doi/10.1037/0033-2909.87.2.245 Siegert et al., 2017; https://doi.org/10.1175/MWR-D-16-0037.1 -von Storch and Zwiers, 1999; https://doi.org/10.1017/CBO9780511612336 +Zwiers and von Storch, 1995; https://doi.org/10.1175/1520-0442(1995)008<0336:TSCIAI>2.0.CO;2 } diff --git a/tests/testthat/test-DiffCorr.R b/tests/testthat/test-DiffCorr.R index b8c8029..3c35818 100644 --- a/tests/testthat/test-DiffCorr.R +++ b/tests/testthat/test-DiffCorr.R @@ -18,6 +18,9 @@ set.seed(2) ref2 <- array(rnorm(10), dim = c(sdate = 10)) set.seed(3) obs2 <- array(rnorm(10), dim = c(sdate = 10)) +## generate time auto-correlation (Eno will change) +set.seed(3) +obs2_2 <- array(1:10 + rnorm(10), dim = c(sdate = 10)) ############################################## @@ -123,8 +126,8 @@ c(0.27347087, 0.50556882, 0.08855968, 0.24199701, 0.22935182, 0.88336336), tolerance = 0.0001 ) expect_equal( -as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb')$p), -c(0.28984782, 0.14395946, 0.41374677, 0.32253784, 0.32677872, 0.03659053), +as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', test.type = "two-sided")$p), +c(0.26166060, 0.15899774, 0.39264452, 0.27959883, 0.34736305, 0.07479832), tolerance = 0.0001 ) expect_equal( @@ -155,7 +158,7 @@ tolerance = 0.0001 ) expect_equal( suppressWarnings(as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', method = "spearman")$p)), -c(0.44479470, 0.11854232, 0.36667620, 0.49095264, 0.46962814, 0.01714977), +c(0.4358970, 0.1341575, 0.3448977, 0.4885738, 0.4735128, 0.0437861), tolerance = 0.0001 ) expect_equal( @@ -165,11 +168,10 @@ tolerance = 0.0001 ) expect_equal( as.vector(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', N.eff = Neff1)$p), -c(0.30406438, 0.14395946, 0.42005522, 0.32253784, 0.33887632, 0.03659053), +c(0.27841537, 0.15899774, 0.40096749, 0.27959883, 0.35889690, 0.07479832), tolerance = 0.0001 ) - #--------------------------- exp1[1] <- NA expect_equal( @@ -217,8 +219,8 @@ dim(DiffCorr(exp2, obs2, ref2)$p), NULL ) expect_equal( -DiffCorr(exp2, obs2, ref2)$p, -0.3201317, +DiffCorr(exp2, obs2, ref2, test.type = 'two-sided')$p, +0.3422608, tolerance = 0.0001 ) expect_equal( @@ -227,11 +229,11 @@ DiffCorr(exp2, obs2, ref2, test.type = 'one-sided')$p, tolerance = 0.0001 ) expect_equal( -DiffCorr(exp2, obs2, ref2, test.type = 'one-sided', alpha = 0.5)$sign, +DiffCorr(exp2, obs2, ref2, test.type = 'one-sided', alpha = 0.7)$sign, FALSE ) expect_equal( -DiffCorr(exp2, obs2, ref2, test.type = 'two-sided', alpha = 0.65)$sign, +DiffCorr(exp2, obs2, ref2, test.type = 'two-sided', alpha = 0.7)$sign, TRUE ) expect_equal( @@ -240,5 +242,19 @@ DiffCorr(exp2, obs2, ref2)$diff, tolerance = 0.0001 ) +# obs2_2 +expect_equal( +DiffCorr(exp2, obs2_2, ref2, test.type = 'two-sided')$p, +0.4524316, +tolerance = 0.0001 +) +expect_equal( +DiffCorr(exp2, obs2_2, ref2, test.type = 'one-sided')$p, +0.5475684, +tolerance = 0.0001 +) + + + }) # suppressWarnings }) -- GitLab From 56848b123069db5cc5af08d3ef75580b1caf63fa Mon Sep 17 00:00:00 2001 From: aho Date: Thu, 13 Oct 2022 16:26:31 +0200 Subject: [PATCH 130/140] Put CatColorBar() in Utils.R (temporary) --- R/Utils.R | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/R/Utils.R b/R/Utils.R index c136914..756e994 100644 --- a/R/Utils.R +++ b/R/Utils.R @@ -1720,3 +1720,92 @@ return(anom) } + +#TODO: Remove from s2dv when PlotLayout can get colorbar info from plotting function directly. +# The function is temporarily here because PlotLayout() needs to draw the colorbars of +# PlotMostLikelyQuantileMap(). +#Draws Color Bars for Categories +#A wrapper of s2dv::ColorBar to generate multiple color bars for different +#categories, and each category has different color set. +CatColorBar <- function(nmap, brks = NULL, cols = NULL, vertical = TRUE, subsampleg = NULL, + bar_limits, var_limits = NULL, + triangle_ends = NULL, plot = TRUE, + draw_separators = FALSE, + bar_titles = NULL, title_scale = 1, label_scale = 1, extra_margin = rep(0, 4), + ...) { + # bar_limits + if (!is.numeric(bar_limits) || length(bar_limits) != 2) { + stop("Parameter 'bar_limits' must be a numeric vector of length 2.") + } + + # Check brks + if (is.null(brks) || (is.numeric(brks) && length(brks) == 1)) { + num_brks <- 5 + if (is.numeric(brks)) { + num_brks <- brks + } + brks <- seq(from = bar_limits[1], to = bar_limits[2], length.out = num_brks) + } + if (!is.numeric(brks)) { + stop("Parameter 'brks' must be a numeric vector.") + } + # Check cols + col_sets <- list(c("#A1D99B", "#74C476", "#41AB5D", "#238B45"), + c("#6BAED6FF", "#4292C6FF", "#2171B5FF", "#08519CFF"), + c("#FFEDA0FF", "#FED976FF", "#FEB24CFF", "#FD8D3CFF"), + c("#FC4E2AFF", "#E31A1CFF", "#BD0026FF", "#800026FF"), + c("#FCC5C0", "#FA9FB5", "#F768A1", "#DD3497")) + if (is.null(cols)) { + if (length(col_sets) >= nmap) { + chosen_sets <- 1:nmap + chosen_sets <- chosen_sets + floor((length(col_sets) - length(chosen_sets)) / 2) + } else { + chosen_sets <- array(1:length(col_sets), nmap) + } + cols <- col_sets[chosen_sets] + } else { + if (!is.list(cols)) { + stop("Parameter 'cols' must be a list of character vectors.") + } + if (!all(sapply(cols, is.character))) { + stop("Parameter 'cols' must be a list of character vectors.") + } + if (length(cols) != nmap) { + stop("Parameter 'cols' must be a list of the same length as the number of ", + "maps in 'maps'.") + } + } + for (i in 1:length(cols)) { + if (length(cols[[i]]) != (length(brks) - 1)) { + cols[[i]] <- grDevices::colorRampPalette(cols[[i]])(length(brks) - 1) + } + } + + # Check bar_titles + if (is.null(bar_titles)) { + if (nmap == 3) { + bar_titles <- c("Below normal (%)", "Normal (%)", "Above normal (%)") + } else if (nmap == 5) { + bar_titles <- c("Low (%)", "Below normal (%)", + "Normal (%)", "Above normal (%)", "High (%)") + } else { + bar_titles <- paste0("Cat. ", 1:nmap, " (%)") + } + } + + if (plot) { + for (k in 1:nmap) { + s2dv::ColorBar(brks = brks, cols = cols[[k]], vertical = FALSE, subsampleg = subsampleg, +# bar_limits = bar_limits, var_limits = var_limits, + triangle_ends = triangle_ends, plot = TRUE, + draw_separators = draw_separators, + title = bar_titles[[k]], title_scale = title_scale, + label_scale = label_scale, extra_margin = extra_margin) + } + } else { + #TODO: col_inf and col_sup + return(list(brks = brks, cols = cols)) + } + +} + -- GitLab From 800d4bdc0d505655615115fbaf29314e05260d93 Mon Sep 17 00:00:00 2001 From: aho Date: Fri, 14 Oct 2022 12:21:28 +0200 Subject: [PATCH 131/140] Change function name to GradientCatsColorBar --- R/PlotLayout.R | 14 +++++++------- R/Utils.R | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/R/PlotLayout.R b/R/PlotLayout.R index 5a0ca9f..c442bf7 100644 --- a/R/PlotLayout.R +++ b/R/PlotLayout.R @@ -628,13 +628,13 @@ PlotLayout <- function(fun, plot_dims, var, ..., special_args = NULL, } else { bar_titles <- NULL } - CatColorBar(nmap = nmap, - brks = brks, cols = cols, vertical = vertical, subsampleg = subsampleg, - bar_limits = display_range, var_limits = var_limits, - triangle_ends = triangle_ends, plot = TRUE, - draw_separators = draw_separators, - bar_titles = bar_titles, title_scale = units_scale, - label_scale = bar_label_scale, extra_margin = bar_extra_margin) + GradientCatsColorBar(nmap = nmap, + brks = brks, cols = cols, vertical = vertical, subsampleg = subsampleg, + bar_limits = display_range, var_limits = var_limits, + triangle_ends = triangle_ends, plot = TRUE, + draw_separators = draw_separators, + bar_titles = bar_titles, title_scale = units_scale, + label_scale = bar_label_scale, extra_margin = bar_extra_margin) } else { # one colorbar ColorBar(brks = colorbar$brks, cols = colorbar$cols, vertical = vertical, subsampleg = subsampleg, diff --git a/R/Utils.R b/R/Utils.R index 756e994..c2c17eb 100644 --- a/R/Utils.R +++ b/R/Utils.R @@ -1727,12 +1727,12 @@ #Draws Color Bars for Categories #A wrapper of s2dv::ColorBar to generate multiple color bars for different #categories, and each category has different color set. -CatColorBar <- function(nmap, brks = NULL, cols = NULL, vertical = TRUE, subsampleg = NULL, - bar_limits, var_limits = NULL, - triangle_ends = NULL, plot = TRUE, - draw_separators = FALSE, - bar_titles = NULL, title_scale = 1, label_scale = 1, extra_margin = rep(0, 4), - ...) { +GradientCatsColorBar <- function(nmap, brks = NULL, cols = NULL, vertical = TRUE, subsampleg = NULL, + bar_limits, var_limits = NULL, + triangle_ends = NULL, plot = TRUE, + draw_separators = FALSE, + bar_titles = NULL, title_scale = 1, label_scale = 1, extra_margin = rep(0, 4), + ...) { # bar_limits if (!is.numeric(bar_limits) || length(bar_limits) != 2) { stop("Parameter 'bar_limits' must be a numeric vector of length 2.") -- GitLab From f35abfb7a78c54f8907c72f2c35bde58e1e6874d Mon Sep 17 00:00:00 2001 From: aho Date: Fri, 14 Oct 2022 19:00:22 +0200 Subject: [PATCH 132/140] Change all warning() to .warning() --- R/ACC.R | 2 +- R/CDORemap.R | 2 +- R/Composite.R | 2 +- R/DiffCorr.R | 8 ++++---- R/EOF.R | 2 +- R/InsertDim.R | 4 ++-- R/PlotEquiMap.R | 2 +- R/REOF.R | 6 +++--- R/RPSS.R | 6 +++--- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/R/ACC.R b/R/ACC.R index ceb1e94..64036f4 100644 --- a/R/ACC.R +++ b/R/ACC.R @@ -170,7 +170,7 @@ ACC <- function(exp, obs, dat_dim = 'dataset', lat_dim = 'lat', lon_dim = 'lon', } ## space_dim (deprecated) if (!missing("space_dim")) { - warning("Parameter 'space_dim' is deprecated. Use 'lat_dim' and 'lon_dim' instead.") + .warning("Parameter 'space_dim' is deprecated. Use 'lat_dim' and 'lon_dim' instead.") lat_dim <- space_dim[1] lon_dim <- space_dim[2] } diff --git a/R/CDORemap.R b/R/CDORemap.R index ce3ed35..f0044cb 100644 --- a/R/CDORemap.R +++ b/R/CDORemap.R @@ -632,7 +632,7 @@ CDORemap <- function(data_array = NULL, lons, lats, grid, method, cdo_version <- as.numeric_version( strsplit(suppressWarnings(system2("cdo", args = '-V', stderr = TRUE))[[1]], ' ')[[1]][5] ) - warning("CDORemap: Using CDO version ", cdo_version, ".") + .warning(paste0("CDORemap: Using CDO version ", cdo_version, ".")) if ((cdo_version >= as.numeric_version('1.7.0')) && (method == 'con')) { method <- 'ycon' } diff --git a/R/Composite.R b/R/Composite.R index be03ac9..03f0d58 100644 --- a/R/Composite.R +++ b/R/Composite.R @@ -170,7 +170,7 @@ Composite <- function(data, occ, time_dim = 'time', space_dim = c('lon', 'lat'), count_k <- plyr::count(occ) if (any(count_k$freq == 1)) { tmp <- count_k$x[which(count_k$freq == 1)] - warning(paste0("Composite K = ", tmp, " has length 1. The p-value is NA.")) + .warning(paste0("Composite K = ", tmp, " has length 1. The p-value is NA.")) } output_dims <- list(composite = c(space_dim, 'K'), diff --git a/R/DiffCorr.R b/R/DiffCorr.R index 95996b6..64e2743 100644 --- a/R/DiffCorr.R +++ b/R/DiffCorr.R @@ -135,9 +135,9 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', stop('Parameter "method" must be "pearson" or "spearman".') } if (method == "spearman") { - warning(paste0("The test used in this function is built on Pearson method. ", - "To verify if Spearman method is reliable, you can run the ", - "Monte-Carlo simulations that are done in Siegert et al., 2017")) + .warning(paste0("The test used in this function is built on Pearson method. ", + "To verify if Spearman method is reliable, you can run the ", + "Monte-Carlo simulations that are done in Siegert et al., 2017")) } ## alpha if (!is.null(alpha)) { @@ -155,7 +155,7 @@ DiffCorr <- function(exp, obs, ref, N.eff = NA, time_dim = 'sdate', stop("Parameter 'test.type' must be 'two-sided' or 'one-sided'.") } #NOTE: warning can be removed in the next release - warning("The default significance test has changed after s2dv_1.2.0. The default method is 'two-sided'.") + .warning("The default significance test has changed after s2dv_1.2.0. The default method is 'two-sided'.") ## ncores if (!is.null(ncores)) { diff --git a/R/EOF.R b/R/EOF.R index d5c79a6..66e69da 100644 --- a/R/EOF.R +++ b/R/EOF.R @@ -131,7 +131,7 @@ EOF <- function(ano, lat, lon, time_dim = 'sdate', space_dim = c('lat', 'lon'), "length as the longitude dimension of 'ano'.")) } if (any(lon > 360 | lon < -360)) { - warning("Some 'lon' is out of the range [-360, 360].") + .warning("Some 'lon' is out of the range [-360, 360].") } ## neofs if (!is.numeric(neofs) | neofs %% 1 != 0 | neofs <= 0 | length(neofs) > 1) { diff --git a/R/InsertDim.R b/R/InsertDim.R index 36ce2f8..533683d 100644 --- a/R/InsertDim.R +++ b/R/InsertDim.R @@ -52,7 +52,7 @@ InsertDim <- function(data, posdim, lendim, name = NULL, ncores = NULL) { if (is.null(name)) { if (is.null(names(lendim))) { name <- 'new' - warning("The name of new dimension is not given. Set the name as 'new'.") + .warning("The name of new dimension is not given. Set the name as 'new'.") } else { name <- names(lendim) } @@ -63,7 +63,7 @@ InsertDim <- function(data, posdim, lendim, name = NULL, ncores = NULL) { } ## ncores if (!missing("ncores")) - warning("Argument 'ncores' is deprecated.") + .warning("Argument 'ncores' is deprecated.", tag = '! Deprecation: ') ############################### # Calculate InsertDim diff --git a/R/PlotEquiMap.R b/R/PlotEquiMap.R index c06f7bb..2c98430 100644 --- a/R/PlotEquiMap.R +++ b/R/PlotEquiMap.R @@ -929,7 +929,7 @@ PlotEquiMap <- function(var, lon, lat, varu = NULL, varv = NULL, dlon <- diff(lon) wher <- which(dlon > (mean(dlon) + 1)) if (length(wher) > 0) { - warning("Detect gap in 'lon' vector, which is considered as crossing the border.") + .warning("Detect gap in 'lon' vector, which is considered as crossing the border.") lon[(wher + 1):dims[1]] <- lon[(wher + 1):dims[1]] - 360 } lonb <- sort(lon, index.return = TRUE) diff --git a/R/REOF.R b/R/REOF.R index 2def222..c9c82cf 100644 --- a/R/REOF.R +++ b/R/REOF.R @@ -146,9 +146,9 @@ REOF <- function(ano, lat, lon, ntrunc = 15, time_dim = 'sdate', # ntrunc is bounded if (ntrunc != min(dim(ano)[time_dim], prod(dim(ano)[space_dim]), ntrunc)) { ntrunc <- min(dim(ano)[time_dim], prod(dim(ano)[space_dim]), ntrunc) - warning(paste0("Parameter 'ntrunc' is changed to ", ntrunc, ", the minimum among ", - "the length of time_dim, the production of the length of space_dim, ", - "and ntrunc.")) + .warning(paste0("Parameter 'ntrunc' is changed to ", ntrunc, ", the minimum among ", + "the length of time_dim, the production of the length of space_dim, ", + "and ntrunc.")) } # Area weighting is needed to compute the fraction of variance explained by diff --git a/R/RPSS.R b/R/RPSS.R index b5ff907..3d50d2b 100644 --- a/R/RPSS.R +++ b/R/RPSS.R @@ -200,9 +200,9 @@ RPSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = 'member', } ## weights if (!is.null(weights)) { - warning(paste0("Parameter 'weights' is deprecated and will be removed in the next release. ", - "Use 'weights_exp' and 'weights_ref' instead. The value will be assigned ", - "to these two parameters now if they are NULL.")) + .warning(paste0("Parameter 'weights' is deprecated and will be removed in the next release. ", + "Use 'weights_exp' and 'weights_ref' instead. The value will be assigned ", + "to these two parameters now if they are NULL."), tag = '! Deprecation: ') if (is.null(weights_exp)) weights_exp <- weights if (is.null(weights_ref)) weights_ref <- weights } -- GitLab From 39932d420cb38d1af7a960ac44d0a83018f126b9 Mon Sep 17 00:00:00 2001 From: aho Date: Mon, 17 Oct 2022 09:52:45 +0200 Subject: [PATCH 133/140] Revise unit test for the warning --- tests/testthat/test-ACC.R | 2 +- tests/testthat/test-DiffCorr.R | 9 ++++----- tests/testthat/test-REOF.R | 7 +++---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/testthat/test-ACC.R b/tests/testthat/test-ACC.R index 04e60ad..6431a9c 100644 --- a/tests/testthat/test-ACC.R +++ b/tests/testthat/test-ACC.R @@ -62,7 +62,7 @@ test_that("1. Input checks", { # space_dim (deprecated) expect_warning( ACC(exp1, obs1, space_dim = c('lat', 'lon'), lat = c(1, 2)), - "Parameter 'space_dim' is deprecated. Use 'lat_dim' and 'lon_dim' instead." + "! Warning: Parameter 'space_dim' is deprecated. Use 'lat_dim' and 'lon_dim'\n! instead." ) # lat_dim expect_error( diff --git a/tests/testthat/test-DiffCorr.R b/tests/testthat/test-DiffCorr.R index b8c8029..3498e8e 100644 --- a/tests/testthat/test-DiffCorr.R +++ b/tests/testthat/test-DiffCorr.R @@ -72,11 +72,10 @@ test_that("1. Input checks", { DiffCorr(exp1, obs1, ref1, method = 'asd', memb_dim = 'memb'), 'Parameter "method" must be "pearson" or "spearman".' ) - expect_warning( - DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', method = 'spearman'), - paste0("The test used in this function is built on Pearson method. ", - "To verify if Spearman method is reliable, you can run the ", - "Monte-Carlo simulations that are done in Siegert et al., 2017") + tmp <- capture_warnings(DiffCorr(exp1, obs1, ref1, memb_dim = 'memb', method = 'spearman')) + expect_match( + tmp, + "! Warning: The test used in this function is built on Pearson method. To verify if\n! Spearman method is reliable, you can run the Monte-Carlo simulations\n! that are done in Siegert et al., 2017", all = F ) # alpha expect_error( diff --git a/tests/testthat/test-REOF.R b/tests/testthat/test-REOF.R index e118e1e..39747e2 100644 --- a/tests/testthat/test-REOF.R +++ b/tests/testthat/test-REOF.R @@ -92,10 +92,9 @@ test_that("1. Input checks", { ############################################## test_that("2. dat1", { - expect_warning( - REOF(dat1, lon = lon1, lat = lat1), - "Parameter 'ntrunc' is changed to 10, the minimum among the length of time_dim, the production of the length of space_dim, and ntrunc.", - fixed = TRUE + expect_equal( + suppressWarnings(REOF(dat1, lon = lon1, lat = lat1)), + REOF(dat1, lon = lon1, lat = lat1, ntrunc = 10) ) expect_equal( names(REOF(dat1, lon = lon1, lat = lat1, ntrunc = 10)), -- GitLab From 87be2145503904bca7885e64c728704d443a3d20 Mon Sep 17 00:00:00 2001 From: aho Date: Mon, 17 Oct 2022 10:27:42 +0200 Subject: [PATCH 134/140] Version bump to 1.3.0; license changed to GPL-3 --- DESCRIPTION | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e8382cd..74364ff 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: s2dv Title: A Set of Common Tools for Seasonal to Decadal Verification -Version: 1.2.0 +Version: 1.3.0 Authors@R: c( person("BSC-CNS", role = c("aut", "cph")), person("An-Chi", "Ho", , "an.ho@bsc.es", role = c("aut", "cre")), @@ -10,7 +10,8 @@ Authors@R: c( person("Carlos", "Delgado", , "carlos.delgado@bsc.es", role = "ctb"), person("Llorenç", "Lledó", , "llorenc.lledo@bsc.es", role = "ctb"), person("Andrea", "Manrique", , "andrea.manrique@bsc.es", role = "ctb"), - person("Deborah", "Verfaillie", , "deborah.verfaillie@bsc.es", role = "ctb")) + person("Deborah", "Verfaillie", , "deborah.verfaillie@bsc.es", role = "ctb"), + person("Eva", "Rifà", , "eva.rifarovira@bsc.es", role = "ctb")) Description: The advanced version of package 's2dverification'. It is intended for 'seasonal to decadal' (s2d) climate forecast verification, but it can also be used in other kinds of forecasts or general climate analysis. @@ -19,7 +20,8 @@ Description: The advanced version of package 's2dverification'. It is from data retrieval, data post-processing, skill scores against observation, to visualization. Compared to 's2dverification', 's2dv' is more compatible with the package 'startR', able to use multiple cores for computation and - handle multi-dimensional arrays with a higher flexibility. + handle multi-dimensional arrays with a higher flexibility. The CDO version used + in development is 1.9.8. Depends: maps, methods, @@ -42,7 +44,7 @@ Imports: easyVerification Suggests: testthat -License: Apache License 2.0 +License: GPL-3 URL: https://earth.bsc.es/gitlab/es/s2dv/ BugReports: https://earth.bsc.es/gitlab/es/s2dv/-/issues LazyData: true -- GitLab From 06ac10c0222f42a51ae48361a2c736dc4b0edfc1 Mon Sep 17 00:00:00 2001 From: aho Date: Mon, 17 Oct 2022 12:19:33 +0200 Subject: [PATCH 135/140] Change license back to Apache 2.0; move 'methods' and 'maps' from Depends to Imports --- DESCRIPTION | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 74364ff..3a79ba6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -23,15 +23,15 @@ Description: The advanced version of package 's2dverification'. It is handle multi-dimensional arrays with a higher flexibility. The CDO version used in development is 1.9.8. Depends: - maps, - methods, R (>= 3.6.0) Imports: abind, bigmemory, graphics, grDevices, + maps, mapproj, + methods, parallel, ClimProjDiags, stats, @@ -44,7 +44,7 @@ Imports: easyVerification Suggests: testthat -License: GPL-3 +License: Apache License 2.0 URL: https://earth.bsc.es/gitlab/es/s2dv/ BugReports: https://earth.bsc.es/gitlab/es/s2dv/-/issues LazyData: true -- GitLab From a871776825f41f626b722a26be92fa95eb39d1a6 Mon Sep 17 00:00:00 2001 From: aho Date: Mon, 17 Oct 2022 14:23:57 +0200 Subject: [PATCH 136/140] Update NEWS.md --- NEWS.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index d65a824..c7daf93 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,15 @@ +# s2dv 1.3.0 (Release date: 2022-10-17) +- New functions: Bias, AbsBiasSS, CRPS, CRPSS +- split RPSS parameter 'weights' into 'weights_exp' and 'weights_ref' +- The warning message format is consistent; use internal function .warning() for all the cases +- PlotEquiMap() bugfixes when lon vector is not continuous +- PlotEquiMap() parameter "dots", "varu", "varv", and "contours" array latitude and longitude dimension order is flexible +- PlotLayout(): Add parameter to change subplot title size +- PlotLayout works with CSTools::PlotMostLikelyQuantileMap() +- Parameter "dat_dim" can be NULL in all functions +- Add "dat_dim" in RPS and RPSS to allow multiple datasets to be calculated +- DiffCorr: Add two-sided significance test. New param "test.type" to specify the one- or two-sided significance test. + # s2dv 1.2.0 (Release date: 2022-06-22) - Cluster(): Fix a bug of calculating nclusters ("K"): the function didn't use the whole data to calculate "K" if parameter "nclusters" is NULL.; Add missing output dimension names - Clim(): Correct the output dimensions for some cases; allow dat_dim to be NULL; obs doesn't need to have dat_dim. @@ -8,7 +20,7 @@ - PlotEquiMap(): Add useRaster = TRUE in image() if possible (i.e., latitude and longitude are regularly spaced.) - PlotEquiMap(): New parameters xlonshft ylatshft xlabels ylabels for self-defined axis - PlotEquiMap(): Flexible map longitude range -- New function: WeightCells, DiffCorr, ResidualCorr, RPS, RPSS +- New function: DiffCorr, ResidualCorr, RPS, RPSS - Clim() and MeanDims() efficiency improvement - CDORemap(): Add arbitrary time metadata to avoid cdo warning like "Warning (find_time_vars): Time variable >time< not found!" - CDORemap(): Stop printing messages from cdo command. -- GitLab From 3c3a511860549c16a2452d9ad0d2f5ddf9b8b4a5 Mon Sep 17 00:00:00 2001 From: aho Date: Mon, 17 Oct 2022 14:44:55 +0200 Subject: [PATCH 137/140] Limit the line width under 100 characters --- R/DiffCorr.R | 6 ++++-- man/DiffCorr.Rd | 6 ++++-- man/s2dv-package.Rd | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/R/DiffCorr.R b/R/DiffCorr.R index 8901107..1e07458 100644 --- a/R/DiffCorr.R +++ b/R/DiffCorr.R @@ -71,8 +71,10 @@ #' exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) #' obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) #' ref <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) -#' res_two.sided_sign <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'two-sided', alpha = 0.05) -#' res_one.sided_pval <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'one-sided', alpha = NULL) +#' res_two.sided_sign <- DiffCorr(exp, obs, ref, memb_dim = 'member', +#' test.type = 'two-sided', alpha = 0.05) +#' res_one.sided_pval <- DiffCorr(exp, obs, ref, memb_dim = 'member', +#' test.type = 'one-sided', alpha = NULL) #' #'@import multiApply #'@export diff --git a/man/DiffCorr.Rd b/man/DiffCorr.Rd index e907d3b..d127af8 100644 --- a/man/DiffCorr.Rd +++ b/man/DiffCorr.Rd @@ -97,8 +97,10 @@ the time series (Zwiers and von Storch, 1995). exp <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) obs <- array(rnorm(1000), dim = c(lat = 3, lon = 2, sdate = 50)) ref <- array(rnorm(1000), dim = c(lat = 3, lon = 2, member = 10, sdate = 50)) -res_two.sided_sign <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'two-sided', alpha = 0.05) -res_one.sided_pval <- DiffCorr(exp, obs, ref, memb_dim = 'member', test.type = 'one-sided', alpha = NULL) +res_two.sided_sign <- DiffCorr(exp, obs, ref, memb_dim = 'member', + test.type = 'two-sided', alpha = 0.05) +res_one.sided_pval <- DiffCorr(exp, obs, ref, memb_dim = 'member', + test.type = 'one-sided', alpha = NULL) } \references{ diff --git a/man/s2dv-package.Rd b/man/s2dv-package.Rd index 3c98a95..f0c407d 100644 --- a/man/s2dv-package.Rd +++ b/man/s2dv-package.Rd @@ -6,7 +6,7 @@ \alias{s2dv-package} \title{s2dv: A Set of Common Tools for Seasonal to Decadal Verification} \description{ -The advanced version of package 's2dverification'. It is intended for 'seasonal to decadal' (s2d) climate forecast verification, but it can also be used in other kinds of forecasts or general climate analysis. This package is specially designed for the comparison between the experimental and observational datasets. The functionality of the included functions covers from data retrieval, data post-processing, skill scores against observation, to visualization. Compared to 's2dverification', 's2dv' is more compatible with the package 'startR', able to use multiple cores for computation and handle multi-dimensional arrays with a higher flexibility. +The advanced version of package 's2dverification'. It is intended for 'seasonal to decadal' (s2d) climate forecast verification, but it can also be used in other kinds of forecasts or general climate analysis. This package is specially designed for the comparison between the experimental and observational datasets. The functionality of the included functions covers from data retrieval, data post-processing, skill scores against observation, to visualization. Compared to 's2dverification', 's2dv' is more compatible with the package 'startR', able to use multiple cores for computation and handle multi-dimensional arrays with a higher flexibility. The CDO version used in development is 1.9.8. } \references{ \url{https://earth.bsc.es/gitlab/es/s2dv/} @@ -36,6 +36,7 @@ Other contributors: \item Llorenç Lledó \email{llorenc.lledo@bsc.es} [contributor] \item Andrea Manrique \email{andrea.manrique@bsc.es} [contributor] \item Deborah Verfaillie \email{deborah.verfaillie@bsc.es} [contributor] + \item Eva Rifà \email{eva.rifarovira@bsc.es} [contributor] } } -- GitLab From fbd268f7742907631580793b2e65d9825e4a55c0 Mon Sep 17 00:00:00 2001 From: aho Date: Tue, 18 Oct 2022 10:44:57 +0200 Subject: [PATCH 138/140] update manual and hide unit test when submitted to CRAN --- .Rbuildignore | 5 +++-- s2dv-manual.pdf | Bin 404372 -> 418182 bytes 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.Rbuildignore b/.Rbuildignore index 67f31a7..6008b57 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -10,7 +10,8 @@ README\.md$ vignettes .gitlab-ci.yml # unit tests should be ignored when building the package for CRAN -#^tests$ +^tests$ # CDO is not in windbuilder, so we can test the unit tests by winbuilder -# but test-CDORemap.R needs to be hidden +# but test-CDORemap.R and test-Load.R needs to be hidden #tests/testthat/test-CDORemap.R +#tests/testthat/test-Load.R diff --git a/s2dv-manual.pdf b/s2dv-manual.pdf index 2b6da42257033ded7f67d7307378f372ae3a10fe..b4929e91355acf41086488ce94a23527e278f884 100644 GIT binary patch delta 370323 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%0D2qqPyBEKo#i1s3n6OS2Bv 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 YR$cWu!LyRqbZ{#o%ztVp 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

5#4MhdgZQyaC@0oqP2D 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≻KN%^W?I$M3*%JS6> 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�iBWNIr`N30p)&j9dzF> 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

ej<~Rtahobsj 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

=YaVz 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 delta 357284 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

=Tm~uEiX)GQjq12ivkh_yvKp*872LV?E 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

^3pu@f=4{Q zV)J~r#JE=y-ZQ;umM8Iafefpl{7xgOcFLHQok9Bww_E%xV6H8C2s%ywEU17KtgqaV zmG?1*cC4KUo6(T_^vDV%xZ|%OMj;V3TY_Q6J4U%ym{i#ZN>X41j+LtVOBjb4j`WbC z??6U)i>#52N&`oH+w_ACUg*wl8A9@4L$d;3_}?xP0=h)NF$oBIL}K6gx#XO1;l6`q zW73)@cUqJe5bGA5pt{~p)Jcwca0k#%iR6p^^w0k63e<&{OfWC4Db3HC9bQ2=_^kK} z^pXPmM1{!}lgB#u_`0IsNE?bE=)|U+@AjcDnFZ*v+N|)j@3TD?Yrf8?%Bol;hP2IL zC%+xXppxWTgPPY)9JDVI77@6V610-PXT9`8R+Bdn7}V_4i1@=CT(IwF1O^XY=@;aW zIA@U}`ZH)vHK+O@FWBJN03q_Wds*H1bWaDd!=4qm$R*u_Zc}o9Xq?*VUu+%^M(x@} zo$|V)s1H46TMhZz(Zy=`*%nrt8EJ>Qrs`R}Gg{gjC-S_O`(v5IC|*>-bV95*XA3Ds zWo;r7)X{j5JTQ7cPwU0Zry9n65GQ5nKT`^M&Cpc$x@6g7qQjM)K zkA>(}|2}e$34J~o9tbMIH8*kEvTzG(2%eK720pQeb=A5H0x9jafB600UezgJ=D4^3 znA|~r=fJaUsQ)uAZqY}Vu%xU?D7Z@ApslZdIBgtIOFz}vg0XjM+=%P-jjDAk1PX68 zen-FJC2bWG%mDRejE^L(n%x;?vzHbO=$l!tt2m4zDY5XM%cIwn=~~9Q+3JfUjYP#B zfYpCW_P&@}%GUw}sawX|`|Y}LN`G2iL#HJnQF7Kd-0I0|fp;nq@ood8y^X&Zxvpsl z-Z~ntpf-iw1y&?}9#TU7c?kQnfgY!y)fw^>vPs&{@Ko^!tc79aH8s3kXUK|}y-ETJ zto`&se3rUt#sywxkphf=C?wP6eskv38R)odBW?W#CFAq}KG#vzK^#{vsG9VHbmOn6 z)xrH#kU;YQ^Vd>F(O~HcIsSf!3U@QK7@3lu)&xt(;`j#6r04jCVwW42;4`4f?S;5q zpGw{qGYZ_v{y@GmG-IO}=w9c^KgZ}%@3i|#Emy1a-{ZNfzp55?Y@_wzO(v?R%zn*ySW01E{|ukbXKe>p!RnxR#*>KrNj*OB*3&^p867j zH#}Qzm3^C3KJdk*Q+&Q-lej~@CGc%S8>ebq$L@g{te^GgU_)5Uh{+*$4Rne5b`Fz4 zqnoGQd@qIEd{XGD!xt!|37@fc!C8aljMt~64hUS~m^mzSAk=b+ zyLe+Tu2+H--i~qQ4I|>jHHmdxo$+udCYO1%!*%#Q2`!A~kFR|5Wcyt!DI*>Pvo<4)Vp!(q!Hsdwc`y{crABZE($*7>}?eE!qs19?>2UK(u1`o4=j=YphyeVZ&9@LnhAp+y6&@q)KK10NuK)s0Yo zE(lPvJ?;IRND0-TumSG4X|Jykn;z$exrjy|noe;3bY{*wYdL>RV}Oy{MZ`5>zAY?Ur#~^VXaNru!gHwwyZlRkPiag6L!hGRca}ELBftzzh`g6GkTV zkI{qh5=&(s_B*1D7M=(vGaL(mSVWMzZUUkdeZy`F(OjH-Xj(QWIf_4uWzIc_t$Bs(qH7GpFwg3Eben4e;%1mzcu#)*cSGw4ROM!z_OaUe8;BdP)MdPbmlmt`eg2+K(R#-M z7JYyd5IL@SL>}cTH|igYdsRMo(_s>Z4%&)mM>^(vnJEjH(&e_gk%m+40krgQr}fvO z^5!YQra6A;nmC$ZuM?|trw^J0BT+F{J*n@>m&qJ&7>V@fDqNsi*T5JhTFpauiOQ($ zSE{L)Sj{Z7#3=euJ~A(t`?Ke#N+;syLT)kfT*HgB=mUECiF+33rRtcqN>6>Rf|qE1 z2~6G8TRmgrz$8P|buwbBpJ>s40zBz~QZDa!XOLxP-T#h2^XSq~p^f#@Aqpsn&b;z( zk|C2BP}KSss=@3rCV;yQNi-m1Nj0p9A)cthj{$%I*dyAqf=ZIN<3IZPA2AJhH5kqP znze|Ne>;_~10iOlY9LJ(Awo|l_9b@_&2f5T;?4=bjytHey9Uha{o@}N2FA{IinOsM zKCcGKN-a}SM18v=9lRtL2nSI9G^`ELD&QEN+6HT!?vRI^9RgWCrexjy&Gmrn(kI8k zbvGnFyc)=(U0YkGVGYUt7+sRZ+N!cqzpnLM$0W$mK)X&QX6n(m0|qu?KH5Mr>34~* z&7?fYSb;P~FHGV|l!iKOKpN+!v{U8@J?4AB(W?^fHGlMM;I<`-3vd6tm~2J>eFg`g zzA1;mMcLQ@J&^yY&JJ#hVp|4L0@qMAY9@mS)#7@abzz|0v}ib90Ddq&TI>T(rAZ3% zo7ztV7N>1)r$;nL5qS%)C_RNvAY?g5`@jpM*Bb7z%@cJ<>i*t$Ku)juwFiz5o>Od? zbB}&!+qu8se%e2c?tLdNu$5JSdbA@G$8A5D<^#uy4YZBZZsQKd4m0q=AlP+_4`ekj z&Cl|sNwTX4BDsCLY{_uH)u`1&>hfRMU7xvgvzr4S-s6P}T=Q*KHnEu@_<<2cWjk7z7Q2F^zJ1^64x?HWfyOth|+2(BL(}vt-Oy1jmD|?DO z=v;wqfOmZIx>K-J-!f3@Z?LR^<9F`i@c*i5cJvJ~KEco`&`MU8br(n1swt&7H#g87|x!cXo@0jr3Q3`gscY5Bvks?sSyP>|{ znKY*}&pHJ%=aAR`k?bXliUcmR# zxe<~3XEvCyzMzvY5P4W5jhb=B^3A@+2rzjt{Oxvsg)8 zmECx#@U3Gm*4XGizI6R__uyvCt(<-D2Yn&6GWi^j>`FDw#{-BYYC@1dtI;@Ce-&f2 zt(L6WMkwK*3C%%4<`r5yGe2JY{ zUcO^un$NGFN+;lUu5O#kH8)6LRRtJB5`vaUUAAy~JxiT`a7u-D#ZIx&9~`)1G%G{_ zuqp?;Edo?J%le3g~6T@7{a&3&o@ngo!jGh-e`a0138Q0 zx8_>cEksVYW(`Iz0%~Oj7o^@&;^rCCNW-$+Bw{wkjnfwaiQ>ta?Whlm?43DBzr`BV zjfXJBi#95gv^oL-B#(I90%JM~M}o<+eVE1I zgQ7`@%*x~Tq!y0w@XocRDNS}O8D@IJnm)ZV)?w;1{eo}}aDTz`#0%-bEYxoQwu<{c zVMJYr2cs1Sb+zR8i)vzl5YINT$Fe)kZr)OKSw5VIF06*EUKq|#Rh}y!M{vZ*kRGou zslevr_+@&CuB`eoRJHtk=(7!mTS&kDVy`9lBKE)W_(PGlTLOsWDQ$*JX3%O}K0`C7 z234ifSz8=0K+#>f&pe==F91f5ZX&t>PnDaMAeyBoi9#|5tpyDZrgWidY=U()PngV- z!&Q9N?X#}C;IHG~3CzIfh^`4qi260TL{7VUL)`d9I`Xja>s>bw-}4X)1oen*l4ws_ z0pxXoJh!XQXkiMUWnq)ylXyzxPSVYJ#k~|JzemUbV4XZ@Aj?no3MO!WyD;HQt!qh8 znEZJKurO_BS+M;+L9z{aQZ8gbJo~!T?{d5+r~ttLcobA^e!Mewb`0?W=V6e9{o=4u z#`;chG$ZJhu`wJWbva#C?O^GYqL~CMvbk3c%#%Yz3*#vDq!Atx)6QlnPS;aYmiT9| zm|D1IAQxyyz4}z%ZcR&|U+r+NWIkT}3;$jWW*4TdQKXc)bhOz3tY-X(qU9bw4Zn;YztOvSe>^(rdcTdiNvGP71BK{^k<-5SL`JB!JU^d`26Eg{m5rCcw zAtFe*>>9_QDONk;X;1r{GQx{DK}{gbWLJ{_6Wu8h41)`6ukR$W%+6HT#_Iai;HjDF zu$l6X=leA*PXUVVvijPRQTl93WM)rUB8lj6ViH!5+&^$^lio$8*OzAt3el=nKKmfQ zN%Nb=Wd+VX+B0v6Am*b z8N;%Tl?xg5}&h*iy{AQ|NZ|<1eb8JSIe0;?;A^5({V1T(} z!ek6?R1+Jlp#8j6ncqa=LyaS&y|xz2_;r8D(kFypaAbYy+>I&V>2rFL+t-NIFEq9FG)`GY~i`fJ~I`d&=3TP%hR(yx&N<;@dgU5f!H(#$)A3o^l+)wDOFK=h8me^10b!7j(u>-Wlnc9M5p zlM)_0WwOZ4`2Wz=2-8nv2q_2yLKqZV(I`Ux;6Pb+;OFY}+woAOe+$-Sy0{t8gDAE- zZ8qtkj?0DoSAE#?hV&1=2aCOdhN+F3t0!$`Gm4h!a`cqUM=Yg#tq~u$}D4Xt|01 z&m0kBc&C8k*;(tz25tUif*o}T6Syip9GNP8SYnM4pgCfHjf6A`1~os<55%B4tOC`( z&XQ|vN7?b7``(4CKp{Uic2NK;+ZJ(_>NEiEfzleY&y9>57s~1(u863R8b5;w`EmPU z*2Jc$d}ouZnaPd7uxGLIdvoAxSvfV

!Gh!VC-9WT5$V(9D%ZDfIv0I|uqSVdml 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 z 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%0A&`H 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� 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 -- GitLab From b7b564ae3c4d05e0191cf3c811d35e7364118c80 Mon Sep 17 00:00:00 2001 From: Carlos Delgado Date: Thu, 20 Oct 2022 20:05:19 +0200 Subject: [PATCH 139/140] fixed significance --- R/AbsBiasSS.R | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/R/AbsBiasSS.R b/R/AbsBiasSS.R index 0838f25..0ceb009 100644 --- a/R/AbsBiasSS.R +++ b/R/AbsBiasSS.R @@ -257,15 +257,16 @@ AbsBiasSS <- function(exp, obs, ref = NULL, time_dim = 'sdate', memb_dim = NULL, obs_data <- obs_data[good_values] } } + ## Bias of the exp - bias_exp <- .Bias(exp = exp_data, obs = obs_data, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + bias_exp <- .Bias(exp = exp_data, obs = obs_data, na.rm = na.rm, absolute = TRUE, time_mean = FALSE) ## Bias of the ref if (is.null(ref)) { ## Climatological forecast - ref_data <- mean(obs_data, na.rm = na.rm) + ref_data <- rep(mean(obs_data, na.rm = na.rm), length(obs_data)) } - bias_ref <- .Bias(exp = ref_data, obs = obs_data, na.rm = na.rm, absolute = TRUE, time_mean = TRUE) + bias_ref <- .Bias(exp = ref_data, obs = obs_data, na.rm = na.rm, absolute = TRUE, time_mean = FALSE) ## Skill score and significance - biasSS[i, j] <- 1 - bias_exp / bias_ref + biasSS[i, j] <- 1 - mean(bias_exp) / mean(bias_ref) sign[i, j] <- .RandomWalkTest(skill_A = bias_exp, skill_B = bias_ref)$signif } } -- GitLab From da686a783710cb0617e39bd099d658865cef19f0 Mon Sep 17 00:00:00 2001 From: aho Date: Fri, 21 Oct 2022 09:50:12 +0200 Subject: [PATCH 140/140] Add check for sign --- tests/testthat/test-AbsBiasSS.R | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/testthat/test-AbsBiasSS.R b/tests/testthat/test-AbsBiasSS.R index b2e70f3..08a4a83 100644 --- a/tests/testthat/test-AbsBiasSS.R +++ b/tests/testthat/test-AbsBiasSS.R @@ -170,6 +170,11 @@ test_that("2. Output checks: dat1", { c(FALSE, FALSE), tolerance = 0.0001 ) + expect_equal( + as.vector(AbsBiasSS(exp1, exp1)$sign), + c(TRUE, TRUE), + tolerance = 0.0001 + ) }) -- GitLab