library(isoreader) # isoreader.isoverse.org
library(isoprocessor) # isoprocessor.isoverse.org
library(tidyverse)
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")

Using isoreader version 1.3.1 and isoprocessor version 0.6.11.

Continuous Flow

Read Data

iso_files <- iso_read_continuous_flow("data")
#> Info: preparing to read 2 data files (all will be cached)...
#> Info: reading file 'data/21848__SW_50.dxf' with '.dxf' reader...
#> Info: reading file 'data/21870__BSA2 CA3_sm.dxf' with '.dxf' reader...
#> Info: finished reading 2 files in 3.15 secs
iso_files %>% iso_get_data_summary()
#> Info: aggregating data summary from 2 data file(s)
#> # A tibble: 2 × 6
#>   file_id    raw_data      file_info  method_info  vendor_data_tab… file_path   
#>   <chr>      <glue>        <chr>      <chr>        <chr>            <chr>       
#> 1 21848__SW… 6681 time po… 19 entries standards, … 5 rows, 30 colu… data/21848_…
#> 2 21870__BS… 6681 time po… 20 entries standards, … 5 rows, 30 colu… data/21870_…

Chroms

iso_files %>% 
  iso_calculate_ratios("66/64") %>% 
  iso_plot_continuous_flow_data(panel = category, color = data, linetype = `Identifier 1`)
#> Info: calculating ratio(s) in 2 data file(s): r66/64

File Info

iso_files %>% iso_get_file_info(
  select = c(
    dt = file_datetime,
    Analysis,
    starts_with("Ident"),
    matches("Method")
  )
)
#> Info: aggregating file info from 2 data file(s), selecting info columns 'c(dt = file_datetime, Analysis, starts_with("Ident"), matches("Method"))'
#> # A tibble: 2 × 7
#>   file_id dt                  Analysis `Identifier 1` `Identifier 2` `EA Method`
#>   <chr>   <dttm>              <chr>    <chr>          <chr>          <chr>      
#> 1 21848_… 2018-04-27 22:32:52 21848    SW_50          3              FFW_SO2_no…
#> 2 21870_… 2018-04-28 07:36:05 21870    BSA2 CA3_sm    25             FFW_SO2_no…
#> # … with 1 more variable: Method <chr>

Resistors

iso_files %>% iso_get_resistors()
#> Info: aggregating resistors info from 2 data file(s)
#> # A tibble: 4 × 4
#>   file_id                  cup       R.Ohm mass 
#>   <chr>                  <int>       <dbl> <chr>
#> 1 21848__SW_50.dxf           1   300000000 64   
#> 2 21848__SW_50.dxf           2 30000000000 66   
#> 3 21870__BSA2 CA3_sm.dxf     1   300000000 64   
#> 4 21870__BSA2 CA3_sm.dxf     2 30000000000 66

Standards

iso_files %>% iso_get_standards()
#> Info: aggregating standards info from 2 data file(s)
#> # A tibble: 8 × 9
#>   file_id     standard gas   delta_name delta_value reference element ratio_name
#>   <chr>       <chr>    <chr> <chr>            <dbl> <chr>     <chr>   <chr>     
#> 1 21848__SW_… SO2_zero SO2   d 18O/16O            0 VSMOW     H       R 2H/1H   
#> 2 21848__SW_… SO2_zero SO2   d 18O/16O            0 VSMOW     O       R 17O/16O 
#> 3 21848__SW_… SO2_zero SO2   d 18O/16O            0 VSMOW     O       R 18O/16O 
#> 4 21848__SW_… SO2_zero SO2   d 34S/32S            0 VCDT      S       R 34S/32S 
#> 5 21870__BSA… SO2_zero SO2   d 18O/16O            0 VSMOW     H       R 2H/1H   
#> 6 21870__BSA… SO2_zero SO2   d 18O/16O            0 VSMOW     O       R 17O/16O 
#> 7 21870__BSA… SO2_zero SO2   d 18O/16O            0 VSMOW     O       R 18O/16O 
#> 8 21870__BSA… SO2_zero SO2   d 34S/32S            0 VCDT      S       R 34S/32S 
#> # … with 1 more variable: ratio_value <dbl>

Data Table

