Skip to content
Open
Show file tree
Hide file tree
Changes from 75 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
ce18316
Add date factor for meteorological seasons
QSparks Oct 12, 2023
094eff5
Clean up comments
QSparks Oct 12, 2023
3b7fabc
Add seasonal frequency to applicable indices and docs.
QSparks Oct 19, 2023
1515a0c
Add tests for seasonal date factor
QSparks Oct 19, 2023
25a454b
Add rx5day and rx1day to tests
QSparks Oct 20, 2023
439fe42
Add NA tests for all clim vars & month-season case
QSparks Oct 24, 2023
38d9b17
Document season definitions, increment version.
QSparks Nov 1, 2023
e51ae3f
Undo updates to changelog and description.
QSparks Nov 4, 2023
0df8567
Break seasonal indices tests up and correct comments.
QSparks Nov 6, 2023
dce1e7b
Remove redundant quantile validity check
QSparks Nov 6, 2023
7c4de3b
Refactor seasonal tests and add related list constants
QSparks Nov 6, 2023
2b4b5e3
Add R-CMD-Check workflow
QSparks Nov 6, 2023
99362de
Address i29 and R CMD Check warning
QSparks Nov 6, 2023
8ed9fc1
Run roxygenize before R CMD Check
QSparks Nov 6, 2023
61729d3
Fix indentation in .yaml
QSparks Nov 7, 2023
5331c67
Build docs in R-CMD-Check job
QSparks Nov 7, 2023
445ab55
Add lifecycle to extra-packages
QSparks Nov 7, 2023
cbd9cfa
Run cross-platform checks on PR only
QSparks Nov 9, 2023
e7763fe
Merge branch 'CI-workflow' into feature/output-extreme-event-timing
QSparks Nov 20, 2023
fc0a566
Introduce 'as.df' parameter to return exact dates
QSparks Nov 24, 2023
c041a5e
Add exact date tests for n or x, rxnday and spells
QSparks Nov 26, 2023
3fa3744
Add exact dates tests for GSL
QSparks Nov 26, 2023
bf64d0e
Update expected.GSL for southern hemisphere and leap years
QSparks Nov 28, 2023
d5b10e3
Directly access non-exported climdex.pcic functions
QSparks Nov 28, 2023
e276175
Add 'next' call in rxnday tests when expected.value is NA
QSparks Nov 28, 2023
5ac63d3
Resolve 'cannot coerce class "PCICt" to a data.frame'
QSparks Nov 29, 2023
770e767
Add NA checks for values in n or x tests
QSparks Nov 29, 2023
34bf591
Clarify ‘include.exact.dates’ param, fix seasonal na mask
QSparks Dec 5, 2023
43d5fb2
End-of-year tests, rename as.df to include.exact.dates
QSparks Dec 5, 2023
f405ef5
Use checkIdentical in place of checkEqualsNumeric
QSparks Dec 5, 2023
e0cd347
Adjust spell and GSL df structure
QSparks Dec 6, 2023
157d25c
Add check lengths and test dates in factors
QSparks Dec 6, 2023
46b291b
Set non-ending GSL to end at EOY, use checkTrue for old-rel
QSparks Dec 7, 2023
9996b12
Add test center.mean.on…, not.all.na & equal length
QSparks Dec 7, 2023
c1c4e96
Custom checkEquals, fix expected s.h. GSL duration
QSparks Dec 8, 2023
ac76bf6
Use '==' in place of '%in%' to index expected n or x dates
QSparks Dec 8, 2023
af8cd59
Test n or x in winter season
QSparks Dec 8, 2023
57fd447
Add exact dates test for spells.can.span.years
QSparks Dec 11, 2023
8bb8cf6
Add leap year spell & random data tests. Use period.sep
QSparks Dec 12, 2023
1fd7b77
Add tavg namasks, comments and improve NA seasons test
QSparks Dec 13, 2023
60ad825
Consistent nday.consec.prec.max. arg order
QSparks Dec 14, 2023
988e763
Check consistent types with and without exact dates
QSparks Dec 14, 2023
95de2d5
Use idx lists from constants to set test indices
QSparks Jan 19, 2024
273122c
Update CHANGELOG and increment version for 1.2-0 release
QSparks Jan 19, 2024
6dee23e
Update CHANGELOG
QSparks Jan 22, 2024
4441b95
Clarify 1.2-0 release notes
QSparks Jan 22, 2024
0ef6817
Clarify 1.2-0 Exact Dates notes
QSparks Jan 22, 2024
1527014
Refactor climdex.r into modular R scripts
QSparks Sep 17, 2024
67ded79
Update R and Rcpp dependencies
QSparks Sep 17, 2024
b7db139
Revert dependency updates, clarify x86 check in tests
QSparks Sep 18, 2024
56debd8
rm debug code
QSparks Sep 18, 2024
c4e7921
Test Intel macOS runner
QSparks Sep 18, 2024
7a6b455
Remove test on macOS runner
QSparks Sep 18, 2024
3107e95
Add separate file for climdexInput validation
QSparks Sep 18, 2024
4a91cd6
WIP: Generic scalar and vector var classes
QSparks Sep 20, 2024
51a6089
Fix examples, 100-character lines and cross-references
QSparks Sep 23, 2024
7d0b0bd
Skip running examples, fix namask add generic stats
QSparks Sep 24, 2024
caa00b1
export gen stat helpers, add Roxygen docstrings
QSparks Sep 25, 2024
cbdb918
WIP: test generic stats
QSparks Sep 25, 2024
cc4a51a
WIP test generic vector stats
QSparks Sep 26, 2024
453c326
Add additional testing for vector stats
QSparks Oct 2, 2024
0c09443
Add additional testing for generics
QSparks Oct 2, 2024
101f188
Add tests for generic variable inputs
QSparks Oct 3, 2024
420db45
Add jdays to raw climdexGenericVector
QSparks Oct 4, 2024
79467f6
Merge branch 'master' into i38-generic-var-templates
QSparks Oct 10, 2024
49514aa
Clean up docs
QSparks Oct 11, 2024
7078bcb
Error message for improperly formatted max.missing.days
QSparks Oct 11, 2024
294925d
Add formulas to the details of the conversion functions
QSparks Oct 11, 2024
96860b0
Fix warnings when calculating circular stats on NA sets
QSparks Oct 11, 2024
6a44213
Use season levels instead of names for na mask test
QSparks Oct 11, 2024
362eb55
Add validation for calendar types and csv data cols
QSparks Oct 24, 2024
efa037d
Gen Vec. Use case-insensitive format, remove name param
QSparks Oct 24, 2024
cc8f5b4
Use date.factor levels as names for circular stats
QSparks Oct 24, 2024
1f23db8
Test vector raw-csv equality, bad calendar exception
QSparks Oct 24, 2024
18fd16f
Fix formatting
QSparks Oct 25, 2024
01cea17
Fix typo in doc, add default param for compute gen stat
QSparks Oct 28, 2024
0d6f547
Reorder roxygen comments to match docs
QSparks Oct 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Depends:
PCICt (>= 0.5-4)
Encoding: UTF-8
Imports:
circular,
methods,
Rcpp (>= 0.11.4),
stats,
Expand All @@ -31,7 +32,12 @@ RoxygenNote: 7.3.2
Collate:
'input_utils.R'
'climdexInput_class.R'
'GenericVariable_utils.R'
'climdexGenericVariable_class.R'
'climdexGenericScalar.R'
'climdexGenericVector.R'
'climdex.pcic-package.R'
'generic_stats.R'
'constants.R'
'date_utils.R'
'precipitation_indices.R'
Expand Down
21 changes: 18 additions & 3 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ export(climdex.cwd)
export(climdex.dtr)
export(climdex.fd)
export(climdex.get.available.indices)
export(climdex.min.max.idx.list)
export(climdex.mean.idx.list)
export(climdex.bootstrap.idx.list)
export(climdex.gsl)
export(climdex.id)
export(climdex.mean.idx.list)
Expand All @@ -35,8 +32,21 @@ export(climdex.tx90p)
export(climdex.txn)
export(climdex.txx)
export(climdex.wsdi)
export(climdexGenericScalar.csv)
export(climdexGenericScalar.raw)
export(climdexGenericVector.csv)
export(climdexGenericVector.raw)
export(climdexInput.csv)
export(climdexInput.raw)
export(compute.stat.scalar)
export(compute.stat.vector)
export(compute_circular_mean)
export(compute_circular_sd)
export(convert_cardinal_to_degrees)
export(convert_cartesian_to_polar)
export(convert_degrees_to_cardinal)
export(convert_polar_to_cartesian)
export(filter_by_direction_range)
export(get.last.monthday.of.year)
export(get.outofbase.quantiles)
export(get.series.lengths.at.ends)
Expand All @@ -49,10 +59,15 @@ export(simple.precipitation.intensity.index)
export(spell.length.max)
export(threshold.exceedance.duration.index)
export(total.precip.op.threshold)
exportClasses(climdexGenericScalar)
exportClasses(climdexGenericVector)
exportClasses(climdexInput)
import(PCICt)
import(Rcpp)
import(methods)
importFrom(circular,circular)
importFrom(circular,mean.circular)
importFrom(circular,sd.circular)
importFrom(stats,quantile)
importFrom(utils,head)
importFrom(utils,read.csv)
Expand Down
170 changes: 170 additions & 0 deletions R/GenericVariable_utils.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Utility function to validate arguments for scalar and vector data.
check.generic.argument.validity <- function( data, dates, max.missing.days, calendar) {

if (length(max.missing.days) != 3 || !all(c("annual", "monthly", "seasonal") %in% names(max.missing.days))) {
stop("max.missing.days must be a named vector with 'annual', 'monthly', and 'seasonal' elements.")
}


# Check that required arguments are provided
if (missing(data)) {
stop("Primary data argument is missing.")
}

if (missing(dates)) {
stop("Argument 'dates' is missing.")
}


if (!is.numeric(data)) {
stop("Primary Data must be numeric.")
}

if (length(data) != length(dates)) {
stop("Primary data and dates must have the same length.")
}

if(!is.null(dates) && !inherits(dates, "PCICt"))
stop(paste("Dates must be of class PCICt."))

# Calendar check: verify it matches one of the recognized types
valid_calendars <- c("360_day", "360", "365_day", "365", "noleap", "gregorian", "proleptic_gregorian")
if (!calendar %in% valid_calendars) {
stop(paste("Invalid calendar type:", calendar,
". Accepted types are '360_day', '360', '365_day', '365', 'noleap', 'gregorian', 'proleptic_gregorian'."))
}
}

