From 977a39984ba4208ff7b727bd9d4d5fd056fd41d1 Mon Sep 17 00:00:00 2001 From: Eva Rifa Date: Mon, 20 Feb 2023 17:36:19 +0100 Subject: [PATCH 1/2] Change londim and latdim to names in SelBox, create unit test and modify WeightedMean due to this change --- R/SelBox.R | 159 ++++++++++++++--------- R/WeightedMean.R | 8 +- man/SelBox.Rd | 17 ++- tests/testthat/test-Selbox.R | 195 +++++++++++++++++++++++++++++ tests/testthat/test-WeightedMean.R | 6 +- 5 files changed, 306 insertions(+), 79 deletions(-) create mode 100644 tests/testthat/test-Selbox.R diff --git a/R/SelBox.R b/R/SelBox.R index bab99bd..37c90c0 100644 --- a/R/SelBox.R +++ b/R/SelBox.R @@ -10,12 +10,10 @@ #' grid of \code{data}'. #'@param region A vector of length four indicating the minimum longitude, the #' maximum longitude, the minimum latitude and the maximum latitude. -#'@param londim An integer number indicating the position of the longitude -#' dimension in the \code{data} object. If NULL (by deafault), the function -#' search for a dimension call 'lon' in the \code{data} input. -#'@param latdim An integer number indicating the position of the latitude -#' dimension in the \code{data} object. If NULL (by default), the function -#' searches for a dimension call 'lat' in the \code{data} input. +#'@param londim A character string indicating the name of the longitudinal +#' dimension. The default value is 'lon'. +#'@param latdim A character string indicating the name of the latitudinal +#' dimension. The default value is 'lat'. #'@param mask A matrix with the same spatial dimensions of \code{data}. #' #'@return A list of length 4: @@ -32,88 +30,129 @@ #' a NULL element is returned.}} #' #'@examples -#'## Example with synthetic data: +#'# Example with synthetic data: #'data <- 1:(20 * 3 * 2 * 4) #'dim(data) <- c(lon = 20, lat = 3, time = 2, model = 4) #'lon <- seq(2, 40, 2) #'lat <- c(1, 5, 10) #' #'a <- SelBox(data = data, lon = lon, lat = lat, region = c(2, 20, 1, 5), -#' londim = 1, latdim = 2, mask = NULL) -#'str(a) +#' londim = "lon", latdim = "lat", mask = NULL) #'@export -SelBox <- function(data, lon, lat, region, londim = NULL, latdim = NULL, mask = NULL) { +SelBox <- function(data, lon, lat, region, londim = 'lon', latdim = 'lat', + mask = NULL) { + # Check inputs + # data + if (is.null(data)) { + stop("Parameter 'data' cannot be NULL.") + } + if (!is.array(data) && !is.matrix(data)) { + stop("Parameter 'data' must be a numeric array or matrix.") + } + dim_names <- names(dim(data)) + if (is.null(dim_names)) { + stop("Parameter 'data' must have dimension names.") + } - if (is.null(data) | is.null(lon) | is.null(lat) | is.null(region)) { - stop("Parameters 'data', 'lon', 'lat' or 'region' cannot be NULL.") + # lon, lat + if (is.null(lon) | is.null(lat)) { + stop("Parameters 'lon' and 'lat' cannot be NULL.") } - if (!is.numeric(lon) | !is.numeric(lat) | !is.numeric(region)) { - stop("Parameters 'lon', 'lat' or 'region' must be numeric.") + if (!is.numeric(lon) | !is.numeric(lat)) { + stop("Parameters 'lon' and 'lat' must be numeric.") } - if (!is.array(data) && !is.matrix(data)) { - stop("Parameter 'data' must be an array or matrix.") + if (!is.null(dim(lon)) | !is.null(dim(lat))) { + if (length(dim(lon)) == 1 & length(dim(lat)) == 1) { + lon <- as.vector(lon) + lat <- as.vector(lat) + } else { + stop("Parameters 'lon' and 'lat' need to be a vector.") + } + } + # region + if (is.null(region)) { + stop("Parameter 'region' cannot be NULL.") } - if (!is.null(dim(lat)) | !is.null(dim(lon))) { - stop("Parameter 'lon' and lat' need to be a vector.") + if (!is.numeric(region)) { + stop("Parameter 'region' must be numeric.") } if (length(region) != 4) { stop("The region argument has to be a vector of length four indicating the ", - "minimum longitude, the maximum longitude, the minimum latitude and the maximum latitude.") - } - dims <- 1:length(dim(data)) - if (is.null(londim)) { - if ("lon" %in% names(dim(data))) { - londim <- which(names(dim(data)) == "lon") - } else if (length(lon) %in% dim(data)) { - londim <- which(dim(data) == length(lon)) - if (length(londim) > 1) { - stop("More than one dimension of the parameter 'data' has the same length as ", - "'lon' parameter.") - } - } else { - stop("Non of the dimensions of the parameter 'data' are of the same length as 'lon'.") - } - } - if (is.null(latdim)) { - if ("lat" %in% names(dim(data))) { - latdim <- which(names(dim(data)) == "lat") - } else if (length(lat) %in% dim(data)) { - latdim <- which(dim(data) == length(lat)) - if (length(latdim) > 1) { - stop("More than one dimension of the parameter 'data' has the same length as 'lat' parameter.") - } - } else { - stop("Non of the dimensions of the parameter 'data' are of the same length as 'lat' parameter.") - } + "minimum longitude, the maximum longitude, the minimum latitude and ", + "the maximum latitude.") } - if (londim == latdim) { - stop("Parameter 'londim' and 'latdim' cannot be equal.") + # londim + if (is.numeric(londim)) { + warning("Numeric 'londim' is deprecated, use dimension names instead. The ", + "corresponding dimension name will be assigned.") + londim <- dim_names[londim] } - if (dim(data)[londim] != length(lon)){ - stop("The longitudinal dimension of parameter 'data' must be of the same length of parameter 'lon'.") + if (!is.character(londim)) { + stop("Parameter 'londim' must be a character string.") } - if (dim(data)[latdim] != length(lat)){ - stop("The latitudinal dimension of parameter 'data' must be of the same length of parameter 'lat'.") + if (length(londim) > 1) { + warning("Parameter 'londim' must be of length 1. Only the first value ", + "will be used.") + londim <- londim[1] } + if (!londim %in% names(dim(data))) { + stop("Parameter 'londim' is not found in 'data'.") + } + if (dim(data)[londim] != length(lon)) { + stop(paste0("The longitudinal dimension of parameter 'data' must be of the ", + "same length as parameter 'lon'.")) + } + # latdim + if (is.numeric(latdim)) { + warning("Numeric 'latdim' is deprecated, use dimension names instead. The ", + "corresponding dimension name will be assigned.") + latdim <- dim_names[latdim] + } + if (!is.character(latdim)) { + stop("Parameter 'latdim' must be a character string.") + } + if (length(latdim) > 1) { + warning("Parameter 'latdim' must be of length 1. Only the first value ", + "will be used.") + latdim <- latdim[1] + } + if (!latdim %in% names(dim(data))) { + stop("Parameter 'latdim' is not found in 'data'.") + } + if (dim(data)[latdim] != length(lat)) { + stop(paste0("The latitudinal dimension of parameter 'data' must be of the ", + "same length as parameter 'lat'.")) + } + # mask + if (!is.null(mask)) { + if (!all(dim(data)[which(names(dim(data)) %in% c(londim, latdim))] %in% + dim(mask)[which(names(dim(mask)) %in% c(londim, latdim))])) { + stop("Parameter 'mask' must have the same spatial dimensions of data.") + } + lon_pos <- which(names(dim(mask)) == londim) + lat_pos <- which(names(dim(mask)) == latdim) + } + if (region[3] <= region[4]) { - LatIdx <- which( lat >= region[3] & lat <= region[4]) + LatIdx <- which(lat >= region[3] & lat <= region[4]) } else { LatIdx <- which(lat <= region[3] | lat >= region[4]) } - #if (region[1] <= region[2]) { - # LonIdx <- which(lon >= region[1] & lon <= region[2]) - #} else { - # LonIdx <- which(lon >= region[1] | lon <= region[2]) - #} + # if (region[1] <= region[2]) { + # LonIdx <- which(lon >= region[1] & lon <= region[2]) + # } else { + # LonIdx <- which(lon >= region[1] | lon <= region[2]) + # } LonIdx <- Lon2Index(lon, lonmin = region[1], lonmax = region[2]) data <- Subset(data, along = londim, indices = LonIdx, drop = "none") data <- Subset(data, along = latdim, indices = LatIdx, drop = "none") + if (!is.null(mask)) { - mask <- Subset(mask, along = latdim - length(dim(data)) + 2, indices = LatIdx, drop = "none") - mask <- Subset(mask, along = londim - length(dim(data)) + 2, indices = LonIdx, drop = "none") + mask <- Subset(mask, along = lat_pos, indices = LatIdx, drop = "none") + mask <- Subset(mask, along = lon_pos, indices = LonIdx, drop = "none") } else { mask <- NULL } - list(data = data, lon = lon[LonIdx], lat = lat[LatIdx], mask = mask) + return(list(data = data, lon = lon[LonIdx], lat = lat[LatIdx], mask = mask)) } diff --git a/R/WeightedMean.R b/R/WeightedMean.R index ffcd273..0932599 100644 --- a/R/WeightedMean.R +++ b/R/WeightedMean.R @@ -133,8 +133,6 @@ WeightedMean <- function(data, lon, lat, region = NULL, mask = NULL, stop(paste0("The latitudinal dimension of parameter 'data' must be of the ", "same length as parameter 'lat'.")) } - lon_pos <- which(dim_names == londim) - lat_pos <- which(dim_names == latdim) # region if (!is.null(region)) { if (length(region) != 4) { @@ -163,11 +161,9 @@ WeightedMean <- function(data, lon, lat, region = NULL, mask = NULL, } } - nlon <- length(lon) - nlat <- length(lat) if (!is.null(region)) { - aux <- SelBox(data, lon = lon, lat = lat, region = region, londim = lon_pos, - latdim = lat_pos, mask = mask) + aux <- SelBox(data, lon = lon, lat = lat, region = region, londim = londim, + latdim = latdim, mask = mask) data <- aux$data lon <- aux$lon lat <- aux$lat diff --git a/man/SelBox.Rd b/man/SelBox.Rd index fa85629..1bc496b 100644 --- a/man/SelBox.Rd +++ b/man/SelBox.Rd @@ -4,7 +4,7 @@ \alias{SelBox} \title{Select spatial region from multidimensional arrays} \usage{ -SelBox(data, lon, lat, region, londim = NULL, latdim = NULL, mask = NULL) +SelBox(data, lon, lat, region, londim = "lon", latdim = "lat", mask = NULL) } \arguments{ \item{data}{An array with minimum two dimensions of latitude and longitude.} @@ -18,13 +18,11 @@ grid of \code{data}'.} \item{region}{A vector of length four indicating the minimum longitude, the maximum longitude, the minimum latitude and the maximum latitude.} -\item{londim}{An integer number indicating the position of the longitude -dimension in the \code{data} object. If NULL (by deafault), the function -search for a dimension call 'lon' in the \code{data} input.} +\item{londim}{A character string indicating the name of the longitudinal +dimension. The default value is 'lon'.} -\item{latdim}{An integer number indicating the position of the latitude -dimension in the \code{data} object. If NULL (by default), the function -searches for a dimension call 'lat' in the \code{data} input.} +\item{latdim}{A character string indicating the name of the latitudinal +dimension. The default value is 'lat'.} \item{mask}{A matrix with the same spatial dimensions of \code{data}.} } @@ -47,13 +45,12 @@ Subset a spatial region from spatial data giving a vector with the maximum and minimum of latitudes and longitudes of the selected region. } \examples{ -## Example with synthetic data: +# Example with synthetic data: data <- 1:(20 * 3 * 2 * 4) dim(data) <- c(lon = 20, lat = 3, time = 2, model = 4) lon <- seq(2, 40, 2) lat <- c(1, 5, 10) a <- SelBox(data = data, lon = lon, lat = lat, region = c(2, 20, 1, 5), - londim = 1, latdim = 2, mask = NULL) -str(a) + londim = "lon", latdim = "lat", mask = NULL) } diff --git a/tests/testthat/test-Selbox.R b/tests/testthat/test-Selbox.R new file mode 100644 index 0000000..adb68cd --- /dev/null +++ b/tests/testthat/test-Selbox.R @@ -0,0 +1,195 @@ +context("ClimProjDiags::SelBox tests") + +############################################## + +# dat1 +dat1 <- 1:(2 * 3 * 2 * 4) +dim(dat1) <- c(lon = 2, lat = 3, time = 2, model = 4) +lon <- seq(2, 40, 20) +lat <- c(1, 5, 10) +region <- c(1, 2, 1, 1) +mask <- array(c(0, 1, 0, 1, 0, 1), dim = c(lon = 2, lat = 3)) + +# dat2 +dat2 <- 1:(2 * 3 * 2 * 4) +dim(dat2) <- c(lons = 2, lats = 3, time = 2, model = 4) +mask2 <- array(c(0, 1, 0, 1, 0, 1), dim = c(lons = 2, lats = 3)) + +# dat3 +dat3 <- 1:(20 * 3) +dim(dat3) <- c(longitude = 20, latitude = 3) +lon3 <- seq(2, 40, 2) +lat3 <- c(1, 5, 10) +region3 <- c(2, 20, 1, 5) +mask3 <- array(c(0, 1, 0, 1, 0, 1), dim = c(latitude = 3, longitude = 20)) + +############################################## + +test_that("1. Input checks", { + # data + expect_error( + SelBox(c(), lon = 1:10, lat = 1:10), + "Parameter 'data' cannot be NULL." + ) + expect_error( + SelBox(1:10, lon = 1:10, lat = 1:10), + "Parameter 'data' must be a numeric array." + ) + expect_error( + SelBox(array(1:10), lon = 1:10, lat = 1:10), + "Parameter 'data' must have dimension names." + ) + # lon, lat + expect_error( + SelBox(array(1:10, dim = c(lon = 10)), lon = NULL, lat = 1:10), + "Parameters 'lon' and 'lat' cannot be NULL." + ) + expect_error( + SelBox(array(1:10, dim = c(lon = 10)), lon = 'a', lat = 1:10), + "Parameters 'lon' and 'lat' must be numeric." + ) + expect_error( + SelBox(array(1:10, dim = c(lon = 10)), + lon = 1:10, lat = array(1:10, dim = c(lon = 10, lat = 1))), + "Parameters 'lon' and 'lat' need to be a vector." + ) + # region + expect_error( + SelBox(dat1, lat = lat, lon = lon, region = NULL), + "Parameter 'region' cannot be NULL." + ) + expect_error( + SelBox(dat1, lat = lat, lon = lon, region = 'a'), + "Parameter 'region' must be numeric." + ) + expect_error( + SelBox(dat1, lat = lat, lon = lon, region = 1:3), + paste0("The region argument has to be a vector of length four indicating the ", + "minimum longitude, the maximum longitude, the minimum latitude and ", + "the maximum latitude.") + ) + # londim + expect_warning( + SelBox(array(1:10, dim = c(lon = 10, lat = 1)), region = region, + lon = 1:10, lat = 1, londim = 1, latdim = 2), + "Numeric 'londim' is deprecated, use dimension names instead. The ", + "corresponding dimension name will be assigned." + ) + expect_error( + SelBox(array(1:10, dim = c(lon = 10, lat = 1)), region = region, + lon = 1:10, lat = 1, londim = list(1,2), latdim = 2), + "Parameter 'londim' must be a character string." + ) + expect_warning( + SelBox(array(1:10, dim = c(lon = 10, lat = 1)), region = region, + lon = 1:10, lat = 1, londim = c('lon', 'lat'), latdim = 'lat'), + "Parameter 'londim' must be of length 1. Only the first value ", + "will be used." + ) + expect_error( + SelBox(array(1:10, dim = c(lons = 10, lat = 1)), region = region, + lon = 1:10, lat = 1, londim = 'lon', latdim = 'lat'), + "Parameter 'londim' is not found in 'data'." + ) + expect_error( + SelBox(array(1:10, dim = c(lon = 10, lat = 1)), region = region, + lon = 1:11, lat = 1, londim = 'lon', latdim = 'lat'), + paste0("The longitudinal dimension of parameter 'data' must be of the ", + "same length as parameter 'lon'.") + ) + # latdim + expect_warning( + SelBox(array(1:10, dim = c(lon = 10, lat = 1)), region = region, + lon = 1:10, lat = 1, latdim = 2), + "Numeric 'latdim' is deprecated, use dimension names instead. The ", + "corresponding dimension name will be assigned." + ) + expect_error( + SelBox(array(1:10, dim = c(lon = 10, lat = 1)), region = region, + lon = 1:10, lat = 1, latdim = list(1,2)), + "Parameter 'latdim' must be a character string." + ) + expect_warning( + SelBox(array(1:10, dim = c(lon = 10, lat = 1)), region = region, + lon = 1:10, lat = 1, latdim = c('lat', 'lon')), + "Parameter 'latdim' must be of length 1. Only the first value ", + "will be used." + ) + expect_error( + SelBox(array(1:10, dim = c(lon = 10, lats = 1)), region = region, + lon = 1:10, lat = 1, londim = 'lon', latdim = 'lat'), + "Parameter 'latdim' is not found in 'data'." + ) + expect_error( + SelBox(array(1:10, dim = c(lon = 10, lat = 1)), region = region, + lon = 1:10, lat = 1:2, londim = 'lon', latdim = 'lat'), + paste0("The latitudinal dimension of parameter 'data' must be of the ", + "same length as parameter 'lat'.") + ) + # mask + expect_error( + SelBox(dat1, lat = lat, lon = lon, region = region, mask = 1:3), + "Parameter 'mask' must have the same spatial dimensions of data." + ) + expect_error( + SelBox(dat1, lat = lat, lon = lon, region = region, mask = mask2), + "Parameter 'mask' must have the same spatial dimensions of data." + ) +}) + +############################################## + +test_that("2. Output checks", { + # dat2 + expect_equal( + names(SelBox(dat2, lat = lat, lon = lon, region = region, + londim = 'lons', latdim = 'lats', mask = mask2)), + c('data', 'lon', 'lat', 'mask') + ) + expect_equal( + dim(SelBox(dat2, lat = lat, lon = lon, region = region, + londim = 'lons', latdim = 'lats')$data), + c(lons = 1, lats = 1, time = 2, model = 4) + ) + expect_equal( + dim(SelBox(dat2, lat = lat, lon = lon, region = region, + londim = 'lons', latdim = 'lats', mask = mask2)$mask), + c(lons = 1, lats = 1) + ) + expect_equal( + as.vector(SelBox(dat2, lat = lat, lon = lon, region = region, + londim = 'lons', latdim = 'lats', mask = mask2)$data), + c(1, 7, 13, 19, 25, 31, 37, 43) + ) + expect_equal( + SelBox(dat2, lat = lat, lon = lon, region = region, + londim = 'lons', latdim = 'lats', mask = mask2)$lat, + 1 + ) + # dat3 + expect_equal( + dim(SelBox(dat3, lat = lat3, lon = lon3, region = region3, + londim = 'longitude', latdim = 'latitude')$data), + c(longitude = 10, latitude = 2) + ) + expect_equal( + dim(SelBox(dat3, lat = lat3, lon = lon3, region = region3, + londim = 'longitude', latdim = 'latitude', mask = mask3)$mask), + c(latitude = 2, longitude = 10) + ) + expect_equal( + SelBox(dat3, lat = lat3, lon = lon3, region = region3, + londim = 'longitude', latdim = 'latitude', mask = mask3)$data, + array(c(1:10, 21:30), dim = c(longitude = 10, latitude = 2)) + ) + expect_equal( + SelBox(dat3, lat = lat3, lon = lon3, region = region3, + londim = 'longitude', latdim = 'latitude', mask = mask3)$lon, + seq(2, 20, 2) + ) + expect_equal( + SelBox(dat3, lat = lat3, lon = lon3, region = region3, + londim = 'longitude', latdim = 'latitude', mask = mask3)$lat, + c(1, 5) + ) +}) diff --git a/tests/testthat/test-WeightedMean.R b/tests/testthat/test-WeightedMean.R index 24144db..3edfcae 100644 --- a/tests/testthat/test-WeightedMean.R +++ b/tests/testthat/test-WeightedMean.R @@ -32,7 +32,7 @@ dat5[1,1,1,1] <- NA ############################################## test_that("1. Input checks", { - # data, lon, lat + # data expect_error( WeightedMean(c(), lon = 1:10, lat = 1:10), "Parameter 'data' cannot be NULL." @@ -203,7 +203,7 @@ test_that("5. Output test: dat4", { expect_equal( as.vector(WeightedMean(dat4, lat = lat4, lon = lon4, mask = mask, region = region)), - c( 0.03610427, 0.02197599), + c(-0.02119318, 0.01728634), tolerance = 0.0001 ) }) @@ -218,7 +218,7 @@ test_that("6. Output test: dat4 (mask, region, ncores)", { expect_equal( as.vector(WeightedMean(dat4, lat = lat4, lon = lon4, mask = mask, region = region, ncores = 1)), - c( 0.03610427, 0.02197599), + c(-0.02119318, 0.01728634), tolerance = 0.0001 ) }) -- GitLab From cfe7a318edce28e155e8a8e860b6ee13bc03b40b Mon Sep 17 00:00:00 2001 From: aho Date: Mon, 27 Feb 2023 13:25:22 +0100 Subject: [PATCH 2/2] Add unit test for region having lon range large to small; Improve Subset() usage --- R/SelBox.R | 8 ++------ tests/testthat/test-Selbox.R | 6 ++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/R/SelBox.R b/R/SelBox.R index 37c90c0..d743aef 100644 --- a/R/SelBox.R +++ b/R/SelBox.R @@ -129,8 +129,6 @@ SelBox <- function(data, lon, lat, region, londim = 'lon', latdim = 'lat', dim(mask)[which(names(dim(mask)) %in% c(londim, latdim))])) { stop("Parameter 'mask' must have the same spatial dimensions of data.") } - lon_pos <- which(names(dim(mask)) == londim) - lat_pos <- which(names(dim(mask)) == latdim) } if (region[3] <= region[4]) { @@ -145,12 +143,10 @@ SelBox <- function(data, lon, lat, region, londim = 'lon', latdim = 'lat', # } LonIdx <- Lon2Index(lon, lonmin = region[1], lonmax = region[2]) - data <- Subset(data, along = londim, indices = LonIdx, drop = "none") - data <- Subset(data, along = latdim, indices = LatIdx, drop = "none") + data <- Subset(data, along = c(londim, latdim), indices = list(LonIdx, LatIdx), drop = "none") if (!is.null(mask)) { - mask <- Subset(mask, along = lat_pos, indices = LatIdx, drop = "none") - mask <- Subset(mask, along = lon_pos, indices = LonIdx, drop = "none") + mask <- Subset(mask, along = c(londim, latdim), indices = list(LonIdx, LatIdx), drop = "none") } else { mask <- NULL } diff --git a/tests/testthat/test-Selbox.R b/tests/testthat/test-Selbox.R index adb68cd..fce7f06 100644 --- a/tests/testthat/test-Selbox.R +++ b/tests/testthat/test-Selbox.R @@ -21,6 +21,7 @@ dim(dat3) <- c(longitude = 20, latitude = 3) lon3 <- seq(2, 40, 2) lat3 <- c(1, 5, 10) region3 <- c(2, 20, 1, 5) +region3_2 <- c(15, 5, 1, 2) mask3 <- array(c(0, 1, 0, 1, 0, 1), dim = c(latitude = 3, longitude = 20)) ############################################## @@ -192,4 +193,9 @@ test_that("2. Output checks", { londim = 'longitude', latdim = 'latitude', mask = mask3)$lat, c(1, 5) ) + expect_equal( + SelBox(dat3, lat = lat3, lon = lon3, region = region3_2, + londim = 'longitude', latdim = 'latitude', mask = mask3)$lon, + c(seq(16, 40, 2), 2, 4) + ) }) -- GitLab