iso_files %>% iso_get_vendor_data_table(with_explicit_units = TRUE)
#> Info: aggregating vendor data table with explicit units from 2 data file(s)
#> # A tibble: 10 × 31
#>    file_id      Nr. `Start [s]` `Rt [s]` `End [s]` `Ampl 64 [mV]` `Ampl 66 [mV]`
#>    <chr>      <int>       <dbl>    <dbl>     <dbl>          <dbl>          <dbl>
#>  1 21848__SW…     1        108.     128.      184.          1503.          7209.
#>  2 21848__SW…     2        585.     621.      754.          1372.          6697.
#>  3 21848__SW…     3       1123.    1143.     1201.          1501.          7200.
#>  4 21848__SW…     4       1203.    1223.     1282.          1523.          7307.
#>  5 21848__SW…     5       1282.    1302.     1373.          1532.          7350.
#>  6 21870__BS…     1        108.     128.      188.          1565.          7512.
#>  7 21870__BS…     2        583.     617.      715.           521.          2503.
#>  8 21870__BS…     3       1123.    1143.     1198.          1538.          7383.
#>  9 21870__BS…     4       1203.    1223.     1281.          1559.          7483.
#> 10 21870__BS…     5       1282.    1302.     1366.          1565.          7514.
#> # … with 24 more variables: BGD 64 [mV] <dbl>, BGD 66 [mV] <dbl>,
#> #   rIntensity 64 [mVs] <dbl>, rIntensity 66 [mVs] <dbl>,
#> #   rIntensity All [mVs] <dbl>, Intensity 64 [Vs] <dbl>,
#> #   Intensity 66 [Vs] <dbl>, Intensity All [Vs] <dbl>,
#> #   Sample Dilution [%] <dbl>, List First Peak <int>, rR 66SO2/64SO2 <dbl>,
#> #   Rps 66SO2/64SO2 <dbl>, Is Ref.? <int>, R 66SO2/64SO2 <dbl>,
#> #   Ref. Name <chr>, rd 66SO2/64SO2 [permil] <dbl>, …

Calculations

These are the calculations that Isodat does. Not saying they are correct, it’s just what it does.

Fetch all information

# retrieve data table, resistors and standards, the relevant data for the calculations
all_data  <-
  iso_files %>% 
  iso_get_all_data() %>% 
  select(file_id, resistors, standards, vendor_data_table)
#> Info: aggregating all data from 2 data file(s)
all_data
#> # A tibble: 2 × 4
#>   file_id                resistors        standards        vendor_data_table
#>   <chr>                  <list>           <list>           <list>           
#> 1 21848__SW_50.dxf       <tibble [2 × 3]> <tibble [4 × 8]> <tibble [5 × 30]>
#> 2 21870__BSA2 CA3_sm.dxf <tibble [2 × 3]> <tibble [4 × 8]> <tibble [5 × 30]>

Do the calculations

# calculations
calculations <- 
  all_data %>% 
  # unpack the vendor data table
  unnest(vendor_data_table) %>% 
  # do each set of calculations within each file
  group_by(file_id) %>% 
  # do all the calculations
  # only columns required for all of the following are rIntensity 64 
  #   and rIntensity 66 (+ resistors and standards)
  mutate(
    # rIntensity All is just the sum of the r(ecorded) intensities
    `rIntensity All new` = `rIntensity 64` + `rIntensity 66`,
    # the intensity of the major ion is just scaled from mVs to Vs
    `Intensity 64 new` = `rIntensity 64` / 1000,
    # the other intensities are scaled by the resistor ratio to account for 
    #   different signal amplification
    resistor_ratio = map_dbl(
      resistors, 
      ~with(.x, R.Ohm[mass == "64"] / R.Ohm[mass == "66"])),
    `Intensity 66 new` = `rIntensity 66` / 1000 * resistor_ratio,
    # IntensityAll then again is just the some of the scaled intensities
    `Intensity All new` = `Intensity 64 new` + `Intensity 66 new`,
    # the r(ecorded) ratio is just based on the r(ecorded)Intensities
    `rR 66SO2/64SO2 new` = `rIntensity 66` / `rIntensity 64`,
    # whereas the actual Ratio is based on known reference ratios and linear 
    # extrapolation (as far as I can tell) between the reference peaks
    ## 1. known isotopic composition of the ref peaks (Rps)
    `Rps 66SO2/64SO2 new` = map2_dbl(
      standards, `Is Ref.?`,
        # nb 1: they only calculate this for reference peaks (silly anyways, 
        #       it's always the same!)
        # nb 2: the SO2 ref gas here (SO2_zero) was defined with delta of 0 in 
        #       both S and O, hence using the ref ratios directly in the following calculation
        # nb 3: note that isodat does not consider the isotopologue 64=32+17+17 
        #       so their ratio is actually slightly wrong!! 
        ~ with(.x, ratio_value[ratio_name == "R 34S/32S"] + 
                    2 * ratio_value[ratio_name == "R 18O/16O"])
        # correct would be the following (but the difference is in the 7th digit, 
        #    see for yourself what difference it makes in the final deltas):
        #~ with(.x, ratio_value[ratio_name == "R 34S/32S"] + 2 * ratio_value[ratio_name == "R 18O/16O"] + ratio_value[ratio_name == "R 17O/16O"]^2)
       ),
    ## 2. linearly extrapolated ref ratio at all retention times
    ref_ratio_at_Rt = 
      lm(y ~ x, 
         data = tibble(
           x = Rt[`Is Ref.?` == 1], 
           y = `rR 66SO2/64SO2 new`[`Is Ref.?` == 1])) %>% 
      predict(newdata = tibble(x = Rt)) %>% 
      as.numeric(),
    ## 3. resulting calculated 66/64 ratio (note that since these relie on rR only, 
    #     the whole resistor shenanegans are not actually necessary)
    `R 66SO2/64SO2 new` = `rR 66SO2/64SO2 new` / ref_ratio_at_Rt * `Rps 66SO2/64SO2 new`,
    # the delta value is then just calculated based on the standard definition R/R - 1)
    `d 66SO2/64SO2 new` = ((`R 66SO2/64SO2 new` / `Rps 66SO2/64SO2 new` - 1) * 1000) %>% 
      # due to inacuracies at the 10^-13 level in most machine calculations, rounding to 10 digits
      round(10),
    # or more elegantly directly from the rR and extrapolated ref ratios
    `d 66SO2/64SO2 new2` = ((`rR 66SO2/64SO2 new` / ref_ratio_at_Rt - 1) * 1000) %>% round(10)
  ) %>% 
  ungroup() 

