Skip to contents

Tutorial Prerequisites: Preparing and importing data

This tutorial follows a complete postprocessing and quality control workflow for a targeted lipidomics analysis. Starting from integrated peak areas, we work towards a curated table of lipid species concentrations ready for statistical analysis. Along the way we assess the analytical and data quality of the run, normalise and quantify against internal standards, correct for run-order drift and between-batch effects, filter features on QC criteria, and export the result. The dataset (a longitudinal plasma study (sPerfect) measured across six batches) is realistic enough to show the decisions a real analysis demands.

1. Set up a project

A new analysis is easiest to manage inside an RStudio or Positron project (see Using RStudio Projects or the Positron User Guide), with a predictable folder layout that keeps the raw data, the exported results, and the processing notebook apart:

my_study/
├── data/           # raw data and metadata files
├── output/         # exported results
└── analysis.Rmd    # your processing notebook

An R Notebook (.Rmd) or Quarto (.qmd) document is a natural home for a workflow like this one: it interleaves code with prose, keeping a record of every decision beside the result it produced. With the project in place, load the package to begin:

2. Importing analysis results

We begin by importing the MRMhub result file, which holds the areas of the integrated peaks (features) for every processed raw data file. The same file also carries the peak retention times and widths and metadata read from the mzML files, such as acquisition time stamps and precursor/product m/z values; setting import_metadata = TRUE brings those across as well. The imported data lands in the dataset_orig slot and is copied to the working dataset. Printing the object at any stage with print(mexp) reports its processing status, and in RStudio you can expand mexp in the Environment pane.

mexp <- MRMhubExperiment(title = "sPerfect")
mexp <- import_data_mrmhub(
  mexp,
  path = "./datasets/sPerfect_MRMhub.tsv",
  import_metadata = TRUE
)
 Imported 499 analyses with 503 features.
 feature_area selected as default feature intensity. Modify with `set_intensity_var()`.
 Analysis metadata associated with 499 analyses.
 Feature metadata associated with 503 features.

3. A glimpse on the imported data

The data is stored in long format (one row per analysis–feature pair) so each measurement carries its own area, retention time, and metadata side by side. Print the working table below, or enter View(mexp@dataset_orig) in the console to browse and filter it in the RStudio viewer. For routine access it is cleaner to use get_analyticaldata(mexp, annotated = TRUE), which returns the same data already joined to its sample and feature annotations.

print(mexp@dataset)
# A tibble: 250,997 × 21
   analysis_order analysis_id  acquisition_time_stamp qc_type batch_id sample_id
            <int> <chr>        <dttm>                 <chr>   <chr>    <chr>    
 1              1 Longit_BLAN… 2017-10-20 14:15:36    SBLK    1        NA       
 2              1 Longit_BLAN… 2017-10-20 14:15:36    SBLK    1        NA       
 3              1 Longit_BLAN… 2017-10-20 14:15:36    SBLK    1        NA       
 4              1 Longit_BLAN… 2017-10-20 14:15:36    SBLK    1        NA       
 5              1 Longit_BLAN… 2017-10-20 14:15:36    SBLK    1        NA       
 6              1 Longit_BLAN… 2017-10-20 14:15:36    SBLK    1        NA       
 7              1 Longit_BLAN… 2017-10-20 14:15:36    SBLK    1        NA       
 8              1 Longit_BLAN… 2017-10-20 14:15:36    SBLK    1        NA       
 9              1 Longit_BLAN… 2017-10-20 14:15:36    SBLK    1        NA       
10              1 Longit_BLAN… 2017-10-20 14:15:36    SBLK    1        NA       
# ℹ 250,987 more rows
# ℹ 15 more variables: replicate_no <int>, specimen <chr>, feature_id <chr>,
#   feature_class <chr>, feature_label <chr>, is_istd <lgl>,
#   is_quantifier <lgl>, analyte_id <chr>, istd_feature_id <chr>,
#   feature_rt <dbl>, feature_area <dbl>, feature_height <dbl>,
#   feature_fwhm <dbl>, feature_width <dbl>, feature_intensity <dbl>

4. Analytical design and timeline

Before touching the signals, understand how the run was structured. The plot below lays out the batch boundaries, the positions of the quality control (QC) samples, and the date, duration, and run time of the analysis: context that informs every later choice about drift, batches, and which QC type to trust. Setting show_timestamp = TRUE overlays acquisition times and reveals any long interruptions within or between batches.

