Untargeted Metabolomics: From LC-MS Data to Pathways With XCMS and MetaboAnalyst
A practical walkthrough of untargeted metabolomics: peak picking in XCMS, feature-table QC and statistics in MetaboAnalyst 6.0, and pathway annotation.
Every untargeted metabolomics study starts with a folder of raw LC-MS files and one unglamorous question: how do thousands of overlapping chromatographic peaks become a clean feature table you can run statistics on, then a biological story about which pathways moved? The gap between a .mzML file and a pathway figure is where most beginners get lost — raw data has no gene names, no obvious identities, just retention times, mass-to-charge ratios, and intensities. This post walks the full pipeline: signal processing in XCMS, statistics and annotation in MetaboAnalyst 6.0, and the interpretation traps in between.
What "Untargeted" Actually Means Here
In targeted metabolomics you decide in advance which 50 or 200 metabolites to measure and optimize the instrument for exactly those. Untargeted flips that: you acquire everything the mass spectrometer can detect in a defined m/z range, then let software discover the features. The upside is discovery. The catch is that a "feature" is not a metabolite. A single compound can generate many features: the protonated ion [M+H]+, adducts like [M+Na]+, isotope peaks, and in-source fragments. A typical human plasma run in positive mode yields 5,000 to 15,000 features from perhaps a few hundred real metabolites. Keep that ratio in mind before over-interpreting a volcano plot full of redundant signals.
The workhorse data format is .mzML, the open standard you get by converting vendor files (Thermo .raw, Agilent .d, Waters .raw) with ProteoWizard's msconvert. Convert with centroiding turned on — profile-mode data will inflate peak-picking time and memory dramatically.
Peak Picking and Alignment in XCMS
XCMS is the R/Bioconductor package that turns raw scans into a feature table. It runs as three conceptual stages, and understanding each one is the difference between a good table and garbage.
First, peak detection finds chromatographic peaks in each file independently. For high-resolution LC-MS the algorithm of choice is centWave, which works in the mass-time plane rather than binning m/z. Its two most consequential parameters are ppm (the expected mass accuracy of your instrument — set it to roughly 2 to 3 times the real accuracy, so ppm = 15 for a 5 ppm Orbitrap) and peakwidth (the expected chromatographic peak width range in seconds, e.g. c(5, 30) for UHPLC). Set peakwidth too narrow and you shred real peaks; too wide and you merge neighbors.
Second, retention time alignment (also called correction) corrects for retention-time drift across a batch. obiwarp aligns full profiles and is robust when samples differ a lot; the peak-groups method aligns using well-behaved features present in most samples.
Third, correspondence groups peaks across samples into features using density-based grouping (PeakDensity), controlled by bw (retention-time bandwidth) and binSize (m/z width of a group). After correspondence, fillChromPeaks() goes back to the raw data to integrate signal for samples where a feature was missing, so your table has real zeros instead of NA holes.
Do not tune XCMS parameters by eye on one file and hope. Use a tool like IPO or the newer parameter-optimization helpers to grid-search ppm, peakwidth, and bw against your own pooled QC samples. The single most common cause of a bad feature table is peakwidth copied from a tutorial that used a completely different chromatography gradient.
Where asari Fits
XCMS is powerful but slow and memory-hungry on large cohorts, and its parameters are famously finicky. asari is a newer Python peak-picker built for speed and reproducibility on high-resolution data. It leans on the mass-tracking idea — following accurate-mass traces across scans — and does automatic mass calibration, which reduces the parameter-tuning burden. For a study with hundreds of injections, asari can produce a feature table in a fraction of the wall-clock time. A pragmatic pattern in 2025-2026 is asari for fast, scalable feature extraction and XCMS when you need its mature ecosystem of adduct/isotope annotation packages like CAMERA or cliqueMS.
| Tool | Language | Best for | Parameter burden |
|---|---|---|---|
XCMS | R / Bioconductor | Mature ecosystem, annotation add-ons | High (needs IPO tuning) |
asari | Python | Large cohorts, speed, auto-calibration | Low |
MZmine | Java (GUI) | Visual, interactive workflows | Medium |
Whichever you pick, install it in an isolated environment. R/Bioconductor and Python bioinformatics stacks both break easily when dependencies collide — see managing conda environments and Bioconda channels for a setup that keeps XCMS and asari from stepping on each other.
The Feature Table Is the Handoff
The artifact that leaves signal processing and enters statistics is a matrix: features (rows, each an m/z × retention-time pair) by samples (columns), filled with peak intensities, plus a metadata column assigning each sample to a group. MetaboAnalyst expects a CSV with samples in rows or columns and a class label. Before you upload, do the sanity checks that separate a real result from an artifact:
- Blank filtering: drop features whose intensity in your solvent blanks is more than, say,
20%of the sample average — these are contaminants, not biology. - QC-based filtering: pooled QC samples (aliquots of every sample mixed together) are injected periodically. A trustworthy feature should have a relative standard deviation across QCs below
20-30%. Anything noisier is measurement noise masquerading as signal. - Detection rate: keep features present in a minimum fraction of samples in at least one group.
This QC discipline is not optional busywork. Untargeted metabolomics has a high false-discovery risk precisely because features are cheap and identities are hard.
Statistical Analysis in MetaboAnalyst 6.0
MetaboAnalyst 6.0 is the web platform (also an R package, MetaboAnalystR) that has become the default hub for the statistics-through-pathways half of the workflow. Upload your feature table, tell it the data type (peak intensity table), and walk the modules in order.
Normalization comes first and matters enormously: you apply three fixes for three different problems — sample normalization (reference-sample or median/sum scaling) for differences in total material; a data transformation (log transformation) to tame the right-skewed, multiplicative nature of intensity data; and a scaling step (Pareto or auto-scaling) so low-abundance metabolites aren't drowned out by a handful of huge peaks. Pareto scaling is the common default for LC-MS because it dampens large peaks without over-inflating noise.
Then explore and test. PCA on the QC samples is your first look — they should cluster tightly in the center; if QCs scatter, your batch has drift you must correct before trusting anything. For group comparison, MetaboAnalyst offers univariate tests (t-test/ANOVA with FDR correction, volcano plots) and multivariate models like PLS-DA and OPLS-DA. Report the FDR-adjusted p-value, not the raw one, at a threshold like q < 0.05. Treat PLS-DA VIP > 1 as a ranking aid, never as proof — PLS-DA will happily find separation in pure noise.
OPLS-DA plots look gorgeous and almost always show clean separation between groups. That is exactly why they are dangerous. A model that separates two groups of random numbers is trivial to build. Before you believe an OPLS-DA result, run the permutation test that MetaboAnalyst provides and confirm the observed statistics beat the permuted null — an R2/Q2 that doesn't stand out from permutations means your separation is overfitting, not biology.
From Features to Metabolite Identities
Here is the hard truth of untargeted work: an m/z value is not an identity. To name a feature you match its accurate mass (and ideally its MS/MS fragmentation spectrum) against databases like HMDB, METLIN, MassBank, or GNPS. A mass match alone gives you a putative annotation — Metabolomics Standards Initiative Level 2 or 3 — while a confirmed identity (Level 1) requires matching retention time and MS/MS to an authentic standard run on your own instrument. Most untargeted papers honestly report Level 2 annotations.
MetaboAnalyst sidesteps some of this with its functional analysis: the mummichog algorithm and GSEA-style approaches predict pathway activity directly from a list of m/z features without requiring you to name each metabolite first. The insight is that if many features map to metabolites in the same pathway, that collective evidence is more robust than any single shaky annotation.
Pathway and Enrichment Annotation
The final module maps confirmed or predicted metabolites onto KEGG pathways and runs enrichment. Pathway analysis in MetaboAnalyst combines enrichment (are my hits over-represented in a pathway?) with topology (do my hits sit at important, high-degree nodes?), producing the familiar bubble plot where the y-axis is -log10(p) and the x-axis is pathway impact. The logic mirrors what transcriptomics does with genes — if you have done over-representation or GSEA-style enrichment with clusterProfiler, the statistical machinery of the hypergeometric test and FDR control will feel familiar; only the entities changed from genes to metabolites.
Two caveats keep pathway results honest. First, KEGG's metabolic coverage is uneven — well-studied pathways like glycolysis are richly annotated, exotic ones barely at all, so absence of enrichment is not absence of biology. Second, the same feature can be assigned to different metabolites in different pathways, so cross-check high-impact hits against your MS/MS evidence before writing the discussion. Metabolomics pairs naturally with other omics; layering it onto bottom-up mass-spec proteomics from the same samples turns a list of moved metabolites into a mechanistic hypothesis.
Practical Takeaways
- Convert to centroided
.mzMLfirst withmsconvert; profile data will cripple peak picking on both time and memory. - Tune XCMS to your own chromatography —
ppm,peakwidth, andbwcopied from a tutorial are the number-one cause of bad feature tables. Optimize against pooled QCs. - QC-filter before you upload: drop features with QC relative standard deviation above
~20-30%and those dominated by blank signal. Cheap features, expensive identities. - Normalize deliberately — sample normalization, log transformation, and Pareto scaling each fix a different problem; skipping them distorts every downstream test.
- Always permutation-test PLS-DA/OPLS-DA. A clean separation plot proves nothing on its own.
- Report annotation confidence honestly — a mass match is a putative (Level 2/3) ID, not a confirmed one, and mummichog predicts pathways without requiring per-metabolite names.
If your environment isn't set up yet, start there: get an isolated toolchain running with Anaconda on macOS so XCMS, asari, and their Bioconductor dependencies each live in a reproducible env before you touch a single raw file.