Compare

Now let’s compare all the isodat values with what we got. The suffix new indicates what we calculated

# look at new rIntensity calculations --> identical? yes
calculations %>% select(Nr., starts_with("rIntensity")) %>% rmarkdown::paged_table()

# look at new Intensity calculations --> identical? yes
calculations %>% select(Nr., starts_with("Intensity")) %>% rmarkdown::paged_table()

# look at new ratio calculations --> identical? yes
calculations %>% select(Nr., matches("R(ps)? 66")) %>% rmarkdown::paged_table()

# look at delta calculations --> identical? yes
calculations %>% select(Nr., starts_with("d 66")) %>% rmarkdown::paged_table()

Dual Inlet

Read Data

Using isoreader version 1.3.1.

iso_files <- iso_read_dual_inlet("data")
#> Info: preparing to read 4 data files (all will be cached)...
#> Info: reading file 'data/SP0.did' with '.did' reader...
#> Info: reading file 'data/SP1.did' with '.did' reader...
#> Info: reading file 'data/SP2.did' with '.did' reader...
#> Info: reading file 'data/SP3.did' with '.did' reader...
#> Info: finished reading 4 files in 2.56 secs
iso_files %>% iso_get_data_summary()
#> Info: aggregating data summary from 4 data file(s)
#> # A tibble: 4 × 6
#>   file_id raw_data          file_info method_info    vendor_data_tab… file_path 
#>   <chr>   <glue>            <chr>     <chr>          <chr>            <chr>     
#> 1 SP0.did 8 cycles, 5 ions… 15 entri… standards, re… 8 rows, 8 colum… data/SP0.…
#> 2 SP1.did 8 cycles, 5 ions… 15 entri… standards, re… 8 rows, 8 colum… data/SP1.…
#> 3 SP2.did 8 cycles, 5 ions… 15 entri… standards, re… 8 rows, 8 colum… data/SP2.…
#> 4 SP3.did 8 cycles, 5 ions… 15 entri… standards, re… 8 rows, 8 colum… data/SP3.…

Voltages

iso_files %>% 
  iso_calculate_ratios("45/44") %>% 
  iso_plot_dual_inlet_data()
#> Info: calculating ratio(s) in 4 data file(s): r45/44

Data Table

