xcms 4.11.5
Package: xcms
Authors: Ossama Edbali, Johannes Rainer
Modified: 2026-10-06 13:45:57.780347
Compiled: Thu Oct 8 21:13:55 2026
Visualization of mass spectrometry data is an important component of LC-MS data analysis, both for quality control and for interpretation and communication of results. Throughout a metabolomics or proteomics workflow, visualization can help identify problems with data acquisition and processing, such as retention-time shifts, signal drift, differences in signal intensity between samples, and incorrect or poorly defined chromatographic peaks. It also provides an essential means of inspecting detected features and communicating results.
xcms provides a range of visualization functions for inspecting LC-MS data and the results of chromatographic peak detection, retention-time correction, and feature definition. These functions cover many common quality-control and diagnostic use cases and are closely integrated with the xcms data structures and processing workflow. For more complex visualizations, however, users may benefit from the flexibility of the ggplot2 grammar of graphics, particularly when combining multiple samples, annotations, experimental metadata, or several graphical layers into a single figure.
lcmsPlot has been developed to facilitate the visualization of LC-MS raw and processed data in a consistent and flexible manner across different software tools and processing workflows. By providing a common ggplot2-based framework for LC-MS visualization, it enables users to apply a consistent graphical approach to data generated or processed by different packages and at different stages of an analysis workflow.
This vignette illustrates how lcmsPlot can be used together with xcms to generate flexible, reproducible, and publication-ready visualizations of LC-MS data.
For a more thorough description of the package, including its support for raw files and for the outputs of other LC-MS software, see the lcmsPlot vignette.
lcmsPlot is a Bioconductor package and can be installed with BiocManager:
if (!requireNamespace("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install("lcmsPlot")
library(xcms)
library(lcmsPlot)
Throughout this vignette we use the preprocessing result that is shipped with xcms and that is described in detail in the main xcms vignette. It is derived from a subset of the data from [1]; the raw files are provided by the faahKO package.
Using loadXcmsData() we get the fully preprocessed object directly, so that
chromatographic peak detection, alignment and correspondence do not have to be
repeated here.
xdata <- loadXcmsData("xdata")
The object contains eight samples assigned to the two sample groups "KO" and
"WT":
Biobase::pData(xdata)
## sample_name sample_group sample_type
## 1 ko15 KO QC
## 2 ko16 KO study
## 3 ko21 KO study
## 4 ko22 KO QC
## 5 wt15 WT study
## 6 wt16 WT study
## 7 wt21 WT QC
## 8 wt22 WT study
Base peak chromatograms, total ion chromatograms, and total ion current plots are useful in LC-MS because they simplify complex datasets, highlight the most intense signals, and enable rapid detection and comparison of chromatographic features.
In the examples below we plot the base peak and total ion chromatograms for the eight samples (faceted):
lcmsPlot(xdata, sample_id_column = "sample_name") +
lp_chromatogram(aggregation_fun = "max") +
lp_facets(facets = "sample_name", ncol = 4)
lcmsPlot(xdata, sample_id_column = "sample_name") +
lp_chromatogram(aggregation_fun = "sum") +
lp_facets(facets = "sample_name", ncol = 4)
We can also plot multiple chromatograms in the same plot, overlapped. In the example below, base peak chromatograms are computed for each sample, grouped by sample group, and displayed together in one panel.
lcmsPlot(xdata, sample_id_column = "sample_name") +
lp_chromatogram(aggregation_fun = "max") +
lp_arrange(group_by = "sample_group") +
lp_labels(title = "Base peak chromatograms", legend = "Sample group") +
lp_legend(position = "bottom")
Below, we generate a total ion current plot for the eight samples under consideration:
lcmsPlot(xdata, sample_id_column = "sample_name") +
lp_total_ion_current(type = "violin") +
lp_arrange(group_by = "sample_group") +
lp_labels(title = "Total ion current", legend = "Sample group")
These plots show the overall amount of ion signal entering the detector at any moment, widely used for quality control and method assessment.
lcmsPlot(xdata, sample_id_column = "sample_name") +
lp_chromatogram(features = rbind(c(
mzmin = 334.9,
mzmax = 335.1,
rtmin = 2700,
rtmax = 2900))) +
lp_arrange(group_by = "sample_group") +
lp_labels(legend = "Sample group")
The above plot can be gridded on metadata factors; in the plot below we arrange
feature_id along the rows and sample_id along the columns with lp_grid(),
and detected chromatographic peaks are highlighted using the highlight_peaks
parameter:
lcmsPlot(xdata, sample_id_column = 'sample_name') +
lp_chromatogram(
features = rbind(
mz334 = c(
mzmin = 334.9, mzmax = 335.1,
rtmin = 2700, rtmax = 2900),
mz278 = c(
mzmin = 278.99721, mzmax = 279.00279,
rtmin = 2740, rtmax = 2840)
),
highlight_peaks = TRUE,
highlight_peaks_color = '#f00') +
lp_grid(rows = 'feature_id', cols = 'sample_id', free_y = TRUE)
The same figure can be produced with the plotting functions of xcms itself.
The extracted ion chromatograms are first created with the chromatogram()
function, which returns an XChromatograms object with one chromatogram per
feature (row) and sample (column), and the detected chromatographic peaks
attached to each of them.
mzr_xic <- rbind(c(334.9, 335.1), c(278.99721, 279.00279))
rtr_xic <- rbind(c(2700, 2900), c(2740, 2840))
chrs_xic <- chromatogram(xdata, mz = mzr_xic, rt = rtr_xic)
## Define one color per sample group
group_colors <- RColorBrewer::brewer.pal(3, "Set1")[1:2]
names(group_colors) <- c("KO", "WT")
sample_colors <- group_colors[chrs_xic$sample_group]
## Label the rows the same way lcmsPlot names its features
feature_ids <- sprintf("M%dT%d", round(rowMeans(mzr_xic)),
round(rowMeans(rtr_xic)))
## One panel per feature (row) and sample (column)
par(mfrow = c(nrow(chrs_xic), ncol(chrs_xic)),
mar = c(3, 3, 1.8, 0.4), mgp = c(1.9, 0.6, 0), cex = 0.7)
for (i in seq_len(nrow(chrs_xic))) {
for (j in seq_len(ncol(chrs_xic))) {
plot(chrs_xic[i, j], col = sample_colors[j],
peakType = "polygon", peakCol = paste0(sample_colors[j], 60),
peakBg = paste0(sample_colors[j], 40),
main = paste(feature_ids[i], chrs_xic$sample_name[j]))
}
}
The result is equivalent to the lp_grid() figure above, but the layout has to
be set up explicitly: par(mfrow = ...) defines the grid, the two nested loops
address the individual cells chrs_xic[i, j] of the XChromatograms object,
and the panel titles and per-sample colors have to be assembled by hand. Each
panel also keeps its own y axis, which corresponds to free_y = TRUE in
lp_grid().
In lcmsPlot, you can also plot directly from an XChromatograms object. In
the example below, two features are defined by their m/z ranges and the
corresponding retention time windows. The resulting plot is arranged as a grid
with features as rows and samples as columns.
mzr <- matrix(c(344, 344, 360, 360), ncol = 2, byrow = TRUE)
rtr <- matrix(c(2500, 2880, 2600, 2880), ncol = 2, byrow = TRUE)
chrs <- chromatogram(xdata, mz = mzr, rt = rtr)
rownames(chrs) <- c("mz344", "mz360")
lcmsPlot(chrs) +
lp_chromatogram(na.rm = TRUE) +
lp_grid(rows = "feature_id", cols = "sample_id")
lp_peak_count_image() bins the retention-time axis and shows how many
chromatographic peaks each sample yielded in each bin, with samples on the y
axis and one tile per sample and bin. It is the counterpart of
xcms::plotChromPeakImage().
lcmsPlot(xdata, sample_id_column = "sample_name") +
lp_peak_count_image(bin_size = 30) +
lp_labels(title = "Chromatographic peaks per 30 s bin")
lp_chrom_peak_rects() draws one rectangle per detected peak, spanning its
retention-time limits by its m/z limits, and shows where in the m/z /
retention-time plane the peak detection actually placed its boundaries.
lcmsPlot(xdata, sample_id_column = "sample_name") +
lp_chrom_peak_rects(
sample_ids = c("ko15", "wt15"),
rt_range = c(3200, 3700),
mz_range = c(480, 540),
fill = "#c0392b60")
The correspondence analysis performed on this data set grouped the
chromatographic peaks of the individual samples into features, i.e. groups of
peaks that likely originate from the same ion. Each feature is identified by
the corresponding row name of featureDefinitions(xdata), by default
"FT001", "FT002", and so on. These identifiers can be passed to the
features parameter of lp_chromatogram() to extract and plot the
corresponding chromatograms across all samples without having to specify m/z
and retention time ranges manually. The ppm and rt_tol parameters define
how far around the feature’s consensus m/z and retention time the signal is
extracted.
lcmsPlot(xdata, sample_id_column = 'sample_name') +
lp_chromatogram(
features = c('FT002', 'FT004', 'FT019', 'FT027'),
ppm = 10,
rt_tol = 80,
highlight_peaks = TRUE,
highlight_peaks_factor = "sample_group") +
lp_arrange(group_by = 'sample_group') +
lp_facets(facets = 'feature_id', ncol = 2, free_x = TRUE, free_y = TRUE) +
lp_labels(title = "Four selected features", legend = "Sample") +
lp_legend(position = "bottom")
Peak density plots show how the detected chromatographic peaks of an m/z window
are distributed along the retention time axis. Each detected peak is drawn as a
point at the y position of its sample and the kernel density estimate of the
peak apex retention times is overlaid as a line. This is the diagnostic used by
the peak density correspondence method and makes it easy to judge whether the
peaks of the different samples are well aligned and whether a feature is
tightly defined. It is the lcmsPlot counterpart of
xcms::plotChromPeakDensity().
When lp_peak_density() follows lp_chromatogram(), the features argument
is inherited from the chromatogram layer and does not have to be repeated. The
bw parameter should match the bandwidth used during correspondence, and when
min_fraction is given the grouping is simulated so that the retention time
regions that would be defined as features are drawn as semi-transparent
rectangles.
lcmsPlot(xdata, sample_id_column = "sample_name") +
lp_chromatogram(
sample_ids = c("ko15", "ko16", "wt16", "wt21"),
features = rbind(c(
mzmin = 334.9, mzmax = 335.1, rtmin = 2700, rtmax = 2900)),
highlight_peaks = TRUE
) +
lp_peak_density(
bw = 30,
min_fraction = 0.5
) +
lp_labels(legend = "Sample ID")
Spectra are added to a plot with the lp_spectra() function. Its mode
parameter determines which scan a spectrum is extracted from.
With mode = "closest" the spectrum is taken from the scan closest to the
retention time given by the rt parameter. The retention time at which the
spectrum was acquired is marked with a vertical dashed line on the
chromatogram, so that the spectrum can be related to the chromatographic peak
it belongs to.
lcmsPlot(xdata, sample_id_column = "sample_name") +
lp_chromatogram(
features = "FT034",
sample_ids = "ko15",
ppm = 20,
rt_tol = 60,
highlight_peaks = TRUE) +
lp_spectra(rt = 2789, mode = "closest", ms_level = 1) +
lp_labels(title = "Feature FT034", legend = "Sample") +
lp_legend(position = "bottom")
With mode = "closest_apex" the retention time of the peak maximum is
determined from the chromatographic peaks detected by xcms and the nearest
scan is used, which removes the need to specify a retention time manually.
In the example below, chromatograms are extracted for two features in one
sample and the MS1 spectrum at the apex of each is displayed below its
chromatogram. The lp_layout() function is used to give the spectra more
vertical space than the chromatograms.
lcmsPlot(xdata, sample_id_column = "sample_name") +
lp_chromatogram(
features = c("FT219", "FT250"),
sample_ids = "ko15",
ppm = 20,
rt_tol = 60,
highlight_peaks = TRUE) +
lp_spectra(mode = "closest_apex", ms_level = 1) +
lp_facets(facets = "feature_id", ncol = 2) +
lp_labels(legend = "Sample") +
lp_legend(position = "bottom") +
lp_layout(design = "C\nS\nS")
Each spectrum is dominated by the m/z of its own feature, accompanied by the corresponding isotope peaks.
To follow how the spectral composition changes over the duration of a
chromatographic peak, several scans can be extracted across it with
mode = "across_peak". The interval parameter defines the retention time
spacing (in seconds) between the selected scans.
lcmsPlot(xdata, sample_id_column = "sample_name") +
lp_chromatogram(
features = "FT083",
sample_ids = "ko15",
ppm = 20,
rt_tol = 60,
highlight_peaks = TRUE) +
lp_spectra(mode = "across_peak", interval = 15, ms_level = 1) +
lp_labels(legend = "Sample") +
lp_legend(position = "bottom") +
lp_layout(design = "C\nS\nS")
This view makes co-elution visible: the ion of the feature dominates the spectrum at the beginning of the peak, while towards its end other ions become the most intense signals of the scan.
Spectra can also be plotted on their own, without an accompanying chromatogram,
which is useful for a detailed comparison of isotope patterns or relative ion
intensities between samples. Standalone spectra are created by using
lp_spectra() without lp_chromatogram().
lcmsPlot(xdata, sample_id_column = "sample_name") +
lp_spectra(
sample_ids = c("ko15", "wt15"),
rt = 3323,
mode = "closest",
ms_level = 1) +
lp_labels(title = "MS1 spectra at 3323 seconds") +
lp_legend(position = "bottom")
sessionInfo()
## R version 4.6.1 (2026-06-24)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 24.04.5 LTS
##
## Matrix products: default
## BLAS: /home/biocbuild/bbs-3.24-bioc/R/lib/libRblas.so
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0 LAPACK version 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] stats4 stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] lcmsPlot_1.1.13 MetaboCoreUtils_1.21.1 MsBackendMgf_1.21.1
## [4] MsExperiment_1.15.0 ProtGenerics_1.45.0 pander_0.6.6
## [7] Spectra_1.23.5 MsDataHub_1.13.3 MassSpecWavelet_1.79.2
## [10] pheatmap_1.0.13 faahKO_1.53.0 MSnbase_2.39.6
## [13] S4Vectors_0.51.10 Biobase_2.73.2 BiocGenerics_0.59.12
## [16] generics_0.1.4 mzR_2.47.1 Rcpp_1.1.2
## [19] MsFeatures_1.21.0 xcms_4.11.5 BiocParallel_1.47.0
## [22] BiocStyle_2.41.0
##
## loaded via a namespace (and not attached):
## [1] RColorBrewer_1.1-3 jsonlite_2.0.0
## [3] MultiAssayExperiment_1.39.1 magrittr_2.0.5
## [5] magick_2.9.1 farver_2.1.2
## [7] MALDIquant_1.22.3 rmarkdown_2.32
## [9] fs_2.1.0 vctrs_0.7.3
## [11] memoise_2.0.1 tinytex_0.61
## [13] BiocBaseUtils_1.15.1 htmltools_0.5.9
## [15] S4Arrays_1.13.2 progress_1.2.3
## [17] AnnotationHub_4.3.2 curl_8.0.0
## [19] signal_1.8-1 SparseArray_1.13.4
## [21] mzID_1.51.0 sass_0.4.10
## [23] bslib_0.12.0 plyr_1.8.9
## [25] httr2_1.3.0 impute_1.87.0
## [27] cachem_1.1.0 igraph_2.3.4
## [29] lifecycle_1.0.5 iterators_1.0.14
## [31] pkgconfig_2.0.3 Matrix_1.7-6
## [33] R6_2.6.1 fastmap_1.2.0
## [35] MatrixGenerics_1.25.0 clue_0.3-68
## [37] digest_0.6.39 pcaMethods_2.5.0
## [39] patchwork_1.3.2 AnnotationDbi_1.75.2
## [41] ExperimentHub_3.3.2 GenomicRanges_1.65.4
## [43] RSQLite_3.53.3 labeling_0.4.3
## [45] filelock_1.0.3 httr_1.4.9
## [47] abind_1.4-8 compiler_4.6.1
## [49] withr_3.0.3 bit64_4.8.6
## [51] doParallel_1.0.17 S7_0.2.2
## [53] PTMods_1.1.0 DBI_1.3.0
## [55] Chromatograms_1.3.4 MASS_7.3-66
## [57] rappdirs_0.3.4 DelayedArray_0.39.8
## [59] tools_4.6.1 PSMatch_1.17.0
## [61] otel_0.2.0 glue_1.8.1
## [63] QFeatures_1.23.2 grid_4.6.1
## [65] cluster_2.1.8.3 reshape2_1.4.5
## [67] gtable_0.3.6 preprocessCore_1.75.1
## [69] tidyr_1.3.2 data.table_1.18.6.1
## [71] hms_1.1.4 XVector_0.53.0
## [73] BiocVersion_3.24.0 foreach_1.5.2
## [75] pillar_1.11.1 stringr_1.6.0
## [77] limma_3.99.0 dplyr_1.2.1
## [79] BiocFileCache_3.3.0 lattice_0.23-1
## [81] bit_4.6.0 tidyselect_1.2.1
## [83] Biostrings_2.81.9 knitr_1.52
## [85] bookdown_0.48 IRanges_2.47.5
## [87] Seqinfo_1.3.2 SummarizedExperiment_1.43.0
## [89] xfun_0.61 statmod_1.5.2
## [91] matrixStats_1.5.0 stringi_1.8.9
## [93] lazyeval_0.2.3 yaml_2.3.12
## [95] evaluate_1.0.5 codetools_0.2-20
## [97] MsCoreUtils_1.25.4 tibble_3.3.1
## [99] BiocManager_1.30.27 cli_3.6.6
## [101] affyio_1.83.0 jquerylib_0.1.4
## [103] dichromat_2.0-1 dbplyr_2.6.0
## [105] png_0.1-9 XML_3.99-0.25
## [107] parallel_4.6.1 ggplot2_4.0.3
## [109] blob_1.3.0 prettyunits_1.2.0
## [111] AnnotationFilter_1.37.0 viridisLite_0.4.3
## [113] scales_1.4.0 affy_1.91.0
## [115] ncdf4_1.24 purrr_1.2.2
## [117] crayon_1.5.3 rlang_1.3.0
## [119] vsn_3.81.1 KEGGREST_1.53.6