The seahtrue package offers a set of functions to be able to perform reproducible data analysis of extracellular flux analysis. The main function revive_xfplate()
reads, preprocess and validates the input data and outputs experimental details and outcome variables in an organized (tidy) way. The output of the revive_xfplate()
is a nested tibble.
With instruments such as the Seahorse XF analyzer from Agilent, but also the O2K from Oroboros, other oyxgraphs (from Hansatech instruments for example) and the ReSipher from Lucid Scientific, scientists are able to analyze oxygen consumption of living biological samples.
Oxygen consumption of cells or small model organism can provide insights into the function of the mitochondria, since mitochondria are most of the time the main O2 consumers of cells. Apart form oxygen consumption the Seahorse XF analyzer is able to analyze in parallel the extracellular acidification of the culture medium in which the sample is emerged. This can be a proxy for glycolytic activity of samples.
Seahorse extracellular flux instruments performs analysis of O2 and pH in either 96 wells, 24 wells or 8 wells, and typically O2 and pH are monitored over a period of around 1 hour, in discrete measurements of typically 3 minutes each. Furthermore, perturbations of cellular functional states can be induced by adding compounds while performing the assay. The most common perturbations that are performed are injections of oligomycin, fccp and anitmycin/rotenone, known as a mitostress test.
Divakaruni, Ajit S., and Martin Jastroch. “A Practical Guide for the Analysis, Standardization and Interpretation of Oxygen Consumption Measurements.” Nature Metabolism 4, no. 8 (August 15, 2022): 978–94. https://doi.org/10.1038/s42255-022-00619-4.
Gerencser, A. A., A. Neilson, S. W. Choi, U. Edman, N. Yadava, R. J. Oh, D. A. Ferrick, D. G. Nicholls, and M. D. Brand. “Quantitative Microplate-Based Respirometry with Correction for Oxygen Diffusion.” Anal Chem 81, no. 16 (August 15, 2009): 6868–78. https://doi.org/10.1021/ac900881z.
Janssen, J. J. E., B. Lagerwaard, A. Bunschoten, H. F. J. Savelkoul, R. J. J. van Neerven, J. Keijer, and V. C. J. de Boer. “Novel Standardized Method for Extracellular Flux Analysis of Oxidative and Glycolytic Metabolism in Peripheral Blood Mononuclear Cells.” Sci Rep 11, no. 1 (January 18, 2021): 1662. https://doi.org/10.1038/s41598-021-81217-4.
Zhang, Xiang, Taolin Yuan, Jaap Keijer, and Vincent C. J. de Boer. “OCRbayes: A Bayesian Hierarchical Modeling Framework for Seahorse Extracellular Flux Oxygen Consumption Rate Data Analysis.” PLOS ONE 16, no. 7 (July 15, 2021): e0253926. https://doi.org/10.1371/journal.pone.0253926.
# data_file_path <-
# system.file("data",
# "revive_output_donor_A.rda",
# package = "seahtrue")
#
# load(data_file_path)
#library(seahtrue)
#library(tidyverse)
revive_output_donor_A <-
system.file("extdata",
"20191219_SciRep_PBMCs_donor_A.xlsx",
package = "seahtrue") %>%
seahtrue::revive_xfplate()
#> → Start function to read seahorse plate data from Excel file:
#> 20191219_SciRep_PBMCs_donor_A.xlsx
#> ℹ Finished collecting assay information.
#> → plateid is identified as:V0174416419V
#> → Rate was exported WITH background correction
#> ℹ Finished preprocessing of the input data
#> ℹ Finished validating the input data
Take a glimpse at the generated data from the revive_xfplate()
function:
revive_output_donor_A %>%
dplyr::glimpse()
#> Rows: 1
#> Columns: 9
#> $ plate_id <chr> "V0174416419V"
#> $ filepath_seahorse <list> [<tbl_df[1 x 3]>]
#> $ date_run <chr> "19-12-2019 17:25"
#> $ date_processed <dttm> 2024-10-29 19:28:37
#> $ assay_info <list> [<tbl_df[1 x 24]>]
#> $ injection_info <list> [<tbl_df[12 x 3]>]
#> $ raw_data <list> [<tbl_df[13824 x 21]>]
#> $ rate_data <list> [<tbl_df[1140 x 12]>]
#> $ validation_output <list> [TRUE, TRUE, [<tbl_df[12 x 10]>], [<tbl_df[13824 x 4…
Our data starts with 4 columns of identifiers. plate_id
, filepath_seahorse
, date_run
and date_processed
, this keeps the data easily traceable with ids on the top level. After that, 5 columns of nested tibbles follow. The assay_info
column contains a tibble/dataframe with information that was stored in the experimental file. This is either information that the user put into the software before running the experiment, after running the software when processing the data, or was generated by the software. The next colum is the injection_info
containing measurement, interval and injection. Then the two data columns for raw_data
and rate_data
are listed as tibble/dataframe. The final column is the validation_output
that has the output of the validations as well as its rules. In the next sections, we will explore each data output separately.
The rate_data
is what scientist typically use for their interpretation of their XF experiments. It contains the calculated OCR (oxygen consumption rate) and ECAR (extracellular acidification rate) values, together with the PER (proton efflux rate). The PER is calculated from ECAR when the buffer capacity is known and set in the experiment. In our rate_data
we only have the OCR and ECAR data, since PER can be easily calculated.
revive_output_donor_A %>%
purrr::pluck("rate_data", 1)
#> # A tibble: 1,140 × 12
#> measurement well group time_wave OCR_wave OCR_wave_bc ECAR_wave ECAR_wave_bc
#> <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1 A01 Back… 1.31 0 0 0 0
#> 2 1 A02 50.0… 1.31 0 6.22 0 2.90
#> 3 1 A03 100.… 1.31 0 26.6 0 5.87
#> 4 1 A04 100.… 1.31 0 21.4 0 4.40
#> 5 1 A05 150.… 1.31 0 3.08 0 12.4
#> 6 1 A06 200.… 1.31 0 41.1 0 8.98
#> 7 1 A07 150.… 1.31 0 39.5 0 9.27
#> 8 1 A08 200.… 1.31 0 40.4 0 4.75
#> 9 1 A09 250.… 1.31 0 58.8 0 7.39
#> 10 1 A10 250.… 1.31 0 59.4 0 6.88
#> # ℹ 1,130 more rows
#> # ℹ 4 more variables: cell_n <dbl>, interval <dbl>, injection <chr>,
#> # flagged_well <lgl>
The rate_data
has well
,measurement
, group
identifiers for each row followed by the time_wave
column which provides the time of the measurement in minutes, and the OCR and ECAR data columns. Also the cell_n
and flagged_well
status is joined in this dataframe. This provides all information for exploring and plotting the data. Since the OCR and ECAR data can be exported with either background on or off, the read functions in the seahtrue package determine whether the OCR and ECAR are background corrected or not, based on whether the Background wells have an OCR of zero. If the data was not corrected for background the the OCR is corrected while reading the .xlsx file. The background corrected data is given in OCR_wave_bc
and ECAR_wave_bc
. If the data was exported without background correction the OCR_wave
and ECAR_wave
data columns would contain the non corrected OCR and ECAR.
The raw_data
tibble contains the raw data that is collected in an XF experiment. This is essential data that can give detailed insights on the quality of the assay. Apart from the data that is presented in the raw data sheet of the .xlsx, some preprocessing output is given. Such as the timescale in seconds (timescale
) and minutes (minutes
), as well as an interval
and injection
id. Also, the background corrected raw data values for pH, O2 and its emissions are given. Again, just like in the rate_data
tibble, the cell_n
and flagged_well
status is given.
revive_output_donor_A %>%
purrr::pluck("raw_data", 1)
#> # A tibble: 13,824 × 21
#> well measurement tick timescale minutes group interval injection O2_em_corr
#> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <dbl> <chr> <dbl>
#> 1 A01 1 0 0 0 Back… 1 Baseline 12422.
#> 2 A02 1 0 0 0 50.0… 1 Baseline 12323.
#> 3 A03 1 0 0 0 100.… 1 Baseline 12483.
#> 4 A04 1 0 0 0 100.… 1 Baseline 12362.
#> 5 A05 1 0 0 0 150.… 1 Baseline 12103.
#> 6 A06 1 0 0 0 200.… 1 Baseline 12274.
#> 7 A07 1 0 0 0 150.… 1 Baseline 12354.
#> 8 A08 1 0 0 0 200.… 1 Baseline 12325.
#> 9 A09 1 0 0 0 250.… 1 Baseline 12347.
#> 10 A10 1 0 0 0 250.… 1 Baseline 12209.
#> # ℹ 13,814 more rows
#> # ℹ 12 more variables: pH_em_corr <dbl>, O2_mmHg <dbl>, pH <dbl>,
#> # pH_em_corr_corr <dbl>, O2_em_corr_bkg <dbl>, pH_em_corr_bkg <dbl>,
#> # O2_mmHg_bkg <dbl>, pH_bkgd <dbl>, pH_em_corr_corr_bkg <dbl>,
#> # bufferfactor <dbl>, cell_n <dbl>, flagged_well <lgl>
The assay_info
has information from user or software provided meta data that is associated with the experiment and plate. For example, the barcode of the cartridge that was used:
revive_output_donor_A %>%
purrr::pluck("assay_info", 1) %>%
pull(cartridge_barcode)
#> [1] "W0013917519B**+405-6+101-2300F+240-2***000A+219-4**+450-1125&"
The XF analyzer reads for each cartridge a barcode that is then associated with the assay. There is some information in the barcode that the software uses for OCR calculation. The emission of the fluorescent O2 sensors at zero oxygen F0
(see Gerencser et al. (2009) Quantitative microplate-based respirometry with correction for oxygen diffusion. Anal Chem 81:6868, for details) is derived from the Stern-Volmer constant KSV
. Where the emission at ambient oxygen is typically set at 12500 AU and ambient oxygen levels in wells in culture medium is set to 151.6900241 mmHg.
# KSV in barcode
revive_output_donor_A %>%
purrr::pluck("assay_info", 1) %>%
pull(cartridge_barcode) %>%
stringr::str_sub(-18, -13)
#> [1] "+219-4"
# KSV in assay info sheet
revive_output_donor_A %>%
purrr::pluck("assay_info", 1) %>%
pull(KSV)
#> [1] 0.0219
# F0 can be calculated using the stern-volmer equation
# and the info
# emission target at ambient O2 = 12500
# O2 level at ambient in sample medium in well = 151.69
#
# F0/F = 1 + KSV*O2
# F0 = (1+KSV*O2)*F
# F0 = (1+ KSV*151.69)*12500
# F0 from assay info sheet
revive_output_donor_A %>%
purrr::pluck("assay_info", 1) %>%
pull(F0)
#> [1] 54025.14
Apart from user and software generated meta info, the functions in the seahtrue package also put some relevant info into this tibble. Such as the time to start the actual measurements (minutes_to_start_measurement_one
), that shows how long the user took to insert the cell plate and start running the measurements. The timer starts at t = 0 minutes when the cartridge is calibrated by the user.
revive_output_donor_A %>%
purrr::pluck("assay_info", 1) %>%
pull(minutes_to_start_measurement_one)
#> [1] 37.23333
Apart from the assay_info
there can be some more meta info associated with the data tibbles in the form of attributes
. These can also be viewed as shown in the following examples:
revive_output_donor_A %>%
purrr::pluck("rate_data", 1) %>% str()
#> tibble [1,140 × 12] (S3: tbl_df/tbl/data.frame)
#> $ measurement : num [1:1140] 1 1 1 1 1 1 1 1 1 1 ...
#> $ well : chr [1:1140] "A01" "A02" "A03" "A04" ...
#> $ group : chr [1:1140] "Background" "50.000" "100.000" "100.000" ...
#> $ time_wave : num [1:1140] 1.31 1.31 1.31 1.31 1.31 ...
#> $ OCR_wave : num [1:1140] 0 0 0 0 0 0 0 0 0 0 ...
#> $ OCR_wave_bc : num [1:1140] 0 6.22 26.64 21.42 3.08 ...
#> $ ECAR_wave : num [1:1140] 0 0 0 0 0 0 0 0 0 0 ...
#> $ ECAR_wave_bc: num [1:1140] 0 2.9 5.87 4.4 12.36 ...
#> $ cell_n : num [1:1140] 0 32472 114732 83567 153510 ...
#> $ interval : num [1:1140] 1 1 1 1 1 1 1 1 1 1 ...
#> $ injection : chr [1:1140] "Baseline" "Baseline" "Baseline" "Baseline" ...
#> $ flagged_well: logi [1:1140] FALSE FALSE FALSE FALSE FALSE FALSE ...
#> - attr(*, "was_background_corrected")= logi TRUE
revive_output_donor_A %>%
purrr::pluck("rate_data", 1) %>%
attributes() %>%
purrr::pluck("was_background_corrected")
#> [1] TRUE
Since every XF experiment uses pertubations with chemicals or nutrients the injection_info
is important for interpretation of the experiment. The injection info can be plucked from the data tibble.
revive_output_donor_A %>%
purrr::pluck("injection_info", 1)
#> # A tibble: 12 × 3
#> measurement interval injection
#> <int> <dbl> <chr>
#> 1 1 1 Baseline
#> 2 2 1 Baseline
#> 3 3 1 Baseline
#> 4 4 2 FCCP
#> 5 5 2 FCCP
#> 6 6 2 FCCP
#> 7 7 3 AM/ROT
#> 8 8 3 AM/ROT
#> 9 9 3 AM/ROT
#> 10 10 4 Monensin/Hoechst
#> 11 11 4 Monensin/Hoechst
#> 12 12 4 Monensin/Hoechst
sessionInfo()
#> R Under development (unstable) (2024-10-21 r87258)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.1 LTS
#>
#> Matrix products: default
#> BLAS: /home/biocbuild/bbs-3.21-bioc/R/lib/libRblas.so
#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_GB LC_COLLATE=C
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: America/New_York
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] seahtrue_1.1.0 dplyr_1.1.4 BiocStyle_2.35.0
#>
#> loaded via a namespace (and not attached):
#> [1] tidyr_1.3.1 sass_0.4.9 utf8_1.2.4
#> [4] generics_0.1.3 stringi_1.8.4 rematch_2.0.0
#> [7] digest_0.6.37 magrittr_2.0.3 evaluate_1.0.1
#> [10] grid_4.5.0 timechange_0.3.0 bookdown_0.41
#> [13] fastmap_1.2.0 cellranger_1.1.0 jsonlite_1.8.9
#> [16] BiocManager_1.30.25 purrr_1.0.2 fansi_1.0.6
#> [19] scales_1.3.0 jquerylib_0.1.4 cli_3.6.3
#> [22] rlang_1.1.4 munsell_0.5.1 withr_3.0.2
#> [25] cachem_1.1.0 yaml_2.3.10 tidyxl_1.0.10
#> [28] tools_4.5.0 colorspace_2.1-1 ggplot2_3.5.1
#> [31] vctrs_0.6.5 logger_0.4.0 R6_2.5.1
#> [34] lifecycle_1.0.4 settings_0.2.7 lubridate_1.9.3
#> [37] snakecase_0.11.1 stringr_1.5.1 janitor_2.2.0
#> [40] pkgconfig_2.0.3 validate_1.1.5 pillar_1.9.0
#> [43] bslib_0.8.0 gtable_0.3.6 glue_1.8.0
#> [46] Rcpp_1.0.13 xfun_0.48 tibble_3.2.1
#> [49] tidyselect_1.2.1 knitr_1.48 htmltools_0.5.8.1
#> [52] rmarkdown_2.28 compiler_4.5.0 readxl_1.4.3