plot_runsequence(
  mexp,
  qc_types = NA,
  show_batches = TRUE,
  batch_zebra_stripe = TRUE,
  batch_fill_color = "#fffbdb",
  segment_linewidth = 0.5,
  show_timestamp = FALSE
)
RunSequence plot of the analysis showing batches and QC positions

Figure 1. Run sequence of the analysis (batch structure, QC-sample positions, and acquisition timeline).

5. Overview of chromatographic separation

A first look at the chromatography confirms that species elute where expected. The plot shows the retention time distribution of all detected lipid species across the study samples, arranged by class.

plot_abundanceprofile(
  mexp,
  variable = "rt",
  qc_types = "SPL",
  log_scale = FALSE,
  density_strip = TRUE,
  show_sum = FALSE,
  x_label = NA,
  feature_map = "lipidomics"
)
Retention-time distribution of all detected lipid species

Figure 2. Retention-time distribution of all detected lipid species across the study samples.

6. Peak picking QC

Within a lipid class, retention time increases smoothly with chain length and decreases with the number of double bonds, so plotting RT against total carbon count (coloured by double-bond number) turns peak-picking errors into visible outliers. Species whose residual from the class trend exceeds outlier_residual_min are labelled, flagging likely misannotations or mis-integrated peaks. The check is only as strong as the coverage behind it: here just a few species per class were measured, so the trend is poorly constrained for some classes and double-bond counts.

plot_rt_vs_chain(
  mexp,
  qc_types = "SPL",
  x_var = "total_c",
  outlier_residual_min = 0.3,
  font_base_size = 8,
  point_size = 1
)
Retention time versus carbon number per lipid class, outliers labelled

Figure 3. Retention time versus total carbon number per lipid class; labelled points flag possible misannotations.

The internal standards (ISTDs) are our clearest window on instrument stability. The same ISTD amount was spiked into every sample except the solvent blanks (SBLK), so in the absence of technical variation each ISTD should read a constant intensity across all samples and sample types. Plotting them in injection order across the six batches makes any drift, batch offset, or blockage immediately visible. Setting output_pdf = TRUE saves the plots to the output subfolder for closer inspection.

plot_runscatter(
  mexp,
  variable = "intensity",
  qc_types = c("BQC", "TQC", "SPL", "PBLK", "SBLK"),
  include_feature_filter = "ISTD",
  exclude_feature_filter = "Hex|282",
  cap_outliers = TRUE,
  log_scale = FALSE,
  show_batches = TRUE,
  font_base_size = 5,
  output_pdf = FALSE,
  cols_page = 4, rows_page = 3
)
RunScatter of internal-standard intensities across batches

Figure 4. Internal-standard intensities across all six batches; the spiked amount is constant, so a flat trend is expected.

RunScatter of internal-standard intensities across batches

Figure 4. Internal-standard intensities across all six batches; the spiked amount is constant, so a flat trend is expected.

RunScatter of internal-standard intensities across batches

Figure 4. Internal-standard intensities across all six batches; the spiked amount is constant, so a flat trend is expected.

8. Adding detailed metadata

Everything from here on (sample roles, ISTD assignments, spike amounts, concentrations) comes from the analysis metadata. The MRMhub Excel template collects, organises, and pre-validates it in one place; open the .xlsx file in the datasets folder to see its structure. On import the metadata is validated: a hard error (a duplicate or missing ID) stops the import with a summary of the problems, while softer issues are reported as warnings you can work through, or bypass with ignore_warnings = TRUE. We then fix the processing order to the injection sequence with set_analysis_order(), and tell MRMhub which of the imported signal variables to treat as the working feature intensity (set_intensity_var("area")), the value all subsequent calculations start from.

mexp <- import_metadata_msorganiser(
  mexp,
  path = "datasets/sPerfect_Metadata.xlsx",
  ignore_warnings = TRUE
)
Found no errors, 4 warnings, and no notes in the metadata.
----------------------------------------------------------------------------
  Type  Table    Column                Issue                           Count
1 W*    Analyses analysis_id           Analyses not in analysis data      15
2 W*    Features feature_id            Feature(s) without metadata         1
3 W*    Features feature_id            Feature(s) not in analysis data     4
4 W*    ISTDs    quant_istd_feature_id Internal standard(s) not used       1

