diff --git a/DESCRIPTION b/DESCRIPTION index a696d37..15ae402 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -53,7 +53,6 @@ Imports: tibble, tidyselect, units, - robCompositions, ggplot2, ks, units, diff --git a/NAMESPACE b/NAMESPACE index ae379f9..9c2f472 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -32,6 +32,7 @@ export(pointcloud_distribution) export(read_archchem) export(remove_units) export(stacey_kramers_1975) +export(standard_sample_bracketing) export(unify_concentration_unit) export(validate) importFrom(geometry,convhulln) diff --git a/R/ASTR_PbIso_AgeModels.R b/R/ASTR_PbIso_AgeModels.R index 227227d..efe9f05 100644 --- a/R/ASTR_PbIso_AgeModels.R +++ b/R/ASTR_PbIso_AgeModels.R @@ -46,20 +46,20 @@ #' @export #' #' @references Albarède, F. and Juteau, M. (1984) Unscrambling the lead model -#' ages. Geochimica et Cosmochimica Acta 48(1), pp. 207–212. +#' ages. Geochimica et Cosmochimica Acta 48(1), pp. 207-212. #' . #' #' Albarède, F., Desaulty, A.-M. and Blichert-Toft, J. (2012) A geological #' perspective on the use of Pb isotopes in Archaeometry. Archaeometry 54, pp. -#' 853–867. . +#' 853-867. . #' #' Cumming, G.L. and Richards, J.R. (1975) Ore lead isotope ratios in a #' continuously changing earth. Earth and Planetary Science Letters 28(2), pp. -#' 155–171. . +#' 155-171. . #' #' Stacey, J.S. and Kramers, J.D. (1975) Approximation of terrestrial lead #' isotope evolution by a two-stage model. Earth and Planetary Science Letters -#' 26(2), pp. 207–221. +#' +#' @returns If `display_details = FALSE`, a data frame with the average values +#' plus standard deviation (SD). Otherwise a list with two data frames. +#' +#' @export +#' +#' @examples +#' data <- data.frame( +#' ID = c("Std", "Sample_A", "Std", "Sample_A", "Std", "Sample_A", "Std"), +#' values = c(16.928, 18.641, 16.932, 18.643, 16.935, 18.642, 16.938), +#' error = c(0.05, 0.02, 0.05, 0.06, 0.03, 0.04, 0.02) +#' ) +#' +#' result <- ASTR::standard_sample_bracketing( +#' df = data, +#' id_values = "values", +#' id_error = "error", +#' sd_input = 2, +#' pos = 1, +#' notation = "delta", +#' display_details = TRUE +#' ) +#' +standard_sample_bracketing <- function( + df, + id_col = "ID", + id_values = "values", + id_error = "error", + std = "Std", + pos = 1, + notation = c("ratio", "delta", "epsilon"), + sd_input = 1, + weight_std = c(0.5, 0.5), + display_details = FALSE +) { + # Check there are no empty values in header nor id_std + if (std == "") { + stop("You need to assign the ID of the standard.") + } + + if (!(id_values %in% colnames(df))) { + stop("The column name for the measured values is not included in the provided data frame.") + } + + if (!(id_error %in% colnames(df))) { + stop("The column name for the measured values is not included in the provided data frame.") + } + + notation <- match.arg(notation) + + # Check of weight values + + checkmate::assert_vector(weight_std, strict = TRUE, len = 2, any.missing = FALSE) + + if (sum(weight_std) != 1) { + stop("The sum of the weights must be 1.") + } + + # prepare variables + df <- df[c(id_col, id_values, id_error)] # data frame with relevant columns for computation + nr <- nrow(df) # count number of rows in the data frame + + sample_names <- sample_results <- error_sample_list <- c() + + # SSB calculation + + while (pos + 1 <= nr) { + # iterates over the whole data frame, it starts with the cycles + std_opening <- df[pos, 2] + error_std_opening <- df[pos, 3] + cycle_end <- cycle_start <- pos + 1 # move the index to the first sample + + while (df[cycle_end, 1] != std) { # Find the second (closing) standard bracket from the cycle + cycle_end <- cycle_end + 1 + } + + std_closing <- df[cycle_end, 2] + error_std_closing <- df[cycle_end, 3] + + std_mean_weighted <- (weight_std[1] * std_opening) + (weight_std[2] * std_closing) # calculate weighted mean + + while (cycle_start < cycle_end) { # run the samples within the cycle + sample_current <- df[cycle_start, 1] + error_measurement <- df[cycle_start, 3] + + total_error <- error_measurement * sqrt( + (weight_std[1] * error_std_opening)^2 + (weight_std[2] * error_std_closing)^2 + ) + + if (!is.na(sample_current) && sample_current != "") { + sample_measurement <- df[cycle_start, 2] + ssb <- sample_measurement / std_mean_weighted + + switch(notation, + delta = { + ssb <- (ssb - 1) * 1000 + }, + epsilon = { + ssb <- (ssb - 1) * 10000 + }, + { + ssb + } + ) + + sample_names <- append(sample_names, sample_current) + sample_results <- append(sample_results, ssb) + error_sample_list <- append(error_sample_list, total_error) + } + cycle_start <- cycle_start + 1 + } + pos <- cycle_start + } + + # for some reason, they are a list rather a vector, so turning them into vector + sample_names <- unlist(sample_names) + sample_results <- unlist(sample_results) + error_sample_list <- unlist(error_sample_list) + + # Combine calculated values into data frame and order them by sample name + results <- data.frame(ID = sample_names, SSB = sample_results, Error_err2SD = error_sample_list) + + # Calculate errors and averages for each sample + summary <- results %>% + dplyr::group_by(.data$ID) %>% + dplyr::summarise( + Mean = format(signif(mean(.data$SSB), 4), nsmall = 4), + SD = format(signif(sd_input * stats::sd(.data$SSB), 4), nsmall = 4) + ) + colnames(results)[2] <- colnames(summary)[2] <- paste(id_values, notation, sep = "_") + + if (sd_input != 1) { + colnames(summary)[3] <- paste0(id_values, "_err", sd_input, "SD") + } else { + colnames(summary)[3] <- paste0(id_values, "_errSD") + } + + if (display_details == TRUE) { + result <- list(results, summary) + } else { + result <- summary + } + result +} diff --git a/R/archchem_basic.R b/R/archchem_basic.R index 5e9c8cb..9641eb1 100644 --- a/R/archchem_basic.R +++ b/R/archchem_basic.R @@ -222,7 +222,7 @@ read_archchem <- function( answer <- readline("Package `readxl` required to import Excel files. Do you want to install it now? [Y/n]: ") if (tolower(answer) %in% c("yes", "y")) { - install.packages("readxl") + utils::install.packages("readxl") } else { stop("Please import your data in another file format or install 'readxl' manually.") } diff --git a/data-raw/FlowerDataProcessing-SBB-test-01.xlsx b/data-raw/FlowerDataProcessing-SBB-test-01.xlsx new file mode 100644 index 0000000..f40c491 Binary files /dev/null and b/data-raw/FlowerDataProcessing-SBB-test-01.xlsx differ diff --git a/data-raw/FlowerDataProcessing-SBB-test-03.xlsx b/data-raw/FlowerDataProcessing-SBB-test-03.xlsx new file mode 100644 index 0000000..16b97c5 Binary files /dev/null and b/data-raw/FlowerDataProcessing-SBB-test-03.xlsx differ diff --git a/data-raw/FlowerDataProcessing-SBB-test-04.csv b/data-raw/FlowerDataProcessing-SBB-test-04.csv new file mode 100644 index 0000000..a8d49a9 --- /dev/null +++ b/data-raw/FlowerDataProcessing-SBB-test-04.csv @@ -0,0 +1,18 @@ +ID;Isotope_data;Isotope_data_Error;SBB_linear;SBB_weighted;;;;;;;; +Std;0,447093;0,000004;;;;;;;;;; +Sample-01;0,446465;0,000003;0,9985828;0,9985854;;;;;;;; +Sample-02;0,446494;0,000003;0,9986474;0,9986500;;;;;;;; +Sample-03;0,446475;0,000003;0,9986038;0,9986064;;;;;;;; +Std;0,447105;0,000004;;;;;;;;;; +Sample-04;0,446816;0,000003;0,99936;0,99936;;;;;;;; +Sample-05;0,446494;0,000003;0,99864;0,99864;;;;;;;; +Sample-06;0,446465;0,000003;0,99858;0,99858;;;;;;;; +Std;0,447098;0,000003;;;;;;;;;; +Sample-09;0,446503;0,000003;0,99869;0,99869;;;;;;;; +Sample-08;0,446734;0,000004;0,99921;0,99920;;;;;;;; +Sample-07;0,446799;0,000003;0,99935;0,99935;;;;;;;; +Std;0,447080;0,000004;;;;;;;;;; +;;;;;;;;;;;; +;;;;;;;;;;;; +;;;;;;;;;;;; +;;;;;;;;;;;; diff --git a/data-raw/FlowerDataProcessing-SBB-test-04.xlsx b/data-raw/FlowerDataProcessing-SBB-test-04.xlsx new file mode 100644 index 0000000..fed723b Binary files /dev/null and b/data-raw/FlowerDataProcessing-SBB-test-04.xlsx differ diff --git a/data-raw/REY_PAAS-C1-normalization.xls b/data-raw/REY_PAAS-C1-normalization.xls new file mode 100644 index 0000000..239da63 Binary files /dev/null and b/data-raw/REY_PAAS-C1-normalization.xls differ diff --git a/dev_guide/drafts/bracketing/bracketing-tidyverse.R b/dev_guide/drafts/bracketing/bracketing-tidyverse.R new file mode 100644 index 0000000..c256bc5 --- /dev/null +++ b/dev_guide/drafts/bracketing/bracketing-tidyverse.R @@ -0,0 +1,114 @@ + + +# here is the function to do SSB using the tidyverse +standard_sample_bracketing <- function( + data = data, + sample_id = "ID", + values = "value", + standard_name = "Std", + multiplier = 1000, + start_at_row = 1 +) { + + library(dplyr) + + # start where the user says the data starts + data <- + data |> + slice(start_at_row:n()) |> + # we need a unique row number to rejoin the data later + mutate(original_row_id = row_number()) + + sample_id_sym <- sym(sample_id) # turn string into symbol + values_sym <- sym(values) # turn string into symbol + + # Find positions of all standard rows + std_rows <- which(data[[sample_id_sym]] == standard_name) + + # build overlapping Std–Sample–Std groups, we duplicate + # some stds to have distinct groups that we can process + groups <- map2( + std_rows[-length(std_rows)], + std_rows[-1], + .f = function(start, end) { + idx <- which(std_rows[-length(std_rows)] == start) + data[start:end, ] |> + mutate(group_id = paste0("group_", sprintf("%03d", idx))) + } + ) + + df_grouped <- bind_rows(groups) + + # for eachStd–Sample–Std group, take the first std and the last std + # and compute mean and add in a new column containing this mean + # for each group + df_grouped_with_means <- + df_grouped |> + group_by(group_id) %>% + # keep only Std rows + filter({{sample_id_sym}} == standard_name) %>% + # take only first and last Std in each group + slice(c(1, n())) %>% + summarise( + mean_std = mean({{values_sym}}, na.rm = TRUE), + .groups = "drop" + ) |> + right_join(df_grouped) + + # for each group, take the sample value, divide by the mean std + # value -1, then multiple by the multiplier + df_grouped_with_means_ssb <- + df_grouped_with_means |> + # Exclude the standards from the final calculation + filter({{sample_id_sym}} != standard_name) |> + mutate( + # output is per mil + ssb_value = ({{values_sym}} / mean_std - 1) * multiplier + ) |> + # Select only the ID and the result to avoid duplicate columns + select(original_row_id, ssb_value) + + # Join the calculated values back to the original data frame + final_data <- data |> + left_join(df_grouped_with_means_ssb, + by = "original_row_id") |> + # Remove the temporary ID column + select(-original_row_id) + + return(final_data) + +} + +# Test the function with some data ------------------------------------------- + +library(readxl) +library(tidyverse) + +# import the data +df <- read_excel("data-raw/ULB_Cu_20190903-test 2.xlsx", + sheet = "03092019", + skip = 2) + +# subset just the sequences of samples to apply SSB +subset_to_bracket <- + df |> + slice(13:45, 68:104) + +# apply the function to create a new data frame with the new column +x <- +standard_sample_bracketing(data = subset_to_bracket, + sample_id = "Sample Name...1", + standard_name = "Cu/Zn in house 25ppb", + values = "Cu65/63 corr 68/66") +x + +# take a look and compare the function result to the Excel result +x |> + select(`Sample Name...1`, + `Cu65/63 corr 68/66`, + `δ65Cu`, + ssb_value) |> + mutate(all_equal = all.equal(`δ65Cu`,ssb_value )) |> View() + + + diff --git a/inst/extdata/test_data_bracketing.xlsx b/inst/extdata/test_data_bracketing.xlsx new file mode 100644 index 0000000..81d97e6 Binary files /dev/null and b/inst/extdata/test_data_bracketing.xlsx differ diff --git a/man/ASTR-package.Rd b/man/ASTR-package.Rd index f79bd93..4d53d87 100644 --- a/man/ASTR-package.Rd +++ b/man/ASTR-package.Rd @@ -16,32 +16,32 @@ Useful links: } \author{ -\strong{Maintainer}: Rose Thomas \email{roseth@posteo.com} (\href{https://orcid.org/0000-0002-8186-3566}{ORCID}) +\strong{Maintainer}: Thomas Rose \email{roseth@posteo.com} (\href{https://orcid.org/0000-0002-8186-3566}{ORCID}) Authors: \itemize{ - \item Acevedo Mejia Andrea (\href{https://orcid.org/0009-0002-7441-1737}{ORCID}) - \item Artioli Gilberto (\href{https://orcid.org/0000-0002-8693-7392}{ORCID}) - \item Becerra María Florencia (\href{https://orcid.org/0000-0001-6302-7452}{ORCID}) - \item Benfer Adam Kevin (\href{https://orcid.org/0009-0004-1253-1068}{ORCID}) - \item Bellemère Lisa - \item Birch Thomas Edward (\href{https://orcid.org/0000-0002-4568-9767}{ORCID}) - \item Desai Karan (\href{https://orcid.org/0009-0008-8224-8435}{ORCID}) - \item d'Imporzano Paolo - \item Eshel Tzilla (\href{https://orcid.org/0000-0003-0976-0877}{ORCID}) - \item Gentile Valerio (\href{https://orcid.org/0000-0003-4402-2730}{ORCID}) - \item Klein Sabine (\href{https://orcid.org/0000-0002-3939-4428}{ORCID}) - \item Klesner Catherine (\href{https://orcid.org/0000-0002-2264-9383}{ORCID}) - \item Murphy Kathryn (\href{https://orcid.org/0000-0002-5906-7299}{ORCID}) - \item Marwick Ben (\href{https://orcid.org/0000-0001-7879-4531}{ORCID}) - \item Merkel Stephen (\href{https://orcid.org/0000-0001-8730-6923}{ORCID}) - \item Pelizzari Marco Daniele - \item Rodler Alexandra (\href{https://orcid.org/0000-0002-4087-7160}{ORCID}) - \item Sabatini Benjamin (\href{https://orcid.org/0000-0002-4199-0253}{ORCID}) - \item Schmid Clemens (\href{https://orcid.org/0000-0003-3448-5715}{ORCID}) - \item van der Meulen-van der Veen Berber (\href{https://orcid.org/0000-0001-5297-0269}{ORCID}) - \item Wang Chen (\href{https://orcid.org/0009-0007-1851-2388}{ORCID}) - \item Westner Katrin Julia (\href{https://orcid.org/0000-0001-5529-1165}{ORCID}) + \item Andrea Acevedo Mejia (\href{https://orcid.org/0009-0002-7441-1737}{ORCID}) + \item Gilberto Artioli (\href{https://orcid.org/0000-0002-8693-7392}{ORCID}) + \item María Florencia Becerra (\href{https://orcid.org/0000-0001-6302-7452}{ORCID}) + \item Adam Kevin Benfer (\href{https://orcid.org/0009-0004-1253-1068}{ORCID}) + \item Lisa Bellemère + \item Thomas Edward Birch (\href{https://orcid.org/0000-0002-4568-9767}{ORCID}) + \item Karan Desai (\href{https://orcid.org/0009-0008-8224-8435}{ORCID}) + \item Paolo d'Imporzano + \item Tzilla Eshel (\href{https://orcid.org/0000-0003-0976-0877}{ORCID}) + \item Valerio Gentile (\href{https://orcid.org/0000-0003-4402-2730}{ORCID}) + \item Sabine Klein (\href{https://orcid.org/0000-0002-3939-4428}{ORCID}) + \item Catherine Klesner (\href{https://orcid.org/0000-0002-2264-9383}{ORCID}) + \item Kathryn Murphy (\href{https://orcid.org/0000-0002-5906-7299}{ORCID}) + \item Ben Marwick (\href{https://orcid.org/0000-0001-7879-4531}{ORCID}) + \item Stephen Merkel (\href{https://orcid.org/0000-0001-8730-6923}{ORCID}) + \item Marco Daniele Pelizzari + \item Alexandra Rodler (\href{https://orcid.org/0000-0002-4087-7160}{ORCID}) + \item Benjamin Sabatini (\href{https://orcid.org/0000-0002-4199-0253}{ORCID}) + \item Clemens Schmid (\href{https://orcid.org/0000-0003-3448-5715}{ORCID}) + \item Berber van der Meulen-van der Veen (\href{https://orcid.org/0000-0001-5297-0269}{ORCID}) + \item Chen Wang (\href{https://orcid.org/0009-0007-1851-2388}{ORCID}) + \item Katrin Julia Westner (\href{https://orcid.org/0000-0001-5529-1165}{ORCID}) } } diff --git a/man/age_models.Rd b/man/age_models.Rd index 6b5154b..dc2ab31 100644 --- a/man/age_models.Rd +++ b/man/age_models.Rd @@ -110,18 +110,18 @@ stacey_kramers_1975(df) } \references{ Albarède, F. and Juteau, M. (1984) Unscrambling the lead model -ages. Geochimica et Cosmochimica Acta 48(1), pp. 207–212. +ages. Geochimica et Cosmochimica Acta 48(1), pp. 207-212. \url{https://dx.doi.org/10.1016/0016-7037(84)90364-8}. Albarède, F., Desaulty, A.-M. and Blichert-Toft, J. (2012) A geological perspective on the use of Pb isotopes in Archaeometry. Archaeometry 54, pp. -853–867. \url{https://doi.org/10.1111/j.1475-4754.2011.00653.x}. +853-867. \url{https://doi.org/10.1111/j.1475-4754.2011.00653.x}. Cumming, G.L. and Richards, J.R. (1975) Ore lead isotope ratios in a continuously changing earth. Earth and Planetary Science Letters 28(2), pp. -155–171. \url{https://dx.doi.org/10.1016/0012-821X(75)90223-X}. +155-171. \url{https://dx.doi.org/10.1016/0012-821X(75)90223-X}. Stacey, J.S. and Kramers, J.D. (1975) Approximation of terrestrial lead isotope evolution by a two-stage model. Earth and Planetary Science Letters -26(2), pp. 207–221. + 1 BIL-1/1 0807 0.9994 7.441e-05 + 2 BIL-2/1 1107 0.9992 6.544e-06 + 3 BIL-4/1 0807 0.9980 2.823e-05 + 4 BIL-4/1A 1107 0.9982 4.09e-05 + 5 BIL-4/2 0807 0.9980 2.031e-05 + 6 BIL-4/2A 1107 0.9986 2.996e-05 + 7 BIL-4/2A 1107 bis 0.9986 NA + 8 BIL-5/1 0807 0.9985 2.837e-05 + 9 BIL-5/1A 1107 0.9987 1.526e-05 + 10 BIL-5/2 0807 0.9986 0.000749 + 11 BIL-5/2A 0807 0.9979 3.235e-05 + 12 BIL-5/2A 1107 0.9979 NA + diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R index 6e5e215..4d9ce08 100644 --- a/tests/testthat/setup.R +++ b/tests/testthat/setup.R @@ -2,7 +2,12 @@ # Additional packages to load for testing or setting up the test environment +library(tibble) +library(dplyr) +library(ggplot2) library(vdiffr) +library(readxl) +library(units) # reference data sets diff --git a/tests/testthat/test_standard_sample_bracketing.R b/tests/testthat/test_standard_sample_bracketing.R new file mode 100644 index 0000000..717319e --- /dev/null +++ b/tests/testthat/test_standard_sample_bracketing.R @@ -0,0 +1,27 @@ +# tests + +test_input <- suppressWarnings( + readxl::read_excel( + system.file("extdata", + "test_data_bracketing.xlsx", + package = "ASTR"), + sheet = "03092019", + skip = 2 + ) +) + +# subset just the sequences of samples to apply SSB +subset_to_bracket <- + test_input %>% + slice(13:45, 68:104) + +test_that("standard sample bracketing works as expected", { + expect_snapshot({ + # turn to data.frame to render the entire table + standard_sample_bracketing(subset_to_bracket, + id_col = "Sample Name...1", + id_values = "Cu65/63 corr 68/66", + id_error = "Error...3", + std = "Cu/Zn in house 25ppb") + }) +}) diff --git a/vignettes/Bracketing_vignette.Rmd b/vignettes/Bracketing_vignette.Rmd new file mode 100644 index 0000000..b1c3bed --- /dev/null +++ b/vignettes/Bracketing_vignette.Rmd @@ -0,0 +1,276 @@ +--- +title: "Standard-Sample-Bracketing" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Standard-Sample-Bracketing} # must match file name! + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +```{r setup} +library(ASTR) +``` + +# Introduction + +This vignette introduces the `standard_sample_bracketing()` function. + +Standard sample bracketing (SSB) is a calibration method used in mass spectrometry to correct for instrumental mass bias by measuring a standard solution with known isotopic composition immediately before and after each unknown sample. + +## Why SSB is needed? + +#### Mass Bias + +During isotope ratio measurements using MC-ICP-MS or TIMS, the instrument introduces mass bias (also known as mass fractionation). + +There are many causes of mass bias, for example: + +1. Lighter isotopes are more easily ionized in the plasma (MC-ICP-MS) or during thermal emission (TIMS), leading to slightly higher signal intensity. + +2. Under identical accelerating voltages, lighter ions acquire higher velocities ($v = \sqrt{2qV/m}$). They pass more efficiently through ion lenses and slits, while heavier ions are transmitted less efficiently. + +3. Faraday cups or ion counters may have small differences in sensitivity for different masses, causing unequal signal amplification. + +The following table shows examples of true and measured Pb isotope ratios: + +| True ratio | Measured ratio | +|:----------------------------------------------:|:--------------:| +| $^{206}\mathrm{Pb}/^{204}\mathrm{Pb}$ = 16.941 | 16.928 | +| $^{207}\mathrm{Pb}/^{204}\mathrm{Pb}$ = 15.549 | 15.530 | + +These effects cause a systematic fractionation of isotopes, typically enriching lighter isotopes in the measured signal. As a result, the measured isotope ratio deviates slightly from its true value. + +#### Mass bias Drifts Over Time + +In MC-ICP-MS and TIMS analyses, instrumental conditions vary slightly with time. Plasma temperature, gas flow, lens voltages, and detector sensitivity all change gradually during a measurement sequence. + +These variations alter the ionization efficiency and transmission of light versus heavy isotopes, causing the apparent isotope ratios to drift. As a result, a single calibration at the beginning of the run cannot fully correct the bias for later samples. + +The following table shows examples of the drifts over time: + +| Time | True ratio | Measured | Drift (%) | +|:-----:|:----------:|:--------:|:---------:| +| 09:00 | 16.941 | 16.928 | –0.08% | +| 09:10 | 16.941 | 16.930 | –0.06% | +| 09:20 | 16.941 | 16.933 | –0.05% | + +To overcome this, the **SSB** technique is used, in which a known standard is measured before and after each sample to dynamically correct for time-dependent mass bias. + +# Principle of SSB + +The core concept of **SSB** is that each sample measurement is bracketed by two standard measurements. The average of the two standards is used to correct for instrumental mass bias drift. + +A typical analytical sequence follows this pattern: + +Std1 → Sample A → Std2 → Sample B → Std3 → Sample C → Std4 → ... + +In this sequence, each **standard (Std)** is a reference material with a known true isotope ratio (e.g., *NIST SRM 981*). Each **sample (Sample)** has an unknown isotope ratio that needs to be corrected. + +Because instrumental drift is generally slow and approximately linear over short time intervals, it can be assumed that the bias varies **linearly** between two consecutive standards. + +The SSB method usually does not report absolute isotope ratios directly, but rather expresses results as **δ values** relative to the reference standard: + +$$ +\delta^{y/x} = +\left( +\frac{R_{\text{sample}}}{R_{\text{standard}}} - 1 +\right) \times 1000 +$$ + +# Function Workflow + +The function developed here outputs the **SSB Linear value**, which represents the normalized isotope ratio relative to the bracketing standards. + +The relationship between the **SSB Linear value** and **δ values** can be expressed as: + +$$ +\delta^{y/x} = (\text{SSB}_{\text{Linear}} - 1) \times 1000 +$$ + +The SSB Linear is calculated as: + +$$ +\text{SSB}_{\text{Linear}} = +\frac{R_{\text{meas,sample}}}{ +\text{mean}(R_{\text{meas,std1}}, R_{\text{meas,std2}})} +$$ + +where: + +$R_{\text{meas,sample}}$: measured isotope ratio of the sample;\ +$R_{\text{meas,std1}}$, $R_{\text{meas,std2}}$: measured isotope ratios of the standards before and after the sample. + +To obtain the **final corrected isotope ratio** (i.e., absolute value), the SSB Linear is multiplied by the true isotope ratio of the reference standard: + +$$ R_{\text{corr,sample}} = \text{SSB}_{\text{Linear}} \times R_{\text{true,std}} $$ + +where $R_{\text{true,std}}$ is the true isotope ratio of the standard material (e.g., NIST SRM 981). + +------------------------------------------------------------------------ + +**In practice**, each sample is typically measured multiple times to improve analytical precision. Each individual measurement is bracketed by two standard measurements (Std), and the SSB correction is calculated for each replicate separately. + +For example, if a sample is measured three times, the measurement sequence will be as follows: + +Std1 → Sample A1 → Std2 → Sample A2 → Std3 → Sample A3 → Std4 + +Each sample replicate (Sample A1, Sample A2, Sample A3) is corrected using the average of the standards measured immediately before and after it. The final isotope ratio for the sample is then obtained as the mean of the three corrected values. + +$$ +\text{SSB}_{\text{Linear}} = \frac{R_{meas,sample}}{\text{mean}(R_{meas,std1}, R_{meas,std2})} +$$ + +where $R_{meas,std1}$ and $R_{meas,std2}$ are the measured isotope ratios of the standards before and after the sample, respectively. + +After all three measurements of the same sample are corrected, their average is taken to obtain the final corrected ratio: + +$$ +R_{corr,avg} = \text{mean}(\text{SSB}_{\text{Linear,1}}, \text{SSB}_{\text{Linear,2}}, \text{SSB}_{\text{Linear,3}}) +$$ + +To obtain the **absolute corrected isotope ratio**, this normalized value is multiplied by the true isotope ratio of the reference standard: + +$$ +R_{\text{corr,sample}} = +R_{\text{corr,avg}} \times R_{\text{true,std}} +$$ + +where $R_{\text{true,std}}$ is the certified true isotope ratio of the reference standard (e.g., NIST SRM 981). + +------------------------------------------------------------------------ + +Also, in some practical analytical runs, several samples are often measured between two standards rather than each sample being individually bracketed. + +For example, the measurement sequence may take the form: + +Std1 → Sample A → Sample B → Sample C → Std2 → Sample D → Sample E → Sample F → Std3 → … + +In this sequence, samples A–C are bracketed by **Std1** and **Std2**, and samples D–F are bracketed by **Std2** and **Std3**. + +To correct for possible instrument drift between the standards, a **linear interpolation (weighted SSB)** approach is used. + +This method assumes that the instrumental response drifts approximately linearly between two consecutive standards. + +For a given sample $S_{j,r}$ located between two consecutive standards $\mathrm{Std}_j$ (preceding) and $\mathrm{Std}_{j+1}$ (following), the interpolated standard ratio is expressed as: + +$$ +\widehat{R}_{\text{std},j}(w_{j,r}) += (1 - w_{j,r})\,R_{\text{meas}}(\mathrm{Std}_j) ++ w_{j,r}\,R_{\text{meas}}(\mathrm{Std}_{j+1}) +$$ + +where $w_{j,r}$ represents the **relative position** of the sample between the two standards (ranging from 0 to 1). + +\ +It serves as a weighting factor in the linear interpolation between the two measured standard ratios. + +The corresponding drift-corrected isotope ratio is calculated as: + +$$ +\text{SSB}_{\text{linear}}(S_{j,r}) = +\frac{R_{\text{meas}}(S_{j,r})} +{(1 - w_{j,r})\,R_{\text{meas}}(\mathrm{Std}_j) ++ w_{j,r}\,R_{\text{meas}}(\mathrm{Std}_{j+1})} +$$ + +Finally, the absolute corrected isotope ratio is obtained by scaling with the certified reference value: + +$$ +R_{\text{corr,sample}} = +R_{\text{corr,avg}} \times R_{\text{true,std}} +$$ + +where $R_{\text{true,std}}$ is the true isotope ratio of the reference standard (e.g., NIST SRM 981). + +------------------------------------------------------------------------ + +# Function Purpose + +The main purpose of this function is to **simplify and speed up** the sample–standard bracketing (SSB) correction process. + +Traditionally, SSB corrections are performed manually in spreadsheets,which can be time-consuming and prone to human error, especially when multiple samples and replicates are involved. + +This function automates the calculation by: + +- Performing all SSB Linear and averaging steps automatically;\ +- Handling multiple samples and replicate measurements in one operation;\ +- Ensuring consistent data processing and reproducibility. + +By using this function, users can obtain corrected isotope ratios more efficiently and avoid the repetitive manual calculations normally required for SSB correction. + +# Example Usage + +This section demonstrates how to use the `standard_sample_bracketing()` function\ +to perform SSB correction automatically from a dataset containing measured isotope ratios. + +#### Example dataset + +The input dataset should contain at least the following columns: + +- **ID:** sample and standard identifiers (e.g., "Std", "Sample_A", "Sample_B", …);\ +- **Isotope_data:** measured isotope ratios (e.g., $^{206}\mathrm{Pb}/^{204}\mathrm{Pb}$). + +Below is an example of the input data structure: + +| ID | Isotope data | +|----------|--------------| +| Std | 16.928 | +| Sample A | 18.641 | +| Std | 16.932 | +| Sample A | 18.643 | +| Std | 16.935 | +| Sample A | 18.642 | +| Std | 16.938 | + +------------------------------------------------------------------------ + +#### Running the function + +```{r example, echo=TRUE} +# Load the package +# library(ASTR) + +# Example data frame +data <- data.frame( + ID = c("Std", "Sample_A", "Std", "Sample_A", "Std", "Sample_A", "Std"), + Isotope_data = c(16.928, 18.641, 16.932, 18.643, 16.935, 18.642, 16.938), + Error = c(16.928, 18.641, 16.932, 18.643, 16.935, 18.642, 16.938) +) + +# Run the function +result <- ASTR::standard_sample_bracketing( + df = data, + id_col = "ID", + id_values = "Isotope_data", + id_error = "Error", + std = "Std", + pos = 1, + notation = "delta" +) + +# Print results +result +``` + +# Output Interpretation + +The `standard_sample_bracketing()` function returns a data frame summarizing the results of each bracketing cycle. + +- **description**: the name of Sample.\ +- **calculations**: represents the SSB Linear value.\ +- **SE**: provides twice the standard deviation, which can be used as an estimate of analytical reproducibility. + +# Reference + +- Mason, T., Weiss, D., Horstwood, M., Parrish, R., Russell, S., Mullane, E., & Coles, B. (2004). *High-precision Cu and Zn isotope analysis by plasma source mass spectrometry – Part 2. Correcting for mass discrimination effects.* Journal of Analytical Atomic Spectrometry, **19**, 209–217. + +- Yang, Y., Hathorne, E., Siebert, C., Gutjahr, M., Fietzke, J., & Frank, M. (2024). *Unravelling instrumental mass fractionation of MC-ICP-MS using neodymium isotopes.* *Chemical Geology*, **662**, 122220. + +- National Institute of Standards and Technology (NIST). (1998). *Certificate of Analysis: Standard Reference Material 981 – Common Lead Isotopic Standard.* Gaithersburg, MD: U.S. Department of Commerce. diff --git a/vignettes/VG.ASTRschema.0.0.2.Rmd b/vignettes/VG.ASTRschema.0.0.2.Rmd index bc4f461..2409296 100644 --- a/vignettes/VG.ASTRschema.0.0.2.Rmd +++ b/vignettes/VG.ASTRschema.0.0.2.Rmd @@ -1,5 +1,5 @@ --- -title: "ASTR schema: Naming conventions" +title: "ASTR schema" csl: apa.csl link-citations: TRUE output: rmarkdown::html_vignette