# Utility function to handle date ranges and generate date factors.
date_info <- function(dates) {
cal <- attr(dates, "cal")

last.day.of.year <- get.last.monthday.of.year(dates)

date.range <- as.PCICt(paste(as.numeric(format(range(dates), "%Y", tz = "GMT")), c("01-01", last.day.of.year), sep = "-"), cal = cal)
date.series <- seq(date.range[1], date.range[2], by = "day")

jdays <- get.jdays.replaced.feb29(get.jdays(date.series))

season_with_year <- classify_meteorological_season_with_year(date.series)

date.factors <- list(
annual = factor(format(date.series, format = "%Y", tz = "GMT")),
monthly = factor(format(date.series, format = "%Y-%m", tz = "GMT")),
seasonal = factor(season_with_year, levels = unique(season_with_year))
)

return(list(
cal = cal,
date.series = date.series,
date.factors = date.factors,
jdays = jdays
))
}

# Generates NA masks based on filled data and date factors
generate_namasks <- function(filled.list, date.factors, max.missing.days) {
namasks <- list(
annual = lapply(filled.list, get.na.mask, date.factors$annual, max.missing.days["annual"]),
monthly = lapply(filled.list, get.na.mask, date.factors$monthly, max.missing.days["monthly"]),
seasonal = lapply(filled.list, get.na.mask, date.factors$seasonal, max.missing.days["seasonal"]))
# Vectors: Combine the masks for magnitude and direction
if ("primary" %in% names(filled.list) && "secondary" %in% names(filled.list)) {
# Synchronize annual masks
namasks$annual$primary <- namasks$annual$primary * namasks$annual$secondary
namasks$annual$secondary <- namasks$annual$primary

# Synchronize monthly masks
namasks$monthly$primary <- namasks$monthly$primary * namasks$monthly$secondary
namasks$monthly$secondary <- namasks$monthly$primary

# Synchronize seasonal masks
namasks$seasonal$primary <- namasks$seasonal$primary * namasks$seasonal$secondary
namasks$seasonal$secondary <- namasks$seasonal$primary
}
namasks$annual <- lapply(names(namasks$annual), function(v) {
d <- namasks$annual[[v]] * as.numeric(tapply(namasks$monthly[[v]], rep(seq_along(namasks$annual[[v]]), each = 12), prod))
dimnames(d) <- dim(d) <- NULL
d
})
names(namasks$annual) <- names(namasks$seasonal) <- names(namasks$monthly)


season_month_counts <- sapply(unique(date.factors$seasonal), function(season) {
length(unique(date.factors$monthly[date.factors$seasonal == season]))
})
data.vars <- names(filled.list)

for (var in data.vars) {
seasonal_namasks <- namasks$seasonal[[var]]
na_months <- unique(date.factors$monthly)[is.na(namasks$monthly[[var]])]
seasons_of_na_months <- unique(date.factors$seasonal[date.factors$monthly %in% na_months])
seasonal_namasks[unique(date.factors$seasonal) %in% seasons_of_na_months] <- NA
# Identify and set NA for seasons with less than 3 months
for (season in seq_along(season_month_counts) ) {
if (!is.na(season_month_counts[season]) && season_month_counts[season] < 3) {
seasonal_namasks[season] <- NA
}
}
namasks$seasonal[[var]] <- seasonal_namasks
}
return(namasks)
}