----------------------------------------------------------------------------
E = Error, W = Warning, W* = Suppressed Warning, N = Note
----------------------------------------------------------------------------
 Analysis metadata associated with 499 analyses.
 Feature metadata associated with 502 features.
 Internal Standard metadata associated with 17 ISTDs.
 Response curve metadata associated with 12 annotated analyses.
mexp <- set_analysis_order(mexp, order_by = "timestamp")
 Analysis order set to "timestamp"
mexp <- set_intensity_var(mexp, variable_name = "area")
 Default feature intensity variable set to "feature_area"

To judge technical trends that affect most analytes at once, the RLA (Relative Log Abundance) plot is the tool of choice (De Livera et al., Analytical Chemistry, 2015). Each feature is expressed relative to its across- or within-batch median and summarised as one boxplot per sample; a well-behaved sample sits tight around zero, so boxes that shift or spread flag pipetting errors, sample spillage, injection-volume changes, or drops in instrument sensitivity. In this run one sample stands out from its batch as a candidate technical outlier.

plot_rla_boxplot(
  mexp,
  variable = "intensity",
  rla_type_batch = "within",
  qc_types = c("BQC", "SPL", "RQC", "TQC", "PBLK"),
  filter_data = FALSE,
  show_timestamp = FALSE,
  outlier_exclude = FALSE,
  x_gridlines = FALSE,
  batch_zebra_stripe = FALSE,
  linewidth = 0.1
)
RLA boxplot per sample within batches

Figure 5. Within-batch relative log abundance per sample; deviating boxes flag pipetting, injection, or sensitivity issues.

10. PCA of all QC types

Principal component analysis condenses all features into a few axes and gives a complementary overview of how the study and QC samples relate, useful for spotting batch effects, technical outliers, and systematic differences between sample types. Here it offers a second, independent view of the candidate outlier flagged by the RLA plot.

plot_pca(
  mexp,
  variable = "feature_intensity",
  qc_types = c("SPL", "BQC", "TQC"),
  filter_data = FALSE,
  pca_dims = c(1, 2),
  labels_threshold_mad = 3,
  log_transform = TRUE,
  include_istd = FALSE,
  point_size = 2, point_alpha = 0.7, ellipse_alpha = 0.3, font_base_size = 8
)
PCA of study and QC samples before outlier removal

Figure 6. PCA of study and QC samples before outlier removal.

11. Excluding technical outliers

With the outlier corroborated by both plots, we remove it from all downstream processing using exclude_analyses(). The function does not delete any data: it sets the sample’s valid_analysis flag to FALSE, so it is simply skipped in later steps and can be reinstated at any time. Replotting the PCA lets us confirm the effect of the exclusion on the sample structure.

mexp <- exclude_analyses(
  mexp,
  analyses = "Longit_batch6_51",
  clear_existing = TRUE
)

plot_pca(
  mexp,
  variable = "intensity",
  qc_types = c("SPL", "BQC", "TQC"),
  filter_data = FALSE,
  pca_dims = c(1, 2),
  labels_threshold_mad = 3,
  log_transform = TRUE,
  include_istd = FALSE,
  shared_labeltext_hide = NA,
  point_size = 2, point_alpha = 0.7, ellipse_alpha = 0.3, font_base_size = 8
)
PCA of study and QC samples after excluding the outlier

Figure 7. PCA after excluding the flagged technical outlier.

12. Response curves

A linear response is a prerequisite for comparing concentrations between samples. Plasma lipid abundances span a wide dynamic range, and because each class-specific ISTD is spiked at a single concentration, the response may be linear near the ISTD level yet flatten for far more or less abundant species. To verify this, we measured injection-volume series at the start and end of the run as a dedicated QC. We first drop the very lowest-abundance features, then plot the curves for a subset of PC species; include_feature_filter accepts regular expressions, so any class or chain-length pattern can be selected.

mexp <- filter_features_qc(
  mexp,
  include_qualifier = FALSE,
  include_istd = TRUE,
  min.intensity.median.spl = 200
)
plot_responsecurves(
  mexp,
  variable = "intensity",
  filter_data = TRUE,
  include_feature_filter = "^PC 3[0-5]",
  output_pdf = FALSE,
  cols_page = 5, rows_page = 4
)
Response curves for selected PC species

Figure 8. Response curves for selected PC species over the injection-volume series.

13. Isotope interference correction

