From 6ae75d90d7815956b5996d5f0b8f03e9e761bfec Mon Sep 17 00:00:00 2001 From: abagna123 Date: Tue, 14 Apr 2026 17:06:01 +0200 Subject: [PATCH 01/10] add error columns to select error columns from ASTR objects --- R/ASTR_column_select.R | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/R/ASTR_column_select.R b/R/ASTR_column_select.R index f9aac12..99cc3fc 100644 --- a/R/ASTR_column_select.R +++ b/R/ASTR_column_select.R @@ -110,3 +110,13 @@ get_unit_columns <- function(x, units, ...) { get_unit_columns.ASTR <- function(x, units, ...) { get_cols_with_unit(x, units) } + +#' @rdname ASTR +#' @export +get_error_columns <- function(x, ...) { + UseMethod("get_error_columns") +} +#' @export +get_error_columns.ASTR <- function(x, ...) { + get_cols_with_ac_class(x, c("ASTR_id", "ASTR_error")) +} From 8a1ad68230a23d1370272ce0fff3d4b320917d7e Mon Sep 17 00:00:00 2001 From: abagna123 Date: Tue, 14 Apr 2026 17:08:59 +0200 Subject: [PATCH 02/10] add rel <-> abs error conversion function --- R/ASTR_error_conversion.R | 127 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 R/ASTR_error_conversion.R diff --git a/R/ASTR_error_conversion.R b/R/ASTR_error_conversion.R new file mode 100644 index 0000000..b8e5136 --- /dev/null +++ b/R/ASTR_error_conversion.R @@ -0,0 +1,127 @@ +#' Convert relative errors to absolute errors in ASTR object +#' +#' Converts all relative error columns to absolute errors using their +#' corresponding measured value columns. +#' +#' @param df An ASTR object +#' @return An ASTR object with errors converted to absolute +#' @export +#' +#' @examples +#' arch_abs <- rel_to_abs(arch) +rel_to_abs <- function(df) { + UseMethod("rel_to_abs") +} + +#' @export +rel_to_abs.ASTR <- function(df) { + + # Basic checks + checkmate::assert_class(df, "ASTR") + + # Find all error columns + error_cols <- get_cols_with_ac_class(df, "ASTR_error") + + if (length(error_cols) == 0) { + warning("No error columns found. Returning unchanged.") + return(df) + } + + # Process each error column + for (err_col in names(error_cols)) { + + # Find matching concentration column + base_name <- gsub("_err.*$", "", err_col) + + if (!base_name %in% names(df)) { + warning("No matching concentration column for: ", err_col, ". Skipping.") + next + } + + # Check if error is relative (has % units) + err_unit <- units::deparse_unit(df[[err_col]]) + + if (!err_unit %in% c("%", "atP", "wtP")) { + warning("Error column ", err_col, " has unit ", err_unit, + " which is not recognized as relative. Skipping.") + next + } + + # absolute = (relative / 100) * measured_value + df[[err_col]] <- (df[[err_col]] / 100) * df[[base_name]] + + # Set units to match the concentration column + units(df[[err_col]]) <- units(df[[base_name]]) + + # Update ASTR_class to mark as absolute error + attr(df[[err_col]], "ASTR_class") <- "ASTR_error_absolute" + } + + return(df) +} + +#' Convert absolute errors to relative errors in ASTR object +#' +#' Converts all absolute error columns to relative errors (%). +#' +#' @param df An ASTR object +#' @return An ASTR object with errors converted to relative +#' @export +#' +#' @examples +#' arch_rel <- abs_to_rel(arch) +abs_to_rel <- function(df) { + UseMethod("abs_to_rel") +} + +#' @export +abs_to_rel.ASTR <- function(df) { + + # Basic checks + checkmate::assert_class(df, "ASTR") + + # Find all error columns (both regular and absolute-marked) + error_cols <- get_cols_with_ac_class(df, "ASTR_error") + abs_error_cols <- get_cols_with_ac_class(df, "ASTR_error_absolute") + + all_error_cols <- unique(c(names(error_cols), names(abs_error_cols))) + + if (length(all_error_cols) == 0) { + warning("No error columns found. Returning unchanged.") + return(df) + } + + # Process each error column + for (err_col in all_error_cols) { + + # Find matching concentration column + base_name <- gsub("_err.*$", "", err_col) + + if (!base_name %in% names(df)) { + warning("No matching concentration column for: ", err_col, ". Skipping.") + next + } + + # Check if error is absolute (same unit as concentration) + err_unit <- units::deparse_unit(df[[err_col]]) + conc_unit <- units::deparse_unit(df[[base_name]]) + + if (err_unit != conc_unit) { + warning("Error column ", err_col, " does not share units with its concentration column.", + "\n Error unit: ", err_unit, " | Concentration unit: ", conc_unit, + "\n Skipping conversion.") + next + } + + # relative = (absolute / measured_value) * 100 + df[[err_col]] <- (df[[err_col]] / df[[base_name]]) * 100 + + # Set units to percent + units(df[[err_col]]) <- units::as_units("%") + + # Update ASTR_class + attr(df[[err_col]], "ASTR_class") <- "ASTR_error" + } + + return(df) +} From b129be64522f26c17ef030307b9ce0b1bad5bc87 Mon Sep 17 00:00:00 2001 From: Thomas Rose Date: Sun, 19 Apr 2026 15:39:15 -0400 Subject: [PATCH 03/10] tests for error column selector, some edits on error conversion functions --- NAMESPACE | 4 ++++ R/ASTR_colname_parser.R | 2 ++ R/ASTR_column_select.R | 20 ++++++++++---------- R/ASTR_error_conversion.R | 18 ++++-------------- man/ASTR.Rd | 3 +++ man/abs_to_rel.Rd | 17 +++++++++++++++++ man/rel_to_abs.Rd | 18 ++++++++++++++++++ tests/testthat/test_ASTR_column_selection.R | 6 ++++++ 8 files changed, 64 insertions(+), 24 deletions(-) create mode 100644 man/abs_to_rel.Rd create mode 100644 man/rel_to_abs.Rd diff --git a/NAMESPACE b/NAMESPACE index 79cf8be..7ceaf9c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,6 +8,7 @@ S3method(get_analytical_columns,ASTR) S3method(get_concentration_columns,ASTR) S3method(get_contextual_columns,ASTR) S3method(get_element_columns,ASTR) +S3method(get_error_columns,ASTR) S3method(get_isotope_columns,ASTR) S3method(get_ratio_columns,ASTR) S3method(get_unit_columns,ASTR) @@ -15,6 +16,7 @@ S3method(print,ASTR) S3method(remove_units,ASTR) S3method(validate,ASTR) S3method(validate,default) +export(abs_to_rel) export(albarede_juteau_1984) export(as_ASTR) export(at_to_wt) @@ -30,6 +32,7 @@ export(get_analytical_columns) export(get_concentration_columns) export(get_contextual_columns) export(get_element_columns) +export(get_error_columns) export(get_isotope_columns) export(get_ratio_columns) export(get_unit_columns) @@ -37,6 +40,7 @@ export(oxide_to_element) export(pb_iso_age_model) export(pointcloud_distribution) export(read_ASTR) +export(rel_to_abs) export(remove_units) export(stacey_kramers_1975) export(unify_concentration_unit) diff --git a/R/ASTR_colname_parser.R b/R/ASTR_colname_parser.R index 0c9a1c6..a5b692a 100644 --- a/R/ASTR_colname_parser.R +++ b/R/ASTR_colname_parser.R @@ -351,6 +351,8 @@ err_percent <- function() { } err_2sd_percent <- function() "\\_err2SD%" err_sd_percent <- function() "\\_errSD%" +err_2se_percent <- function() "\\_err2SE%" +err_se_percent <- function() "\\_errSE%" err_abs <- function() { paste0(c( diff --git a/R/ASTR_column_select.R b/R/ASTR_column_select.R index 99cc3fc..68180a1 100644 --- a/R/ASTR_column_select.R +++ b/R/ASTR_column_select.R @@ -80,6 +80,16 @@ get_concentration_columns.ASTR <- function(x, ...) { get_cols_with_ac_class(x, c("ASTR_id", "ASTR_concentration")) } +#' @rdname ASTR +#' @export +get_error_columns <- function(x, ...) { + UseMethod("get_error_columns") +} +#' @export +get_error_columns.ASTR <- function(x, ...) { + get_cols_with_ac_class(x, c("ASTR_id", "ASTR_error")) +} + get_cols_with_unit <- function(x, units) { units <- sapply(units, function(unit) transform_notation(unit)) @@ -110,13 +120,3 @@ get_unit_columns <- function(x, units, ...) { get_unit_columns.ASTR <- function(x, units, ...) { get_cols_with_unit(x, units) } - -#' @rdname ASTR -#' @export -get_error_columns <- function(x, ...) { - UseMethod("get_error_columns") -} -#' @export -get_error_columns.ASTR <- function(x, ...) { - get_cols_with_ac_class(x, c("ASTR_id", "ASTR_error")) -} diff --git a/R/ASTR_error_conversion.R b/R/ASTR_error_conversion.R index b8e5136..2ace042 100644 --- a/R/ASTR_error_conversion.R +++ b/R/ASTR_error_conversion.R @@ -8,19 +8,14 @@ #' @export #' #' @examples -#' arch_abs <- rel_to_abs(arch) +#' rel_to_abs <- function(df) { - UseMethod("rel_to_abs") -} - -#' @export -rel_to_abs.ASTR <- function(df) { # Basic checks checkmate::assert_class(df, "ASTR") # Find all error columns - error_cols <- get_cols_with_ac_class(df, "ASTR_error") + error_cols <- get_error_columns(df) if (length(error_cols) == 0) { warning("No error columns found. Returning unchanged.") @@ -69,19 +64,14 @@ rel_to_abs.ASTR <- function(df) { #' @export #' #' @examples -#' arch_rel <- abs_to_rel(arch) +#' abs_to_rel <- function(df) { - UseMethod("abs_to_rel") -} - -#' @export -abs_to_rel.ASTR <- function(df) { # Basic checks checkmate::assert_class(df, "ASTR") # Find all error columns (both regular and absolute-marked) - error_cols <- get_cols_with_ac_class(df, "ASTR_error") + error_cols <- get_error_columns(df, "ASTR_error") abs_error_cols <- get_cols_with_ac_class(df, "ASTR_error_absolute") all_error_cols <- unique(c(names(error_cols), names(abs_error_cols))) diff --git a/man/ASTR.Rd b/man/ASTR.Rd index 768494f..91da787 100644 --- a/man/ASTR.Rd +++ b/man/ASTR.Rd @@ -14,6 +14,7 @@ \alias{get_element_columns} \alias{get_ratio_columns} \alias{get_concentration_columns} +\alias{get_error_columns} \alias{get_unit_columns} \alias{remove_units} \alias{unify_concentration_unit} @@ -66,6 +67,8 @@ get_ratio_columns(x, ...) get_concentration_columns(x, ...) +get_error_columns(x, ...) + get_unit_columns(x, units, ...) remove_units(x, ...) diff --git a/man/abs_to_rel.Rd b/man/abs_to_rel.Rd new file mode 100644 index 0000000..68b489f --- /dev/null +++ b/man/abs_to_rel.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ASTR_error_conversion.R +\name{abs_to_rel} +\alias{abs_to_rel} +\title{Convert absolute errors to relative errors in ASTR object} +\usage{ +abs_to_rel(df) +} +\arguments{ +\item{df}{An ASTR object} +} +\value{ +An ASTR object with errors converted to relative +} +\description{ +Converts all absolute error columns to relative errors (\%). +} diff --git a/man/rel_to_abs.Rd b/man/rel_to_abs.Rd new file mode 100644 index 0000000..fe7d732 --- /dev/null +++ b/man/rel_to_abs.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ASTR_error_conversion.R +\name{rel_to_abs} +\alias{rel_to_abs} +\title{Convert relative errors to absolute errors in ASTR object} +\usage{ +rel_to_abs(df) +} +\arguments{ +\item{df}{An ASTR object} +} +\value{ +An ASTR object with errors converted to absolute +} +\description{ +Converts all relative error columns to absolute errors using their +corresponding measured value columns. +} diff --git a/tests/testthat/test_ASTR_column_selection.R b/tests/testthat/test_ASTR_column_selection.R index 860bfd5..4395949 100644 --- a/tests/testthat/test_ASTR_column_selection.R +++ b/tests/testthat/test_ASTR_column_selection.R @@ -32,6 +32,12 @@ test_that("column selection based on ASTR column types", { "U", "V", "Cr", "Co", "Ni", "Sr", "Se" ) ) + expect_all_true( + colnames(get_error_columns(test_input)) == + c("ID", "d65Cu_err2SD", "SiO2_errSD%", "FeOtot_err2SD", "206Pb/204Pb_err2SD", + "207Pb/204Pb_err2SD", "208Pb/204Pb_err2SD", "207Pb/206Pb_err2SD", "208Pb/206Pb_err2SD" + ) + ) expect_all_true( colnames(get_unit_columns(test_input, c("ng/g", "µg/ml", "%"))) == c("ID", "SiO2_errSD%", "ZnO", "Ag", "Sn") From 305f4e627f23ff696c76736177cf9df52aca998c Mon Sep 17 00:00:00 2001 From: abagna123 Date: Tue, 21 Apr 2026 14:51:45 +0200 Subject: [PATCH 04/10] revise error_conversion function --- R/ASTR_error_conversion.R | 87 +++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 50 deletions(-) diff --git a/R/ASTR_error_conversion.R b/R/ASTR_error_conversion.R index 2ace042..fc2a322 100644 --- a/R/ASTR_error_conversion.R +++ b/R/ASTR_error_conversion.R @@ -1,14 +1,28 @@ -#' Convert relative errors to absolute errors in ASTR object +#' Convert between relative and absolute errors #' -#' Converts all relative error columns to absolute errors using their -#' corresponding measured value columns. +#' Convert relative error columns (e.g., those ending with _errSD%) to absolute +#' errors, or vice versa. Relative errors are percentages. Absolute errors have +#' the same units as the measured values. #' #' @param df An ASTR object -#' @return An ASTR object with errors converted to absolute -#' @export +#' @return An ASTR object with converted error columns. #' #' @examples +#' # rel_to_abs: absolute = (relative / 100) * value +#' # When SiO2 = 31.6 wt%, relative error = 4.3% +#' # absolute error = (4.3 / 100) * 31.6 = 1.36 wt% +#' arch_abs <- rel_to_abs(arch) +#' +#' # abs_to_rel: relative = (absolute / value) * 100 +#' # When SiO2 = 31.6 wt%, absolute error = 1.36 wt% +#' # relative error = (1.36 / 31.6) * 100 = 4.3% +#' arch_rel <- abs_to_rel(arch) #' +#' @name error_conversion +NULL + +#' @rdname error_conversion +#' @export rel_to_abs <- function(df) { # Basic checks @@ -25,81 +39,57 @@ rel_to_abs <- function(df) { # Process each error column for (err_col in names(error_cols)) { - # Find matching concentration column - base_name <- gsub("_err.*$", "", err_col) - - if (!base_name %in% names(df)) { - warning("No matching concentration column for: ", err_col, ". Skipping.") + # Only process relative errors + if (!is_err_percent(err_col)) { next } - # Check if error is relative (has % units) - err_unit <- units::deparse_unit(df[[err_col]]) + # Find matching concentration column + base_name <- gsub("_err(2)?(SD|SE)(%)?$", "", err_col) - if (!err_unit %in% c("%", "atP", "wtP")) { - warning("Error column ", err_col, " has unit ", err_unit, - " which is not recognized as relative. Skipping.") + if (!base_name %in% names(df)) { + warning("No matching concentration column for: ", err_col, ". Skipping.") next } - # absolute = (relative / 100) * measured_value + # Absolute = (relative / 100) * measured_value df[[err_col]] <- (df[[err_col]] / 100) * df[[base_name]] # Set units to match the concentration column units(df[[err_col]]) <- units(df[[base_name]]) - - # Update ASTR_class to mark as absolute error - attr(df[[err_col]], "ASTR_class") <- "ASTR_error_absolute" } return(df) } -#' Convert absolute errors to relative errors in ASTR object -#' -#' Converts all absolute error columns to relative errors (%). -#' -#' @param df An ASTR object -#' @return An ASTR object with errors converted to relative +#' @rdname error_conversion #' @export -#' -#' @examples -#' abs_to_rel <- function(df) { # Basic checks checkmate::assert_class(df, "ASTR") - # Find all error columns (both regular and absolute-marked) - error_cols <- get_error_columns(df, "ASTR_error") - abs_error_cols <- get_cols_with_ac_class(df, "ASTR_error_absolute") - - all_error_cols <- unique(c(names(error_cols), names(abs_error_cols))) + # Find all error columns + error_cols <- get_error_columns(df) - if (length(all_error_cols) == 0) { + if (length(error_cols) == 0) { warning("No error columns found. Returning unchanged.") return(df) } # Process each error column - for (err_col in all_error_cols) { - - # Find matching concentration column - base_name <- gsub("_err.*$", "", err_col) + for (err_col in names(error_cols)) { - if (!base_name %in% names(df)) { - warning("No matching concentration column for: ", err_col, ". Skipping.") + # Only process absolute errors + if (!is_err_abs(err_col)) { next } - # Check if error is absolute (same unit as concentration) - err_unit <- units::deparse_unit(df[[err_col]]) - conc_unit <- units::deparse_unit(df[[base_name]]) + # Find matching concentration column + base_name <- gsub("_err(2)?(SD|SE)(%)?$", "", err_col) - if (err_unit != conc_unit) { - warning("Error column ", err_col, " does not share units with its concentration column.", - "\n Error unit: ", err_unit, " | Concentration unit: ", conc_unit, - "\n Skipping conversion.") + if (!base_name %in% names(df)) { + warning("No matching concentration column for: ", err_col, ". Skipping.") next } @@ -108,9 +98,6 @@ abs_to_rel <- function(df) { # Set units to percent units(df[[err_col]]) <- units::as_units("%") - - # Update ASTR_class - attr(df[[err_col]], "ASTR_class") <- "ASTR_error" } return(df) From c65cd4c8b43c4c85d95ec30fd2df6b2376aef9f6 Mon Sep 17 00:00:00 2001 From: abagna123 Date: Wed, 22 Apr 2026 17:22:53 +0200 Subject: [PATCH 05/10] add error conversion function --- R/ASTR_error_conversion.R | 62 ++++++++++++--------------------------- 1 file changed, 19 insertions(+), 43 deletions(-) diff --git a/R/ASTR_error_conversion.R b/R/ASTR_error_conversion.R index fc2a322..c2d438c 100644 --- a/R/ASTR_error_conversion.R +++ b/R/ASTR_error_conversion.R @@ -8,15 +8,11 @@ #' @return An ASTR object with converted error columns. #' #' @examples -#' # rel_to_abs: absolute = (relative / 100) * value -#' # When SiO2 = 31.6 wt%, relative error = 4.3% -#' # absolute error = (4.3 / 100) * 31.6 = 1.36 wt% -#' arch_abs <- rel_to_abs(arch) +#' # For SiO2 = 31.6 wt% with relative error 4.3%: +#' # rel_to_abs: (4.3 / 100) * 31.6 = 1.36 wt% #' -#' # abs_to_rel: relative = (absolute / value) * 100 -#' # When SiO2 = 31.6 wt%, absolute error = 1.36 wt% -#' # relative error = (1.36 / 31.6) * 100 = 4.3% -#' arch_rel <- abs_to_rel(arch) +#' # For SiO2 = 31.6 wt% with absolute error 1.36 wt%: +#' # abs_to_rel: (1.36 / 31.6) * 100 = 4.3% #' #' @name error_conversion NULL @@ -29,36 +25,25 @@ rel_to_abs <- function(df) { checkmate::assert_class(df, "ASTR") # Find all error columns - error_cols <- get_error_columns(df) + error_cols <- colnames(df)[sapply(colnames(df), is_err_percent)] if (length(error_cols) == 0) { warning("No error columns found. Returning unchanged.") return(df) } - # Process each error column - for (err_col in names(error_cols)) { + # Process all error columns + for (err_col in error_cols) { + base_name <- remove_suffix(err_col) - # Only process relative errors - if (!is_err_percent(err_col)) { - next - } + if (base_name %in% names(df)) { + # Absolute = relative * measured_value + df[[err_col]] <- df[[err_col]] * df[[base_name]] - # Find matching concentration column - base_name <- gsub("_err(2)?(SD|SE)(%)?$", "", err_col) - - if (!base_name %in% names(df)) { - warning("No matching concentration column for: ", err_col, ". Skipping.") - next + # Set units to match the concentration column + units(df[[err_col]]) <- units(df[[base_name]]) } - - # Absolute = (relative / 100) * measured_value - df[[err_col]] <- (df[[err_col]] / 100) * df[[base_name]] - - # Set units to match the concentration column - units(df[[err_col]]) <- units(df[[base_name]]) } - return(df) } @@ -70,31 +55,22 @@ abs_to_rel <- function(df) { checkmate::assert_class(df, "ASTR") # Find all error columns - error_cols <- get_error_columns(df) + error_cols <- colnames(df)[sapply(colnames(df), is_err_abs)] if (length(error_cols) == 0) { warning("No error columns found. Returning unchanged.") return(df) } - # Process each error column - for (err_col in names(error_cols)) { - - # Only process absolute errors - if (!is_err_abs(err_col)) { - next - } - - # Find matching concentration column - base_name <- gsub("_err(2)?(SD|SE)(%)?$", "", err_col) + # Process all error columns + for (err_col in error_cols) { + base_name <- remove_suffix(err_col) if (!base_name %in% names(df)) { - warning("No matching concentration column for: ", err_col, ". Skipping.") next } - - # relative = (absolute / measured_value) * 100 - df[[err_col]] <- (df[[err_col]] / df[[base_name]]) * 100 + # relative = (absolute / measured_value) + df[[err_col]] <- df[[err_col]] / df[[base_name]] # Set units to percent units(df[[err_col]]) <- units::as_units("%") From 7bccb0aa9f605f97366d77bcffc202b9b0231d59 Mon Sep 17 00:00:00 2001 From: abagna123 Date: Wed, 22 Apr 2026 17:23:56 +0200 Subject: [PATCH 06/10] add comprehensive tests for error conversion functions --- tests/testthat/test-error_conversion.R | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tests/testthat/test-error_conversion.R diff --git a/tests/testthat/test-error_conversion.R b/tests/testthat/test-error_conversion.R new file mode 100644 index 0000000..d57ee31 --- /dev/null +++ b/tests/testthat/test-error_conversion.R @@ -0,0 +1,63 @@ +test_input <- suppressWarnings( + read_ASTR( + system.file("extdata", "test_data_input_good.csv", package = "ASTR"), + id_column = "Sample", + context = c("Lab no.", "Site", "latitude", "longitude", "Type", "method_comp") + ) +) + +# --- rel_to_abs --- + +test_that("rel_to_abs: numeric conversion is correct", { + result <- rel_to_abs(test_input) + relative_raw <- as.numeric(test_input[["SiO2_errSD%"]]) + value_raw <- as.numeric(test_input[["SiO2"]]) + expect_equal(as.numeric(result[["SiO2_errSD%"]]), + (relative_raw / 100) * value_raw, tolerance = 1e-6) +}) + +test_that("rel_to_abs: error col units match base col after conversion", { + result <- rel_to_abs(test_input) + expect_equal(as.character(units(result[["SiO2_errSD%"]])), + as.character(units(result[["SiO2"]]))) +}) + +test_that("rel_to_abs: base col and class unchanged", { + result <- rel_to_abs(test_input) + expect_equal(as.numeric(result[["SiO2"]]), as.numeric(test_input[["SiO2"]])) + expect_s3_class(result, "ASTR") +}) + +test_that("rel_to_abs: NA in base col produces NA in error col, no crash", { + expect_no_error(result <- rel_to_abs(test_input)) + na_rows <- is.na(as.numeric(test_input[["SiO2"]])) + expect_true(all(is.na(as.numeric(result[["SiO2_errSD%"]])[na_rows]))) +}) + +test_that("rel_to_abs: warns when no relative error cols present", { + df_no_err <- test_input[, !grepl("%$", colnames(test_input))] + class(df_no_err) <- class(test_input) + expect_warning(rel_to_abs(df_no_err), "No error columns found") +}) + +test_that("rel_to_abs: errors on non-ASTR input", { + expect_error(rel_to_abs(data.frame(x = 1))) +}) + +# --- abs_to_rel --- + +test_that("abs_to_rel: numeric conversion is correct", { + abs_input <- rel_to_abs(test_input) + result <- abs_to_rel(abs_input) + expect_equal(as.numeric(result[["SiO2_errSD%"]]), + as.numeric(test_input[["SiO2_errSD%"]]), tolerance = 1e-6) +}) + +test_that("abs_to_rel: error col units are percent after conversion", { + result <- abs_to_rel(rel_to_abs(test_input)) + expect_equal(as.character(units(result[["SiO2_errSD%"]])), "%") +}) + +test_that("abs_to_rel: errors on non-ASTR input", { + expect_error(abs_to_rel(data.frame(x = 1))) +}) From adacf6aa5a464beb927be5c60177e27d0ac5b86c Mon Sep 17 00:00:00 2001 From: Thomas Rose Date: Mon, 25 May 2026 11:37:06 -0400 Subject: [PATCH 07/10] fix absolute errors catching also relative errors --- R/ASTR_colname_parser.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/ASTR_colname_parser.R b/R/ASTR_colname_parser.R index a5b692a..506839a 100644 --- a/R/ASTR_colname_parser.R +++ b/R/ASTR_colname_parser.R @@ -282,7 +282,7 @@ is_err_percent <- function(colname) { grepl(err_percent(), colname, perl = TRUE) } is_err_abs <- function(colname) { - grepl(err_abs(), colname, perl = TRUE) + grepl(paste0("(", err_abs(), ")(?!%)"), colname, perl = TRUE) } is_isotope_ratio <- function(colname) { grepl(isotope_ratio(), colname, perl = TRUE) From df524966c871fe4e3d0bc781747a00aafd02337b Mon Sep 17 00:00:00 2001 From: Thomas Rose Date: Mon, 25 May 2026 14:03:56 -0400 Subject: [PATCH 08/10] fix typo in file name --- tests/testthat/_snaps/{goem_sk_lines => geom_sk_lines}/image.svg | 0 tests/testthat/{test_goem_sk_lines.R => test_geom_sk_lines.R} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/testthat/_snaps/{goem_sk_lines => geom_sk_lines}/image.svg (100%) rename tests/testthat/{test_goem_sk_lines.R => test_geom_sk_lines.R} (100%) diff --git a/tests/testthat/_snaps/goem_sk_lines/image.svg b/tests/testthat/_snaps/geom_sk_lines/image.svg similarity index 100% rename from tests/testthat/_snaps/goem_sk_lines/image.svg rename to tests/testthat/_snaps/geom_sk_lines/image.svg diff --git a/tests/testthat/test_goem_sk_lines.R b/tests/testthat/test_geom_sk_lines.R similarity index 100% rename from tests/testthat/test_goem_sk_lines.R rename to tests/testthat/test_geom_sk_lines.R From b3efd627f0a657bb4e5bc71a56a1120ff252773e Mon Sep 17 00:00:00 2001 From: Thomas Rose Date: Mon, 25 May 2026 14:15:21 -0400 Subject: [PATCH 09/10] implement error conversion: * handling edge cases * inclusion in parsing workflow * update implementation documentation and vignette --- NEWS.md | 1 + R/ASTR_basic.R | 6 +- R/ASTR_conversion_error.R | 100 ++++++++++++++++++++ R/ASTR_error_conversion.R | 80 ---------------- man/abs_to_rel.Rd | 17 ---- man/error_conversion.Rd | 36 +++++++ man/rel_to_abs.Rd | 18 ---- tests/testthat/_snaps/ASTR_basic.md | 30 +++--- tests/testthat/test-error_conversion.R | 63 ------------ tests/testthat/test_ASTR_column_selection.R | 4 +- tests/testthat/test_conversion_error.R | 77 +++++++++++++++ vignettes/ASTR.showcase.Rmd | 7 +- vignettes/VG.ASTR.Schema.Implementation.Rmd | 2 + 13 files changed, 241 insertions(+), 200 deletions(-) create mode 100644 R/ASTR_conversion_error.R delete mode 100644 R/ASTR_error_conversion.R delete mode 100644 man/abs_to_rel.Rd create mode 100644 man/error_conversion.Rd delete mode 100644 man/rel_to_abs.Rd delete mode 100644 tests/testthat/test-error_conversion.R create mode 100644 tests/testthat/test_conversion_error.R diff --git a/NEWS.md b/NEWS.md index f279ee8..dea1ba0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,3 +2,4 @@ * Implementation of ASTR schema. * Support and conversions for geochemical non-SI units: *at%*, *wt%* (elements and oxides) +* Support and conversions for relative and absolute analytical precisions. diff --git a/R/ASTR_basic.R b/R/ASTR_basic.R index 1d78077..b894e40 100644 --- a/R/ASTR_basic.R +++ b/R/ASTR_basic.R @@ -174,9 +174,11 @@ as_ASTR <- function( df4 <- remove_unit_substrings(df3) # turn into tibble-derived object df5 <- tibble::new_tibble(df4, nrow = nrow(df4), class = "ASTR") + # convert relative to absolute errors + df6 <- rel_to_abs(df5) # post-reading validation if (validate) { - validation_output <- validate(df5, quiet = FALSE) + validation_output <- validate(df6, quiet = FALSE) if (nrow(validation_output) > 0) { warning( "See the full list of validation output with: ", @@ -184,7 +186,7 @@ as_ASTR <- function( ) } } - return(df5) + return(df6) } # helper function to rename column names diff --git a/R/ASTR_conversion_error.R b/R/ASTR_conversion_error.R new file mode 100644 index 0000000..fc8b3f6 --- /dev/null +++ b/R/ASTR_conversion_error.R @@ -0,0 +1,100 @@ +#' Convert between relative and absolute analytical uncertainties +#' +#' Convert relative to absolute analytical uncertainties and vice versa in ASTR +#' objects. Work only for objects of class `ASTR`. +#' +#' @param df An ASTR object +#' @return An ASTR object with converted analytical precision columns. The +#' unchanged input, if it does not contain columns of the respective +#' analytical precision type +#' +#' +#' @name error_conversion +#' @export +#' +#' @examples +#' test_file <- system.file("extdata", "test_data_input_good.csv", package = "ASTR") +#' arch <- read_ASTR(test_file, id_column = "Sample", context = 1:7) +#' +#' arch2 <- abs_to_rel(arch) +#' +#' arch3 <- rel_to_abs(arch2) +#' +#' # Conversion is lossless +#' all.equal(arch$`SiO2_errSD`, arch3$`SiO2_errSD`) +#' +rel_to_abs <- function(df) { + + # Basic checks + checkmate::assert_class(df, "ASTR") + + # Find all error columns + error_cols <- colnames(df)[is_err_percent(colnames(df))] + + if (length(error_cols) == 0) { + return(df) + } + + df_old <- df + + # Process all error columns + for (err_col in error_cols) { + base_name <- remove_suffix(err_col) + + if (base_name %in% names(df)) { + # absolute = relative * measured_value + df[[err_col]] <- df[[err_col]] * df[[base_name]] / ifelse(inherits(df[[base_name]], "units"), 1, 100) + + # Set units to match the concentration column + if (inherits(df[[base_name]], "units")) { + units(df[[err_col]]) <- units(df[[base_name]]) + } else { + units(df[[err_col]]) <- NULL + } + } + } + + # assign ASTR class + df <- preserve_ASTR_attrs(df, df_old) + + # rename column names + colnames(df)[colnames(df) %in% error_cols] <- gsub("%", "", error_cols) + + return(df) +} + +#' @rdname error_conversion +#' @export +abs_to_rel <- function(df) { + + # Basic checks + checkmate::assert_class(df, "ASTR") + + # Find all error columns + error_cols <- colnames(df)[is_err_abs(colnames(df))] + + if (length(error_cols) == 0) { + return(df) + } + + df_old <- df + + # Process all error columns + for (err_col in error_cols) { + base_name <- remove_suffix(err_col) + + # relative = (absolute / measured_value) + df[[err_col]] <- df[[err_col]] / df[[base_name]] * ifelse(inherits(df[[base_name]], "units"), 1, 100) + + # Set units to percent + units(df[[err_col]]) <- units::as_units("%") + } + + # assign ASTR class + df <- preserve_ASTR_attrs(df, df_old) + + # rename columns + colnames(df)[colnames(df) %in% error_cols] <- paste0(error_cols, "%") + + return(df) +} diff --git a/R/ASTR_error_conversion.R b/R/ASTR_error_conversion.R deleted file mode 100644 index c2d438c..0000000 --- a/R/ASTR_error_conversion.R +++ /dev/null @@ -1,80 +0,0 @@ -#' Convert between relative and absolute errors -#' -#' Convert relative error columns (e.g., those ending with _errSD%) to absolute -#' errors, or vice versa. Relative errors are percentages. Absolute errors have -#' the same units as the measured values. -#' -#' @param df An ASTR object -#' @return An ASTR object with converted error columns. -#' -#' @examples -#' # For SiO2 = 31.6 wt% with relative error 4.3%: -#' # rel_to_abs: (4.3 / 100) * 31.6 = 1.36 wt% -#' -#' # For SiO2 = 31.6 wt% with absolute error 1.36 wt%: -#' # abs_to_rel: (1.36 / 31.6) * 100 = 4.3% -#' -#' @name error_conversion -NULL - -#' @rdname error_conversion -#' @export -rel_to_abs <- function(df) { - - # Basic checks - checkmate::assert_class(df, "ASTR") - - # Find all error columns - error_cols <- colnames(df)[sapply(colnames(df), is_err_percent)] - - if (length(error_cols) == 0) { - warning("No error columns found. Returning unchanged.") - return(df) - } - - # Process all error columns - for (err_col in error_cols) { - base_name <- remove_suffix(err_col) - - if (base_name %in% names(df)) { - # Absolute = relative * measured_value - df[[err_col]] <- df[[err_col]] * df[[base_name]] - - # Set units to match the concentration column - units(df[[err_col]]) <- units(df[[base_name]]) - } - } - return(df) -} - -#' @rdname error_conversion -#' @export -abs_to_rel <- function(df) { - - # Basic checks - checkmate::assert_class(df, "ASTR") - - # Find all error columns - error_cols <- colnames(df)[sapply(colnames(df), is_err_abs)] - - if (length(error_cols) == 0) { - warning("No error columns found. Returning unchanged.") - return(df) - } - - # Process all error columns - for (err_col in error_cols) { - base_name <- remove_suffix(err_col) - - if (!base_name %in% names(df)) { - next - } - # relative = (absolute / measured_value) - df[[err_col]] <- df[[err_col]] / df[[base_name]] - - # Set units to percent - units(df[[err_col]]) <- units::as_units("%") - } - - return(df) -} diff --git a/man/abs_to_rel.Rd b/man/abs_to_rel.Rd deleted file mode 100644 index 68b489f..0000000 --- a/man/abs_to_rel.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ASTR_error_conversion.R -\name{abs_to_rel} -\alias{abs_to_rel} -\title{Convert absolute errors to relative errors in ASTR object} -\usage{ -abs_to_rel(df) -} -\arguments{ -\item{df}{An ASTR object} -} -\value{ -An ASTR object with errors converted to relative -} -\description{ -Converts all absolute error columns to relative errors (\%). -} diff --git a/man/error_conversion.Rd b/man/error_conversion.Rd new file mode 100644 index 0000000..6f44435 --- /dev/null +++ b/man/error_conversion.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ASTR_conversion_error.R +\name{error_conversion} +\alias{error_conversion} +\alias{rel_to_abs} +\alias{abs_to_rel} +\title{Convert between relative and absolute analytical uncertainties} +\usage{ +rel_to_abs(df) + +abs_to_rel(df) +} +\arguments{ +\item{df}{An ASTR object} +} +\value{ +An ASTR object with converted analytical precision columns. The +unchanged input, if it does not contain columns of the respective +analytical precision type +} +\description{ +Convert relative to absolute analytical uncertainties and vice versa in ASTR +objects. Work only for objects of class \code{ASTR}. +} +\examples{ +test_file <- system.file("extdata", "test_data_input_good.csv", package = "ASTR") +arch <- read_ASTR(test_file, id_column = "Sample", context = 1:7) + +arch2 <- abs_to_rel(arch) + +arch3 <- rel_to_abs(arch2) + +# Conversion is lossless +all.equal(arch$`SiO2_errSD`, arch3$`SiO2_errSD`) + +} diff --git a/man/rel_to_abs.Rd b/man/rel_to_abs.Rd deleted file mode 100644 index fe7d732..0000000 --- a/man/rel_to_abs.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ASTR_error_conversion.R -\name{rel_to_abs} -\alias{rel_to_abs} -\title{Convert relative errors to absolute errors in ASTR object} -\usage{ -rel_to_abs(df) -} -\arguments{ -\item{df}{An ASTR object} -} -\value{ -An ASTR object with errors converted to absolute -} -\description{ -Converts all relative error columns to absolute errors using their -corresponding measured value columns. -} diff --git a/tests/testthat/_snaps/ASTR_basic.md b/tests/testthat/_snaps/ASTR_basic.md index e2aaf91..cd73e79 100644 --- a/tests/testthat/_snaps/ASTR_basic.md +++ b/tests/testthat/_snaps/ASTR_basic.md @@ -45,21 +45,21 @@ 12 ICP-MS 0.512991 2.04 0.8000 4.5 [wtP] 0.070 [wtP] 1.56 [wtP] 13 ICP-MS NA -0.24 -0.0504 3.2 [wtP] 0.040 [wtP] 3.64 [wtP] 14 ICP-MS NA -0.42 -0.0420 3.7 [wtP] 0.060 [wtP] 7.51 [wtP] - MgO Al2O3 SiO2 SiO2_errSD% P2O5 S - 1 0.77 [wtP] 3.92 [wtP] 31.63 [wtP] 4.30 [%] 0.15 [wtP] 2.57 [atP] - 2 0.55 [wtP] 3.46 [wtP] 29.06 [wtP] 2.88 [%] NA [wtP] NA [atP] - 3 0.33 [wtP] 5.87 [wtP] 30.50 [wtP] 5.71 [%] 0.11 [wtP] 3.68 [atP] - 4 0.76 [wtP] 4.33 [wtP] 25.73 [wtP] 2.22 [%] 0.23 [wtP] 2.76 [atP] - 5 0.52 [wtP] 4.17 [wtP] 43.50 [wtP] 4.65 [%] 0.40 [wtP] 0.57 [atP] - 6 0.58 [wtP] 3.58 [wtP] 33.83 [wtP] 3.67 [%] 0.29 [wtP] 0.61 [atP] - 7 0.64 [wtP] 5.60 [wtP] 33.81 [wtP] 3.00 [%] 0.43 [wtP] 0.77 [atP] - 8 0.59 [wtP] 4.47 [wtP] 26.39 [wtP] 2.87 [%] 0.62 [wtP] 3.04 [atP] - 9 0.53 [wtP] 3.73 [wtP] 21.18 [wtP] 3.44 [%] 0.24 [wtP] 3.93 [atP] - 10 0.70 [wtP] 6.27 [wtP] 31.73 [wtP] 6.12 [%] 0.28 [wtP] 0.21 [atP] - 11 0.46 [wtP] 2.91 [wtP] 16.91 [wtP] 3.73 [%] 0.22 [wtP] 3.57 [atP] - 12 0.58 [wtP] 3.60 [wtP] 28.49 [wtP] 4.89 [%] 0.19 [wtP] 1.95 [atP] - 13 0.88 [wtP] 5.24 [wtP] 38.04 [wtP] 10.12 [%] 0.30 [wtP] 1.35 [atP] - 14 0.84 [wtP] 3.97 [wtP] 31.67 [wtP] 4.30 [%] 0.18 [wtP] 0.83 [atP] + MgO Al2O3 SiO2 SiO2_errSD P2O5 S + 1 0.77 [wtP] 3.92 [wtP] 31.63 [wtP] 1.360090 [wtP] 0.15 [wtP] 2.57 [atP] + 2 0.55 [wtP] 3.46 [wtP] 29.06 [wtP] 0.836928 [wtP] NA [wtP] NA [atP] + 3 0.33 [wtP] 5.87 [wtP] 30.50 [wtP] 1.741550 [wtP] 0.11 [wtP] 3.68 [atP] + 4 0.76 [wtP] 4.33 [wtP] 25.73 [wtP] 0.571206 [wtP] 0.23 [wtP] 2.76 [atP] + 5 0.52 [wtP] 4.17 [wtP] 43.50 [wtP] 2.022750 [wtP] 0.40 [wtP] 0.57 [atP] + 6 0.58 [wtP] 3.58 [wtP] 33.83 [wtP] 1.241561 [wtP] 0.29 [wtP] 0.61 [atP] + 7 0.64 [wtP] 5.60 [wtP] 33.81 [wtP] 1.014300 [wtP] 0.43 [wtP] 0.77 [atP] + 8 0.59 [wtP] 4.47 [wtP] 26.39 [wtP] 0.757393 [wtP] 0.62 [wtP] 3.04 [atP] + 9 0.53 [wtP] 3.73 [wtP] 21.18 [wtP] 0.728592 [wtP] 0.24 [wtP] 3.93 [atP] + 10 0.70 [wtP] 6.27 [wtP] 31.73 [wtP] 1.941876 [wtP] 0.28 [wtP] 0.21 [atP] + 11 0.46 [wtP] 2.91 [wtP] 16.91 [wtP] 0.630743 [wtP] 0.22 [wtP] 3.57 [atP] + 12 0.58 [wtP] 3.60 [wtP] 28.49 [wtP] 1.393161 [wtP] 0.19 [wtP] 1.95 [atP] + 13 0.88 [wtP] 5.24 [wtP] 38.04 [wtP] 3.849648 [wtP] 0.30 [wtP] 1.35 [atP] + 14 0.84 [wtP] 3.97 [wtP] 31.67 [wtP] 1.361810 [wtP] 0.18 [wtP] 0.83 [atP] CaO TiO2 MnO FeOtot FeOtot_err2SD ZnO 1 2.11 [wtP] 0.52 [wtP] 0.20 [wtP] 43.83 [wtP] 4.120 [wtP] 5.64 [%] 2 2.34 [wtP] 0.49 [wtP] 0.54 [wtP] 51.02 [wtP] 3.890 [wtP] 4.07 [%] diff --git a/tests/testthat/test-error_conversion.R b/tests/testthat/test-error_conversion.R deleted file mode 100644 index d57ee31..0000000 --- a/tests/testthat/test-error_conversion.R +++ /dev/null @@ -1,63 +0,0 @@ -test_input <- suppressWarnings( - read_ASTR( - system.file("extdata", "test_data_input_good.csv", package = "ASTR"), - id_column = "Sample", - context = c("Lab no.", "Site", "latitude", "longitude", "Type", "method_comp") - ) -) - -# --- rel_to_abs --- - -test_that("rel_to_abs: numeric conversion is correct", { - result <- rel_to_abs(test_input) - relative_raw <- as.numeric(test_input[["SiO2_errSD%"]]) - value_raw <- as.numeric(test_input[["SiO2"]]) - expect_equal(as.numeric(result[["SiO2_errSD%"]]), - (relative_raw / 100) * value_raw, tolerance = 1e-6) -}) - -test_that("rel_to_abs: error col units match base col after conversion", { - result <- rel_to_abs(test_input) - expect_equal(as.character(units(result[["SiO2_errSD%"]])), - as.character(units(result[["SiO2"]]))) -}) - -test_that("rel_to_abs: base col and class unchanged", { - result <- rel_to_abs(test_input) - expect_equal(as.numeric(result[["SiO2"]]), as.numeric(test_input[["SiO2"]])) - expect_s3_class(result, "ASTR") -}) - -test_that("rel_to_abs: NA in base col produces NA in error col, no crash", { - expect_no_error(result <- rel_to_abs(test_input)) - na_rows <- is.na(as.numeric(test_input[["SiO2"]])) - expect_true(all(is.na(as.numeric(result[["SiO2_errSD%"]])[na_rows]))) -}) - -test_that("rel_to_abs: warns when no relative error cols present", { - df_no_err <- test_input[, !grepl("%$", colnames(test_input))] - class(df_no_err) <- class(test_input) - expect_warning(rel_to_abs(df_no_err), "No error columns found") -}) - -test_that("rel_to_abs: errors on non-ASTR input", { - expect_error(rel_to_abs(data.frame(x = 1))) -}) - -# --- abs_to_rel --- - -test_that("abs_to_rel: numeric conversion is correct", { - abs_input <- rel_to_abs(test_input) - result <- abs_to_rel(abs_input) - expect_equal(as.numeric(result[["SiO2_errSD%"]]), - as.numeric(test_input[["SiO2_errSD%"]]), tolerance = 1e-6) -}) - -test_that("abs_to_rel: error col units are percent after conversion", { - result <- abs_to_rel(rel_to_abs(test_input)) - expect_equal(as.character(units(result[["SiO2_errSD%"]])), "%") -}) - -test_that("abs_to_rel: errors on non-ASTR input", { - expect_error(abs_to_rel(data.frame(x = 1))) -}) diff --git a/tests/testthat/test_ASTR_column_selection.R b/tests/testthat/test_ASTR_column_selection.R index 4395949..764a43d 100644 --- a/tests/testthat/test_ASTR_column_selection.R +++ b/tests/testthat/test_ASTR_column_selection.R @@ -34,12 +34,12 @@ test_that("column selection based on ASTR column types", { ) expect_all_true( colnames(get_error_columns(test_input)) == - c("ID", "d65Cu_err2SD", "SiO2_errSD%", "FeOtot_err2SD", "206Pb/204Pb_err2SD", + c("ID", "d65Cu_err2SD", "SiO2_errSD", "FeOtot_err2SD", "206Pb/204Pb_err2SD", "207Pb/204Pb_err2SD", "208Pb/204Pb_err2SD", "207Pb/206Pb_err2SD", "208Pb/206Pb_err2SD" ) ) expect_all_true( colnames(get_unit_columns(test_input, c("ng/g", "µg/ml", "%"))) == - c("ID", "SiO2_errSD%", "ZnO", "Ag", "Sn") + c("ID", "ZnO", "Ag", "Sn") ) }) diff --git a/tests/testthat/test_conversion_error.R b/tests/testthat/test_conversion_error.R new file mode 100644 index 0000000..4ec8a4b --- /dev/null +++ b/tests/testthat/test_conversion_error.R @@ -0,0 +1,77 @@ +test_input <- suppressWarnings( + read_ASTR( + system.file("extdata", "test_data_input_good.csv", package = "ASTR"), + id_column = "Sample", + context = c("Lab no.", "Site", "latitude", "longitude", "Type", "method_comp") + ) +) + +# --- abs_to_rel --- + +result <- abs_to_rel(test_input) + +test_that("abs_to_rel: numeric conversion is correct", { + expect_equal(as.numeric(result[["d65Cu_err2SD%"]]), + as.numeric(test_input[["d65Cu_err2SD"]] / test_input[["d65Cu"]] * 100), tolerance = 1e-6) +}) + +test_that("abs_to_rel: error col units are percent after conversion", { + expect_equal(as.character(units(result[["SiO2_errSD%"]])), "%") +}) + +test_that("abs_to_rel: input returned unchanged if no absolute error cols present", { + df_no_err <- test_input[, !is_err_abs(colnames(test_input))] + expect_identical(abs_to_rel(df_no_err), df_no_err) +}) + +test_that("abs_to_rel: errors on non-ASTR input", { + expect_error(abs_to_rel(data.frame(x = 1))) +}) + +# --- rel_to_abs --- + +result2 <- rel_to_abs(result) + +test_that("rel_to_abs: numeric conversion is correct", { + expect_equal(as.numeric(result2[["SiO2_errSD"]]), + as.numeric(result[["SiO2_errSD%"]] * result[["SiO2"]] / 100), tolerance = 1e-6) +}) + +test_that("rel_to_abs: column renamed to absolute error", { + expect_all_false(grepl("_err.{3}%", colnames(result2), perl = TRUE)) +}) + +test_that("rel_to_abs: error col units match base col after conversion", { + expect_equal(units(result2[["SiO2_errSD"]]), + units(result2[["SiO2"]])) +}) + +test_that("rel_to_abs: base col and class unchanged", { + expect_equal(result2[["SiO2"]], result[["SiO2"]]) + expect_s3_class(result2, "ASTR") +}) + +test_that("rel_to_abs: input returned unchanged if no relative error cols present", { + df_no_err <- test_input[, !is_err_percent(colnames(test_input))] + expect_identical(rel_to_abs(df_no_err), df_no_err) +}) + +# test not valid because column does no include NAs +#test_that("rel_to_abs: NA in base col produces NA in error col, no crash", { +# na_rows <- is.na(as.numeric(test_input[["SiO2"]])) +# expect_true(all(is.na(as.numeric(result[["SiO2_errSD"]])[na_rows]))) +#}) + +test_that("rel_to_abs: errors on non-ASTR input", { + expect_error(rel_to_abs(data.frame(x = 1))) +}) + +# --- conversion is reversible --- + +error_reversed <- rel_to_abs(abs_to_rel(test_input)) + +test_that("error conversion is reversible and unitless values handled correctly", { + expect_equal(test_input[["SiO_err2SD"]], error_reversed[["SiO2_err2SD"]]) + expect_equal(test_input[["d65Cu_err2SD"]], error_reversed[["d65Cu_err2SD"]]) + expect_equal(test_input[["206Pb/204Pb_err2SD"]], error_reversed[["206Pb/204Pb_err2SD"]]) +}) diff --git a/vignettes/ASTR.showcase.Rmd b/vignettes/ASTR.showcase.Rmd index 511eb46..c0429f9 100644 --- a/vignettes/ASTR.showcase.Rmd +++ b/vignettes/ASTR.showcase.Rmd @@ -23,6 +23,7 @@ set.seed(123) data <- data.frame( "Group" = c(rep("A", 5), rep("B", 5)), "Cu_wt%" = round(runif(10, 85, 95), 1), + "Cu_err2SD%" = round(runif(10, 0.2, 0.3), 2), "Sn_wt%" = round(runif(10, 5, 10), 1), "As_wt%" = round(runif(10, 1.5, 2.5), 2), "Sb_wt%" = round(runif(10, 0.5, 1), 2), @@ -58,7 +59,7 @@ Note how the column headers are organised according to the ASTR conventions. Thi We can now perform the reading process. As we are working with mock-up data in an R vignette, we do not read from the file system. We only need to call an essential internal function of `read_ASTR()`: `as_ASTR()`. It turns R data.frames to `ASTR` objects. If we would start from an Excel file we would call `read_ASTR()` instead. -To do this in practice we not only have to submit our `data` to `as_ASTR()`, but also set two other arguments: 1. We have to define one of the columns as the ID column with `id_column`, and 2. we explicitly have to mark any column providing contextual information with `context` (i.e. no analytical values, cf. [ASTR schema: Implementation](VG.ASTR.Schema.Implementation.html)). +To do this in practice, we not only have to submit our `data` to `as_ASTR()`, but also set two other arguments: 1. We have to define one of the columns as the ID column with `id_column`, and 2. we explicitly have to mark any column providing contextual information with `context` (i.e. no analytical values, cf. [ASTR schema: Implementation](VG.ASTR.Schema.Implementation.html)). ```{r echo=TRUE} data <- as_ASTR(data, id_column = "Group", context = c("Group")) @@ -80,13 +81,13 @@ This summary of where to find the missing values comes in handy for large datase In our case, the dataset is fine. We might want to check on the missing values in the copper isotope data because apparently there was something wrong with the Excel file. In case of the lead isotope data, however, they are intentional (they were not measured for samples from group A). -Having a closer look on the imported data set shows, that ASTR did one more thing: +Having a closer look on the imported data set shows, that ASTR did two more things: ```{r echo=TRUE} data ``` -Our relative unit "ppm" was converted into SI-units and the values now have a measurement unit R can understand (note that the unit `wt%` is written as `wtP`)^[The %-sign can not be used in custom units]: +The relative analytical precisions of the copper concentrations were automatically converted to absolute analytical precisions, facilitating, e.g., calculations and plotting. And our relative unit "ppm" was converted into SI-units; the values now have a measurement unit R can understand (note that the unit `wt%` is written as `wtP`)^[The %-sign can not be used in custom units]: ``` {r} units(data$Ag[1]) diff --git a/vignettes/VG.ASTR.Schema.Implementation.Rmd b/vignettes/VG.ASTR.Schema.Implementation.Rmd index 8804042..2fd25f0 100644 --- a/vignettes/VG.ASTR.Schema.Implementation.Rmd +++ b/vignettes/VG.ASTR.Schema.Implementation.Rmd @@ -43,8 +43,10 @@ This vignette outlines how the conventions defined in [the ASTR schema](VG.ASTRs * One column must be specified as `ID` column during import to provide a unique identifier for each line in the dataset. To ensure uniqueness of its values, `_1`, `_2`, ... `_n` will be added to non-unique values. The original column is preserved. * Columns without a column header will be removed during import. * The following notations will be automatically identified and replaced with `NA`, unless you explicitly define other values in `read_ASTR()`: `NA`, `N.A.`, `N/A`, `na`, `n/a`, `-`, and `n.d.`. Values containing common excel error messages (*#DIV/0!*, *#VALUE!*, *#REF!*, *#NAME?*, *#NUM!*, *#N/A*, and *#NULL!*) are also replaced with `NA` by default. +* Relative analytical precisions will be converted into absolute analytical precisions. If relative analytical precisions are required, conversion is possible with `abs_to_rel()`. This avoids potential conflicts in handling element concentrations in per cent. * Units will be removed from column names because {units} stores them in the column attributes. This allows for clean column names and therefore of e.g. axis labels in plots. They can be "shifted" from the column attributes back to the column names by `remove_units()` with `recover_unit_names = TRUE`. + # Units * The package relies on `{units}`, which uses the [udunits](https://www.unidata.ucar.edu/software/udunits) C library, for handling all SI units (e.g. *µg/ml*) and relative concentration units (e.g. *ppm(m/V)*). If the mixture type is not specified, *m/m* is assumed. Non-SI units *wt%* and *at%* were defined as `wtP` and `atP`, respectively. From a16cb2fe073da30791a752ca0f7d3cfdb882c03e Mon Sep 17 00:00:00 2001 From: Thomas Rose Date: Mon, 25 May 2026 14:26:03 -0400 Subject: [PATCH 10/10] include error conversion functions --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index f99197d..9ffedbd 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -23,6 +23,7 @@ reference: Functions for creating and handling ASTR objects. contents: - ASTR + - error_conversion - title: Data subsetting desc: > Pre-compiled lists for easier subsetting of data