generate_filled_list <- function(data, dates, date.series) {
if (is.vector(data)) {
return(list(create.filled.series(data, trunc(dates), date.series)))
} else {
filled.list <- sapply(data, function(x) {
return(create.filled.series(x, trunc(dates), date.series))
}, simplify = FALSE)
return(filled.list)
}
}


# Reads data from a CSV file, validates it, and converts date columns to PCICt dates.
read_csv_data <- function(
file,
data.columns,
date.columns,
date.format,
na.strings,
calendar
) {

calling_func <- as.character(sys.call(-1)[[1]])

# Ensure that the number of data columns matches the type of the calling function
if (grepl("Scalar", calling_func, ignore.case = TRUE) && length(data.columns) != 1) {
stop("For scalar data, 'data.columns' should contain exactly 1 column.")
} else if (grepl("Vector", calling_func, ignore.case = TRUE) && length(data.columns) != 2) {
stop("For vector data, 'data.columns' should contain exactly 2 columns.")
}

# Read the CSV file
GV.csv <- read.csv(file, na.strings = na.strings)

# Check that data columns exist
for (col in data.columns) {
if (!(col %in% names(GV.csv))) {
stop(paste("Data column", col, "not found in data."))
}
}

# Check that date columns exist
if (!all(date.columns %in% names(GV.csv))) {
stop(paste("Date columns", paste(date.columns, collapse = ", "), "not found in data."))
}

# Extract data cols
data_values <- lapply(data.columns, function(col) GV.csv[[col]])

# Extract the date fields and create date strings
date_strings <- apply(GV.csv[date.columns], 1, function(row) paste(row, collapse = " "))

# Convert date strings to PCICt dates
dates <- as.PCICt(strptime(date_strings, format = date.format, tz = "UTC"), cal = calendar)

return(list(data = data_values, dates = dates))
}
129 changes: 129 additions & 0 deletions R/climdexGenericScalar.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#' @title climdexGenericScalar.raw
#'
#' @description
#' Creates a `ClimdexGenericScalar` object from raw scalar climate data.
#'
#' @details
#' This function processes scalar climate data (e.g., humidity, snow-depth)
#' and creates a `ClimdexGenericScalar` object. The function fills missing
#' values and applies NA masks based on the `max.missing.days` argument.
#' The `ClimdexGenericScalar` class is used to compute
#' basic climate indices from scalar data.
#'
#' @param data A numeric vector containing the scalar climate data.
#' @param dates A `PCICt` vector corresponding to the data dates.
#' @param max.missing.days A named vector indicating the maximum allowed missing days for `annual`, `monthly`, and `seasonal` time periods.
#' @param northern.hemisphere Whether this point is in the northern hemisphere.
#' @param calendar String representing the calendar type, e.g., "gregorian".
#' @return A `ClimdexGenericScalar` object containing the processed data.
#'
#' @seealso \code{\link{climdexGenericVector.raw}}, \code{\link{climdexGenericScalar.csv}}
#'
#' @examples
#' data <- c(10.5, 12.3, 11.2)
#' dates <- as.PCICt(c("2024-01-01", "2024-01-02", "2024-01-03"),
#' format = "%Y-%m-%d", cal = "gregorian")
#' climdexGenericScalar.raw(data,
#' dates,
#' max.missing.days = c(annual = 15, monthly = 3, seasonal = 6))
#'
#' @export