Some peaks of interest are co-integrated with the isotopic peaks of other lipid species, for example the M+2 isotope of a species two mass units lighter falling in the same transition. correct_custom_interferences() subtracts these declared contributions from the raw feature intensities, reading which feature interferes with which, and by how much, from the metadata; the original values are preserved in feature_intensity_before. The interference factors here were derived with LICAR (https://github.com/SLINGhub/LICAR). Inspect the “Features (Analytes)” sheet of the metadata file to see which species are affected.

mexp <- correct_custom_interferences(mexp)
plot_interference_correction(
  mexp,
  qc_types = c("BQC", "SPL", "TQC", "LTR")
)
QC plot of species before and after interference correction

Figure 9. Selected species before and after isotope-interference correction.

14. Normalization and quantification based on ISTDs

Quantification proceeds in two steps. normalize_by_istd() divides each lipid’s peak area by the area of its assigned class-specific internal standard within the same injection, writing the ratio to feature_norm_intensity; this cancels most of the injection-to-injection and matrix variation shared by an analyte and its ISTD. quantify_by_istd() then converts those ratios into absolute concentrations from three quantities held in the metadata (the ISTD concentration in the spike solution, the spiked volume, and the sample amount) and stores the result in feature_conc. Because both new variables sit beside the raw areas, the RLA and PCA plots above can be rerun on the normalised data with variable = "norm_intensity" or variable = "conc".

mexp <- normalize_by_istd(mexp)
 460 features normalized with 17 ISTDs in 498 analyses.
mexp <- quantify_by_istd(mexp)
 460 feature concentrations calculated based on 42 ISTDs and sample amounts of 498 analyses.
 Concentrations are given in μmol/L.

15. Effects of class-wide ISTD normalization

Class-specific ISTDs are common practice in lipidomics, but they are rarely authentic standards for every species they normalise. A non-authentic ISTD may elute at a different time, experience different matrix effects, and fragment differently, so its response can diverge from that of the analyte. Normalising against such an ISTD can then add variability rather than remove it. Because a good ISTD should reduce the coefficient of variation (CV), comparing the CV of QC and study samples before and after normalisation is a direct way to catch a misbehaving standard: a class whose CV rises after normalisation is a warning sign worth investigating before trusting its concentrations.

mexp <- filter_features_qc(
  mexp,
  include_qualifier = FALSE,
  include_istd = TRUE,
  min.intensity.median.spl = 1000
)
plot_normalization_qc(
  mexp,
  before_norm_var = "intensity",
  after_norm_var = "norm_intensity",
  plot_type = "diff",
  qc_types = c("TQC", "BQC", "SPL"),
  facet_by_class = TRUE,
  point_size = 2,
  y_lim = c(-5, 15)
)
Normalization QC plot of CV change per feature by class

Figure 10. Change in CV per feature after ISTD normalisation, faceted by lipid class.

16. Drift correction

Even after ISTD normalisation, concentrations can drift gradually within a batch as the instrument response changes. We correct this with a Gaussian-kernel smoother fitted through the study samples in each batch (batch_wise = TRUE), which suits large, well-randomised sample sets like this longitudinal study; kernel_size sets the width of the smoothing window. The corrected values replace conc, the pre-correction values are kept in conc_before, and the fitted trend in conc_before_fit. The summary the function prints is not a formal diagnostic of the fit but a quick check (reported as a change in median CV) that the correction improved precision without introducing artefacts. A scale correction along the fit is available via scale_smooth = TRUE.

mexp <- correct_drift_gaussiankernel(
  mexp,
  variable = "conc",
  ref_qc_types = "SPL",
  ignore_istd = TRUE,
  batch_wise = TRUE,
  replace_previous = TRUE,
  recalc_trend_after = TRUE,
  kernel_size = 10,
  outlier_filter = FALSE,
  outlier_ksd = 5,
  location_smooth = TRUE,
  scale_smooth = FALSE,
  show_progress = FALSE
)
! 4 feature(s) contain one or more zero or negative `conc` values. Verify your data or use `log_transform_internal = FALSE`.
! 1 features showed no variation in the study sample's original values across analyses. 
! 1 features have invalid values after smoothing. NA will be be returned for all values of these faetures. Set `use_original_if_fail = FALSE to return orginal values..
! Smoothing failed for 1 feature(s) in all batches. Please check data, metadata, and fit parameters.
! Smoothing failed for 1 feature(s) in at least one batch: PG 36:2. Please check data, metadata and fit parameters.
 Drift correction was applied to 459 of 460 features (batch-wise).
 The median per-feature CV change of all features in study samples was -1.00% (range: -12.53% to 2.59%; a positive value means the CV increased). The median CV across all features across batches decreased from 39.00% to 37.71%.

To see the effect, we plot one example species (PC 40:8) before and after correction. Since we reuse the same plot several times, we wrap it in a small function with the recurring parameters preset; variable selects which stage to show (conc_before or conc) and feature which species.

my_trend_plot <- function(variable, feature) {
  plot_runscatter(
    mexp,
    variable = variable,
    qc_types = c("BQC", "TQC", "SPL"),
    include_feature_filter = feature,
    exclude_feature_filter = "ISTD",
    cap_outliers = TRUE,
    log_scale = FALSE,
    show_trend = TRUE,
    output_pdf = FALSE,
    cols_page = 1, rows_page = 1
  )
}
my_trend_plot("conc_before", "PC 40:8")
RunScatter of PC 40:8 concentrations before drift correction

Figure 11. PC 40:8 raw concentrations before drift correction, with the fitted within-batch trend.

my_trend_plot("conc", "PC 40:8")
RunScatter of PC 40:8 concentrations after drift correction

Figure 12. PC 40:8 concentrations after within-batch drift correction.

17. Batch-effect correction

Drift correction flattens the trend inside each batch but leaves the batches at different levels, since each is fitted independently. correct_batch_centering() aligns them by shifting every batch to a common median, computed from the study samples (ref_qc_types = "SPL"); correct_scale = TRUE additionally equalises the between-batch spread. As with drift correction, the aligned values replace conc and the previous ones are retained in conc_before. We replot the example species to confirm the batches now line up.

mexp <- correct_batch_centering(
  mexp,
  variable = "conc",
  ref_qc_types = "SPL",
  replace_previous = TRUE,
  correct_location = TRUE,
  correct_scale = TRUE,
  log_transform_internal = TRUE
)

my_trend_plot("conc", "PC 40:8")
RunScatter of PC 40:8 after batch centering

Figure 13. PC 40:8 after drift correction followed by batch centering; batch trends are aligned.

18. Saving runscatter plots of all features as PDF

For a full record it helps to save runscatter plots for every species, or a chosen subset, to a multi-page PDF in the output subfolder. Blanks are usually worth excluding, as they can show erratic concentrations when feature and ISTD signals sit close together or below the limit of detection. The include_feature_filter and exclude_feature_filter arguments select analytes and both accept regular expressions, which a language model can help you construct for more intricate patterns.

plot_runscatter(
  mexp,
  variable = "conc",
  qc_types = c("BQC", "TQC", "SPL"),
  include_feature_filter = NA,
  exclude_feature_filter = "ISTD",
  cap_outliers = TRUE,
  log_scale = FALSE,
  show_trend = TRUE,
  output_pdf = TRUE,
  path = "./output/runscatter_after-drift-batch-correction.pdf",
  page_width = 297, page_height = 210,
  cols_page = 2, rows_page = 2,
  show_progress = TRUE
)

page_width and page_height set the page size; without them an A4 page is used, oriented by page_orientation.

The single figures made earlier in this workflow are saved with save_plot(), which writes any plot at a defined size and resolution and can produce several formats in one call:

plot_pca(mexp, variable = "conc", qc_types = c("BQC", "SPL")) |>
  save_plot(path = "./output/pca", format = c("pdf", "png"),
            width = 180, height = 120)

The plot is returned visibly, so it still appears in the notebook while being written to file.

See Saving plots for the available formats and when to prefer a vector or a raster one.

19. QC-based feature filtering

The final curation step removes features that fail defined QC criteria. filter_features_qc() combines several: the quality of the response curves (minimum r², slope, and maximum y-intercept), the signal-to-blank ratio against process blanks, the absolute signal level as a proxy for the limit of detection, and the precision in the batch QCs (max.cv.conc.bqc). The criteria are applied hierarchically (a feature counts as failing CV only once it has passed the S/B and signal filters) and features.to.keep retains named species regardless. The filter can be run repeatedly, overwriting or, with clear_existing = FALSE, amending the previous set.

mexp <- filter_features_qc(
  mexp,
  clear_existing = TRUE,
  use_batch_medians = TRUE,
  include_qualifier = FALSE,
  include_istd = FALSE,
  response.curves.selection = c(1, 2),
  response.curves.summary = "mean",
  min.rsquare.response = 0.8,
  min.slope.response = 0.75,
  max.yintercept.response = 0.5,
  min.signalblank.median.spl.pblk = 10,
  min.intensity.median.spl = 100,
  max.cv.conc.bqc = 25,
  features.to.keep = c(
    "CE 20:4", "CE 22:5", "CE 22:6", "CE 16:0", "CE 18:0"
  )
)
! %CV not computed for 4440 feature×QC-type×variable combinations with fewer than 3 replicates (LTR: 4440).
 QC metrics calculated for 502 features across 7 sample types, including normalized-intensity, concentration, and response-curve statistics.
! The QC parameter min.intensity.median.spl contains NAs for the following features: LPC O-22:1, PC 34:5, PC 35:1, SM 35:1|PC P_32:1 M+1, and SM 35:1|PC P-32:1 M+1. These features failed QC.
! The QC parameter min.signalblank.median.spl.pblk contains NAs for the following features: LPC O-22:1, PC 34:5, PC 35:1, PG 36:2, SM 35:1|PC P_32:1 M+1, and SM 35:1|PC .... These features failed QC.
! The QC parameter max.cv.conc.bqc contains NAs for the following features: Cer d18:1/12:0 (ISTD) [M-H20>264], Cer d18:1/25:0 (ISTD) [M-H20>264], Hex2Cer.... These features failed QC.
! The following features were forced to be retained despite not meeting filtering criteria: CE 16:0, CE 20:4, CE 22:5, and CE 22:6
 New feature QC filters were defined: 324 of 423 quantifier features meet QC criteria (not including the 25 quantifier ISTD features).

20. Summary of the QC filtering

The plot below summarises the filtering per lipid class. Green segments count the species that passed every criterion; the remaining segments count those that failed each one. Because the criteria are hierarchical, a species is attributed to the first filter it fails (for example CV), having already cleared the lower ones (S/B, LOD), so the counts partition the features rather than double-counting them.

Feature QC filter summary by lipid class

Figure 14. Feature QC filtering outcome per lipid class; green marks species passing all criteria.

The next plot gives the same picture across all features, with the total retained, and adds a Venn diagram showing how many features each individual criterion excluded, and where those exclusions overlap.

Overall feature filter summary with Venn diagram

Figure 15. Overall feature filtering summary with a Venn diagram of exclusion criteria.

21. Lipidome profile

As a final check, we plot the concentration profile of the filtered dataset. Comparing these values (the most abundant species, the summed concentration per class, or the ratios between classes) against in-house reference ranges or the literature is the quickest way to confirm that quantification landed in a plausible range and that no gross errors slipped through.

plot_abundanceprofile(
  mexp,
  variable = "conc",
  qc_types = "SPL",
  filter_data = TRUE,
  log_scale = TRUE,
  x_label = NA,
  feature_map = "lipidomics"
)
Concentration profile of the filtered lipidome

Figure 16. Concentration profile of the filtered study-sample lipidome.

22. Saving a report with data, metadata and processing details

The full post-processing can be written to a formatted Excel workbook, with separate sheets for the raw and processed datasets, the associated metadata, the per-feature QC metrics, and a record of the processing steps applied: a self-contained account of how the numbers were produced.

save_report_xlsx(mexp, path = tempfile(fileext = ".xlsx"))
 The data processing report of experiment 'sPerfect' has been saved to /tmp/RtmppSsxsf/file35305bcd3377.xlsx.

For downstream statistics it is often easier to export a single flat, wide CSV of a chosen data subset. This is the format used to share the data for the statistical analysis in the next part of the workshop.

save_dataset_csv(
  mexp,
  path = tempfile(fileext = ".csv"),
  variable = "conc",
  qc_types = "SPL",
  include_qualifier = FALSE,
  filter_data = TRUE
)

23. Sharing the MRMhubExperiment dataset

Finally, the whole mexp object can be serialized to an RDS file and shared. RDS files open in any R session (even without the mrmhub package installed) and the saved MRMhubExperiment can be reloaded for further processing, replotting, or inspection with mrmhub.

path <- tempfile(fileext = ".rds")
saveRDS(mexp, file = path, compress = TRUE)
my_saved_exp <- readRDS(file = path)
print(my_saved_exp)

Next steps