From 2bd594c3b9f7a5cd4d28869a151825a58a8771a5 Mon Sep 17 00:00:00 2001 From: chross22 <52218551+chross22@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:11:39 -0400 Subject: [PATCH] Add the Labrador Current retroflection index, with its citation Jutras et al. publish their index as source data with the paper, so this fetches the authors' own values rather than reimplementing them. It joins the existing catalog as LCR alongside NAO, AO, AMO, and PDO. It is the odd one out among them and often the more directly useful: the other four are atmospheric patterns, while this describes a current - how much of the Labrador Current turns east at the Grand Banks instead of continuing along the shelf, and so how much cold, fresh, oxygen-rich water reaches the Scotian Shelf and Gulf of Maine. For shelf water properties that is a shorter causal chain than the NAO. Being the output of one study rather than an operational product, it carries a citation that travels with it: a new `reference` field on the catalog entry, surfaced by index_dictionary() both as a column and in its printed output, and repeated in the fetch_climate_index() docs and the README. The NOAA indices have no single paper to point at, so their reference is NA rather than invented. Two format details, both established from the file rather than assumed: - The dates are decimal years on a fixed 365-day year. The step between rows is 1/365 to eleven significant figures; reading them as 365.25 would walk the derived dates off by several days across the record and move values into neighbouring months. - Daily values are averaged to monthly, since every other index here is monthly and attach_climate_index() joins on year and month. The underlying index is already 12-month smoothed, so little is lost. Months backed by fewer than half their days are dropped. The 365-day convention rolls the final record into a January of its own, which would otherwise appear as a confident-looking January 2015 value resting on a single day - the failure mode min_coverage guards against elsewhere. Verified against the live file: 260 monthly values, May 1993 to December 2014, attaching correctly to observations. Co-Authored-By: Claude Opus 5 --- R/climate_indices.R | 131 +++++++++++++++++++++++++- README.Rmd | 34 ++++++- README.md | 35 ++++++- man/fetch_climate_index.Rd | 10 ++ man/parse_decimal_year_csv.Rd | 41 ++++++++ tests/testthat/test-climate-indices.R | 64 +++++++++++++ 6 files changed, 311 insertions(+), 4 deletions(-) create mode 100644 man/parse_decimal_year_csv.Rd diff --git a/R/climate_indices.R b/R/climate_indices.R index f299888..1f9fddb 100644 --- a/R/climate_indices.R +++ b/R/climate_indices.R @@ -53,6 +53,27 @@ climate_indices <- function() { description = paste("Leading mode of North Pacific SST variability.", "Included for completeness; of limited relevance to", "Atlantic shelf systems.") + ), + LCR = list( + label = "Labrador Current retroflection", + source = "Jutras et al. 2023, Nature Communications", + url = paste0("https://static-content.springer.com/esm/", + "art%3A10.1038%2Fs41467-023-38321-y/MediaObjects/", + "41467_2023_38321_MOESM5_ESM.csv"), + format = "decimal_year_csv", + reference = paste("Jutras M, Dufour CO, Mucci A, Talbot LC (2023)", + "Large-scale control of the retroflection of the", + "Labrador Current. Nature Communications 14:2623.", + "doi:10.1038/s41467-023-38321-y"), + description = paste("How much of the Labrador Current turns eastward at", + "the Grand Banks instead of continuing southwest along", + "the shelf. Positive values mean stronger retroflection,", + "so less cold, fresh, oxygen-rich Labrador water reaches", + "the Scotian Shelf and Gulf of Maine. Unlike the", + "atmospheric indices here it describes a current rather", + "than a pressure or temperature pattern, which makes it", + "the more direct predictor of shelf water properties.", + "Covers 1993-2014 only.") ) ) } @@ -68,7 +89,12 @@ index_dictionary <- function() { dictionary <- do.call(rbind, lapply(names(catalog), function(name) { entry <- catalog[[name]] data.frame(name = name, label = entry$label, source = entry$source, - url = entry$url, description = entry$description, + url = entry$url, + # Indices published with a paper carry its citation. The + # operational ones from NOAA have no single paper to point at, + # so this is empty for them rather than invented. + reference = entry$reference %||% NA_character_, + description = entry$description, stringsAsFactors = FALSE) })) class(dictionary) <- c("datamatch_index_dictionary", "data.frame") @@ -85,7 +111,16 @@ print.datamatch_index_dictionary <- function(x, ...) { cat(strrep("-", 62), "\n", sep = "") print(flat[c("name", "label", "source")], row.names = FALSE, right = FALSE) cat("\nThese have no spatial dimension: one value per month, basin-wide.\n") - cat("Sources: as.data.frame(index_dictionary())$url\n") + + cited <- flat[!is.na(flat$reference), ] + if (nrow(cited) > 0) { + cat("\nCite when used:\n") + for (i in seq_len(nrow(cited))) { + cat(" ", cited$name[i], ": ", cited$reference[i], "\n", sep = "") + } + } + + cat("\nSources: as.data.frame(index_dictionary())$url\n") invisible(x) } @@ -97,6 +132,18 @@ print.datamatch_index_dictionary <- function(x, ...) { #' The published files use fixed-width year-by-month tables with provider-specific #' missing-value codes, which are parsed here into one row per month. #' +#' @section Citing an index: +#' `LCR` is the published output of a specific study rather than an operational +#' product, and should be cited when used: +#' +#' > Jutras M, Dufour CO, Mucci A, Talbot LC (2023) Large-scale control of the +#' > retroflection of the Labrador Current. *Nature Communications* **14**:2623. +#' > \doi{10.1038/s41467-023-38321-y} +#' +#' The series is the source data published with that paper's Figure 3, fetched +#' from the journal rather than recomputed, so the values are the authors' own. +#' `as.data.frame(index_dictionary())$reference` carries this at runtime. +#' #' @param index an index name from [climate_indices()] #' @param years years to keep; `NULL` keeps the whole record #' @param url override the catalog URL, if a provider has moved the file @@ -145,6 +192,13 @@ fetch_climate_index <- function(index, years = NULL, url = NULL) { #' @return a data frame with `YEAR`, `MONTH`, and the named value column #' @keywords internal parse_index_table <- function(lines, format, name) { + # Not every published index is a year-by-month table. The retroflection index + # is a daily series in decimal years, so it is parsed separately rather than + # bent into the shape of the CPC and PSL files. + if (format == "decimal_year_csv") { + return(parse_decimal_year_csv(lines, name)) + } + lines <- trimws(lines) lines <- lines[nzchar(lines)] @@ -196,6 +250,79 @@ parse_index_table <- function(lines, format, name) { series } +#' Parse a daily index published as decimal years +#' +#' The format the Jutras et al. retroflection index is published in: a couple of +#' figure-caption lines, a `Date,