climdexGenericScalar.raw <- function(
data,
dates,
max.missing.days = c(annual = 15, monthly = 3, seasonal = 6),
northern.hemisphere = TRUE,
calendar = "gregorian"
) {

check.generic.argument.validity(data,dates,max.missing.days,calendar)

date.info <- date_info(dates)
jdays = date.info$jdays
date.series = date.info$date.series
date.factors = date.info$date.factors

filled.list <- generate_filled_list(data, dates, date.series)
names(filled.list) <- "data"
namasks <- generate_namasks(filled.list, date.factors, max.missing.days)
obj <- new("climdexGenericScalar",
data = filled.list[["data"]],
dates = date.series,
date.factors = date.factors,
jdays = jdays,
namasks = namasks,
northern.hemisphere = northern.hemisphere,
max.missing.days = max.missing.days)

return(obj)
}

#' @title climdexGenericScalar.csv
#'
#' @description
#' Reads scalar climate data from a CSV file and creates a `ClimdexGenericScalar` object.
#'
#' @details
#' This function reads scalar climate data (e.g., humidity, snow-depth) from a CSV file
#' and generates a `ClimdexGenericScalar` object.
#'
#' The CSV file should contain the climate data in a specified column, and the date fields should be provided in separate columns.
#'
#' @param file The file path to the CSV containing the scalar climate data.
#' @param data.column The name of the column containing the scalar data in the CSV file.
#' @param date.columns A vector of column names corresponding to the date fields in the CSV file.
#' @param date.format A string representing the format of the date fields.
#' @param na.strings A character vector of strings to interpret as `NA`.
#' @param northern.hemisphere Logical indicating whether the data is from the northern hemisphere.
#' @param max.missing.days A named vector specifying the maximum number of missing days allowed for annual, monthly, and seasonal periods.
#' @param calendar A string representing the calendar type (e.g., "gregorian").
#'
#' @return A `ClimdexGenericScalar` object containing the processed scalar climate data.
#'
#' @seealso \code{\link{climdexGenericScalar.raw}}, \code{\link{climdexGenericVector.csv}}
#'
#' @examples
#' # Example usage for scalar data:
#'
#' # Simulating CSV data for humidity
#' csv_data <- "
#' year,month,day,humidity
#' 2024,01,01,80
#' 2024,01,02,82
#' 2024,01,03,85
#' "
#'
#' # Write the CSV to a temporary file
#' temp_file <- tempfile(fileext = ".csv")
#' writeLines(csv_data, temp_file)
#'
#' # Call the climdexGenericScalar.csv function
#' climdexGenericScalar.csv(temp_file, data.column = "humidity",
#' date.columns = c("year", "month", "day"),
#' date.format = "%Y %m %d", calendar = "gregorian")

#' @export

climdexGenericScalar.csv <- function(
file,
data.column,
date.columns,
date.format,
na.strings = NULL,
northern.hemisphere = TRUE,
max.missing.days = c(annual = 15, monthly = 3, seasonal = 6),
calendar = "gregorian"
) {

GS.csv <- read_csv_data(file, data.column, date.columns, date.format, na.strings, calendar)
obj <- climdexGenericScalar.raw(
data = GS.csv$data[[1]],
dates = GS.csv$dates,
northern.hemisphere = northern.hemisphere,
max.missing.days = max.missing.days,
calendar = calendar
)

return(obj)
}
Loading