iso_files %>% iso_get_vendor_data_table(with_explicit_units = TRUE)
#> Info: aggregating vendor data table with explicit units from 4 data file(s)
#> # A tibble: 32 × 9
#>    file_id cycle `d 45N2O/44N2O` `d 46N2O/44N2O` `d 18O/16O` `d 15N/14N`
#>    <chr>   <int>           <dbl>           <dbl>       <dbl>       <dbl>
#>  1 SP0.did     1          0.0696           -6.42       -6.47      0.246 
#>  2 SP0.did     2         -0.0252           -6.99       -7.04      0.161 
#>  3 SP0.did     3         -0.152            -6.43       -6.48      0.0133
#>  4 SP0.did     4         -0.0402           -6.79       -6.84      0.140 
#>  5 SP0.did     5          0.0236           -6.38       -6.43      0.196 
#>  6 SP0.did     6         -0.0685           -6.26       -6.30      0.0962
#>  7 SP0.did     7         -0.112            -6.41       -6.46      0.0541
#>  8 SP0.did     8         -0.0870           -6.60       -6.65      0.0859
#>  9 SP1.did     1          0.0213           -5.82       -5.87      0.179 
#> 10 SP1.did     2         -0.0117           -6.28       -6.33      0.157 
#> # … with 22 more rows, and 3 more variables: d 17O/16O <dbl>,
#> #   AT% 15N/14N <dbl>, AT% 18O/16O <dbl>

Calculations

# calculate ratios
ratios <- 
  iso_files %>% 
  iso_calculate_ratios(c("45/44", "46/44")) %>% 
  iso_get_raw_data() %>% 
  select(file_id, type, cycle, starts_with("r"))
#> Info: calculating ratio(s) in 4 data file(s): r45/44, r46/44
#> Info: aggregating raw data from 4 data file(s)
ratios
#> # A tibble: 68 × 5
#>    file_id type     cycle `r45/44` `r46/44`
#>    <chr>   <chr>    <int>    <dbl>    <dbl>
#>  1 SP0.did standard     0    0.809    0.596
#>  2 SP0.did standard     1    0.809    0.597
#>  3 SP0.did standard     2    0.810    0.597
#>  4 SP0.did standard     3    0.810    0.597
#>  5 SP0.did standard     4    0.810    0.597
#>  6 SP0.did standard     5    0.811    0.597
#>  7 SP0.did standard     6    0.811    0.597
#>  8 SP0.did standard     7    0.811    0.597
#>  9 SP0.did standard     8    0.811    0.597
#> 10 SP0.did sample       1    0.809    0.593
#> # … with 58 more rows
  
# get bracketing standards
pre_standard <- ratios %>% filter(type == "standard", cycle < max(cycle)) %>% mutate(cycle = cycle + 1)
post_standard <- ratios %>% filter(type == "standard", cycle > min(cycle))
standards <- left_join(pre_standard, post_standard, by = c("file_id", "cycle", "type"))

# calculate deltas based on average of bracketing standards
deltas <- ratios %>% filter(type == "sample") %>% 
  left_join(standards, by = c("file_id", "cycle")) %>% 
  mutate(
    `d45/44` = (2 * `r45/44` / (`r45/44.x` + `r45/44.y`) - 1) * 1e3,
    `d46/44` = (2 * `r46/44` / (`r46/44.x` + `r46/44.y`) - 1) * 1e3
  ) %>% 
  select(file_id, cycle, starts_with("d"))
deltas
#> # A tibble: 32 × 4
#>    file_id cycle `d45/44` `d46/44`
#>    <chr>   <dbl>    <dbl>    <dbl>
#>  1 SP0.did     1   0.0696    -6.42
#>  2 SP0.did     2  -0.0252    -6.99
#>  3 SP0.did     3  -0.152     -6.43
#>  4 SP0.did     4  -0.0402    -6.79
#>  5 SP0.did     5   0.0236    -6.38
#>  6 SP0.did     6  -0.0685    -6.26
#>  7 SP0.did     7  -0.112     -6.41
#>  8 SP0.did     8  -0.0870    -6.60
#>  9 SP1.did     1   0.0213    -5.82
#> 10 SP1.did     2  -0.0117    -6.28
#> # … with 22 more rows

Compare

Compare this with the vendor data table.

# check identical --> correct
deltas %>% 
  left_join(iso_get_vendor_data_table(iso_files), by = c("file_id", "cycle")) %>% 
  rmarkdown::paged_table()
#> Info: aggregating vendor data table from 4 data file(s)

delta 18O and delta 15N can be calculated from delta 45 and delta 46 with 17O correction (e.g. using Jan Kaiser and Thomas Röckmann in “Correction of mass spectrometric isotope ratio measurements for isobaric isotopologues of O2, CO, CO2, N2O and SO2”,Rapid Communications in Mass Spectrometry, 2008, 3997–4008).

Note that both the delta calcluations and 17O correction will be available through functions in isoprocessor at some point (might be already, this file may be out of date).