andes documentation¶
The full reference for the andes binary and its outputs; install and quick start are in
README.md. andes --help shows the flags a normal run needs. The rest are
advanced: hidden from --help because the engine derives them and reports what it chose.
Use them to reproduce a measurement or override a derivation.
Advanced (hidden) flags include: --fragment-index, --gbdt-max-trees,
--peak-filter, --ethcd-activation, --isotope-error, and the glyco tuning group
(--glyco-tol-ppm, --glyco-retrieval-tol-ppm, --glyco-max-peaks, --glyco-y-max-charge,
--glyco-cz-max-charge, --glyco-scans).
1. CLI reference¶
Flags are kebab-case long options, defined in crates/andes/src/bin/andes/cli.rs.
Input formats¶
--spectrum picks the reader from the file extension; there is no format flag.
| Extension | Reader | Build requirement | Runtime requirement | Notes |
|---|---|---|---|---|
.mzML / .mzml |
mzML (streaming) | always built | none | Full activation + instrument auto-detection (§4). |
.raw |
Thermo RawFileReader | --features thermo |
.NET 8 (bundled in release archives) | Same results as the equivalent mzML. Supports --chimeric. |
.d |
Bruker timsTOF (timsrust) |
--features timstof |
none | DDA-PASEF, MS2 only; runs from early acquisition software (frame compression type 1, about 2018 and older) are not readable, convert those to mzML. TOF fragmentation is beam-type CID, so it routes to a high-res HCD model (hcd_qexactive_tryp or its protocol variant until a TOF model ships), 20 ppm feature tolerance. --chimeric / --precursor-cal degrade to a normal search. |
any other (e.g. .mgf) |
MGF | always built | none | No metadata; MS2, model from flags. |
Native .raw/.d search MS2 only (MS3 reporter scans are filtered at load).
Required¶
| Flag | Type | Default | Description | Legacy form |
|---|---|---|---|---|
--spectrum |
path | (required) | Input spectrum file; reader chosen by extension (see above). | Java -s <FILE> |
--database |
path | (required) | Target FASTA, or UniProt XML when the name ends in .xml / .xml.gz (§1a). Gzip is read directly. Decoys are generated (see --decoy-strategy, --decoy-prefix). |
Java -d <FILE> |
--output-pin |
path | (required) | Output Percolator .pin. |
Java -o <FILE> (when -outputFormat pin) |
Search parameters¶
| Flag | Type | Default | Description | Legacy form |
|---|---|---|---|---|
--precursor-tol |
string | 20ppm |
Symmetric precursor tolerance, e.g. 20ppm or 0.02da. |
Java -t 20ppm |
--enzyme |
enum | trypsin |
trypsin, chymotrypsin, lysc, aspn, gluc, lysn, argc, alphalp, nocleavage, nonspecific (alias elastase). A comma list (gluc,trypsin) uses every enzyme listed. |
Java -e |
--charge |
MIN..MAX |
2..5 |
Charges tried when the spectrum has none. | (no direct Java flag; set via param file in Java) |
--enzyme-specificity |
enum | fully |
Tolerable termini: fully (Java -ntt 2), semi (-ntt 1), non-specific (-ntt 0). |
Java -ntt |
--max-missed-cleavages |
u32 | 1 |
Missed cleavages per peptide. Not applied to a non-specific digest (--enzyme nonspecific, --enzyme-specificity non-specific), where every position is a cleavage site. |
Java -maxMissedCleavages 1 |
--min-length |
u32 | 6 |
Minimum peptide length. | Java -minLength 6 |
--max-length |
u32 | 50 |
Maximum peptide length. | Java -maxLength 40 |
--top-n |
u32 | 10 |
PSMs kept per spectrum (before positional isomers are collapsed in the output, see §3a). | Java -n 10 |
--isotope-error |
MIN..MAX |
-1..2 |
Isotope-error offsets tried. | Java -ti -1,2 |
--min-peaks |
u32 | 10 |
Spectra with fewer MS2 peaks are skipped. | Java -minNumPeaks 10 |
Modifications¶
| Flag | Type | Default | Description | Legacy form |
|---|---|---|---|---|
--mods |
path | (off) | Java-format mods.txt (§2). Without it: Carbamidomethyl C (fixed), Oxidation M (variable) and Acetyl on the protein N-terminus (variable), max 3 variable mods per peptide. Numeric Da masses only. |
Java -mod <FILE> |
Scoring¶
| Flag | Type | Default | Description | Legacy form |
|---|---|---|---|---|
--fragmentation |
enum | auto |
auto, CID, ETD, HCD, UVPD. auto reads the file (§4); on MGF it falls back to CID and warns. |
Java -m |
--protocol |
enum | auto |
auto, phospho, iTRAQ, iTRAQ-phospho, TMT, standard. An explicit value selects the protocol model. auto keeps the model; if it finds TMT/iTRAQ reporter ions it turns on the isobaric peak filter and (with no --mods) adds the tag as a fixed mod. |
Java -protocol |
--score |
enum | auto |
What ranks candidates and fills RawScore: rank (low-res), strong (high-res), or auto (by the model's instrument class). |
— |
--gbdt-max-trees |
u32 | 100 |
Trees per GBDT ensemble, 0 = all. 100 is 33–41% faster and identification-neutral (2026-09). --glyco uses all trees unless set. |
— |
--peak-filter |
WINDOW_DA:PEAKS |
protocol default | Keep the PEAKS most intense peaks per WINDOW_DA. Unset = 100:20 for isobaric data, else off; window 0 forces off. |
— |
--ethcd-activation |
enum | hcd |
EThcD/ETciD routing: hcd (no EThcD model exists) or etd (c/z path). |
— |
--model-store |
path | (bundled) | Model store instead of resources/models/ (directory or one models.parquet). |
— |
--model |
string | (auto-select) | Load this model id, skipping selection. | — |
Model selection tries an exact (activation, instrument class, enzyme, protocol) match, then
drops the protocol, then takes the closest instrument class. The instrument class comes from
the file; use --model-store plus --model for an unbundled model.
Calibration¶
| Flag | Type | Default | Description | Legacy form |
|---|---|---|---|---|
--precursor-cal |
enum | off |
off, auto, on. A pre-pass learns a ppm shift from confident PSMs and tightens the precursor tolerance; auto skips it on small samples. Off by default: on the benchmark sets the pre-pass took 9–17% of the run and never fired, and where it fired (UPS1, larger sample) the tightened window lost 7.6% of PSMs. Skipped (with a warning) on .raw and .d. |
Java -precursorCal auto\|on\|off |
Candidate retrieval is automatic. andes builds the candidate
index out-of-core (cached as andes-candidx-<hash>.bin in --index-cache-dir (default: the directory of the output files), ~90–250 MB
on the benchmark databases, reused by later runs on the same database and settings) and retrieves
the 100 candidates whose singly-charged b/y ions best match each spectrum (at least 3 matched).
On Astral this gave +22.2% PSMs over full-window enumeration at an equal true FDP of 1.00%; on
phospho, 164 s against 5,954 s. Low-res searches use the index too, but
only the 150 most intense peaks of each spectrum vote: with every peak voting, the 0.5 Da bins let
noise swamp the count (TMT 12,281 -> 4,368 PSMs at 1%). Capped, the index matches exhaustive
enumeration (TMT 12,347 vs 12,281 PSMs at 1%, UPS1 15,848 vs 15,838) at the same CPU and wall time
in less than half the memory (TMT 1.7 vs 5.9 GB). Every search uses the out-of-core index: --glyco
looks its candidates up by backbone mass in its own sequon-only table built from it, and the
--refine second pass searches an in-memory index of its anchored peptides. ETD and UVPD spectra
score every candidate in each precursor window, because the b/y-only fragment-ion index does not
hold their c/z and a/x ions. The engine prints
its choice; --fragment-index off scores every candidate in each precursor window instead
(benchmarks).
Runtime¶
| Flag | Type | Default | Description | Legacy form |
|---|---|---|---|---|
--threads |
usize | logical CPU count | Worker threads. | Java -thread N |
--ms-level |
u8 | 2 |
MS level to search in mzML. .raw/.d, MGF and --chimeric always search MS2. |
— |
--temp-dir |
dir | output directory | Directory of the run's private temporary files (MS1 scans between searching and quantification, the --mbr directory without --quant-dir, the PIN of --keep-pin false); created if missing, emptied at exit; config key io.temp_dir. An unwritable directory is an error. |
— |
--index-cache-dir |
dir | output directory | Directory of the candidate-index cache (andes-candidx-<key>.bin), created if missing; config key search.index_cache_dir. A shared directory builds the index once for several tasks, a node-local one gives a cache per node. A directory that cannot be written is an error. |
— |
--max-spectra |
usize | 0 |
Bench mode: first N MS2 spectra only (0 = all); skips TSV output. |
— |
Output¶
| Flag | Type | Default | Description | Legacy form |
|---|---|---|---|---|
--output-tsv |
path | (off) | Tab-separated PSM report (§3b). | Java -outputFormat 1 with output path |
--output-parquet |
dir | (off) | QPX .idparquet/ bundle (§3e). |
— |
No environment variable changes a search result; the binary reads none. The test-harness
variables are listed in docs/ENV_VARS.md.
1a. Workflow parameters (grouped by experimental design)¶
Opt-in modes, each switched on by one parent flag.
Decoys & FDR strategy¶
For a pre-built target+decoy FASTA, use --decoy-strategy none with --decoy-prefix or
--decoy-suffix.
Decoys are peptide-level. Each target protein is cut into the segments between the cleavage
sites of the search's proteases (--enzyme, all of them for a multi-protease digest), and each
segment is permuted with three kinds of residue in place: the residues that make a cut (K and R
for trypsin), the residue on its cleavage side (its last residue, or its first for AspN and LysN)
and an initiator Met. Every enzymatic peptide, missed cleavages included, thus has a decoy at the
same position with the same composition, mass, length, C-terminal residue, missed cleavages and
modification sites (a phospho decoy has as many S/T/Y as its target), the way a wrong peptide of
the same digest looks. A segment's decoy depends only on its residues, so a peptide shared by
several proteins has one decoy. A segment whose decoy reads as any target segment (I and L
equal: a palindrome, or a permutation that spells another protein's peptide) is redrawn by a
seeded shuffle; a segment too short to change stays, and such a decoy peptide is counted as a
target. A non-specific search (--enzyme nonspecific, or --enzyme-specificity non-specific,
as for HLA peptides) has no segments; its decoys permute each whole sequence. A semi-specific
search uses the segments: its fully specific peptides keep their composition.
| Flag | Type | Default | Description |
|---|---|---|---|
--decoy-strategy |
enum | shuffle |
shuffle (seeded shuffle of each segment), reverse (pseudo-reverse each segment: PEPTIDEK → EDITPEPK), sequon, or none. Use sequon with --glyco: a permutation leaves fewer N-X-S/T sequons than the target, which makes glyco q-values anti-conservative; sequon shuffles and then restores each segment's sequon count. reverse is not the default because a pseudo-reversed peptide ending in K shares c/z ions with its target at every I/L (the decoy's z ion i+1 and the target's c ion i+1 differ by 0.024 Da), so on ETD spectra the decoy scores on its target's spectrum. |
--decoy-prefix |
string | XXX_ |
Accession prefix marking a decoy. |
--decoy-suffix |
string | (off) | Decoy accession suffix (quantms _rev convention). |
--decoy-seed |
u64 | fixed | (advanced) Seed of the decoy shuffles (shuffle, and the redraw of a segment whose decoy reads as a target). |
UniProt XML databases¶
--database also reads UniProtKB XML (uniprot_sprot.xml.gz, a proteome download, or
rest.uniprot.org/uniprotkb/<acc>.xml). Each entry becomes one protein with the UniProt FASTA
header (sp|P02769|ALBU_BOVIN, Albumin OS=Bos taurus OX=9913 GN=ALB PE=1 SV=4), so on its own
an XML database searches exactly like the UniProt FASTA. The XML also holds annotation that FASTA
drops. andes uses four kinds, all off until asked for: annotated modification sites, natural
variants, isoforms and mature chains. A file that ends inside an element (a truncated download),
or an XML file with no UniProt entries, is an error.
| Flag | Type | Default | Description |
|---|---|---|---|
--uniprot-variants |
bool | off | Add one target entry per natural sequence variant. Only peptides that carry the variant are enumerated from it. |
--uniprot-isoforms |
bool | off | Add one target entry per described isoform, rebuilt from the canonical sequence and its splice variants. |
--uniprot-chains |
bool | off | Add one target entry per mature chain or peptide with a terminus the precursor lacks. Only peptides at those new termini are enumerated from it. |
--uniprot-evidence |
enum | experimental |
Minimum UniProt evidence for a variant, an annotated site or a mature chain: experimental, curated, or any (see below). |
--knockoffs |
int | 4 |
Knockoff entries per single-residue variant of a canonical sequence, from --uniprot-variants or --annotations, and knockoff sites per annotated modification site (0: none). With rescoring they give variant PSMs a knockoff q-value, and variant PSMs whose variant has no knockoff are not accepted. See the README section on knockoff variants. |
Annotated modification sites. modified residue, lipid moiety-binding region and
glycosylation site features become sites with the monoisotopic mass of the named modification,
taken from UniProt's PTM vocabulary (ptmlist.txt). Interchain ubiquitin, NEDD8 and ISG15
cross-links on K become Gly-Gly sites (+114.0429). A site only matters to a variable mod declared
sites=annotated in the mods file (§2). That mod is then placed only on
residues the protein annotates with a modification of the same mass (±0.005 Da, which keeps
phospho and sulfo apart). Without that attribute a mod applies to every matching residue, as
before. A restricted mod therefore searches far fewer positions than the same mod applied
everywhere. The log reports, for each restricted mod, how many annotated sites of its mass the
database holds. It warns when there are none, because the mod is then never placed. Features with
no single mass (N-linked (GlcNAc...) asparagine, SUMO cross-links) are counted in the log and
not used.
Natural variants. Each usable variant becomes its own target protein holding the canonical
sequence with the change applied, e.g. sp|P04637_VAR_005851_D7H|P53_HUMAN. The middle field is
<accession>_<feature id>_<change>, or <accession>_VAR_<change> when UniProt gives no id. The
change is written D7H, KR120-121EE or F508del (1-based). Candidate generation keeps only
the spans that read differently from the canonical sequence. Such a span overlaps the replaced
residues, or holds both residues next to a deletion. For an indel inside a repeat (AQR →
AQQR), which could sit at more than one place in the run, the span must hold the whole run
around it. A replacement that keeps a run of the original residues between two changes
(KRAPEPTIDEKR → ERAPEPTIDEKE) counts each change on its own, so a peptide inside the kept run
(APEPTIDEK) is not a variant peptide. I and L count as the same residue throughout. Every hit on
a variant entry is therefore a hit on the variant, and its Proteins / Protein column names
it; the canonical peptides are not searched twice. Multi-allelic variants give one entry
per allele. These variants are skipped:
- the original residues do not match the canonical sequence;
- the position is unknown;
- the location is on an isoform that is not searched (see below);
- the variant deletes a protein terminus;
- the change is only I↔L, which no mass spectrum tells from the canonical sequence.
A peptide carries at most one variant. A peptide that does not contain the variant is not a variant peptide, even when the variant changes its cleavage context. For example, after an E→K variant the next peptide becomes tryptic, but it is not searched.
Isoforms (--uniprot-isoforms). Each described isoform of the entry's alternative products
becomes its own target protein, as in UniProt's canonical-and-isoform FASTA:
sp|P04637-2|P53_HUMAN, Isoform 2 of Cellular tumor antigen p53 OS=Homo sapiens OX=9606 GN=TP53.
Its sequence is the canonical one with the isoform's splice variant features applied. It
carries the canonical sites on the residues it shares with the canonical sequence, plus the sites
UniProt locates on the isoform itself. With --uniprot-variants, the variants UniProt locates on
the isoform become entries of their own, named after the isoform id
(<isoform id>_<feature id>_<change>). A peptide the isoform shares with the canonical sequence
lists both proteins, as with the FASTA. Isoforms are not
filtered by evidence: UniProt only describes an isoform it has curated. An isoform is skipped,
and counted in the log, when it is external or not described, or when one of its splice
variants is missing, does not fit the canonical sequence, or overlaps another.
Mature chains (--uniprot-chains). Removing a signal peptide, transit peptide, propeptide or
initiator Met gives the mature protein new termini. The peptides there are often not cleavage
products of the search enzyme in the precursor, and they can carry protein-terminal mods. Each chain or peptide feature with such a terminus becomes its own target protein
holding the chain's residues: sp|P02769_PRO_0000001058|ALBU_BOVIN, with
CHAIN=PRO_0000001058:25-607 appended to the description (CHAIN_<begin>-<end> when the
feature has no id). Only peptides that start at a new N-terminus or end at a new C-terminus are
enumerated from it. All other peptides of the chain are also in the precursor, so they are not
searched twice. The new termini are protein termini, so protein-terminal mods (ProtNTerm,
ProtCTerm) apply there, and the canonical sites on the chain's residues carry over. A chain
that starts right after the initiator Met adds nothing, because the Met-cleaved canonical
N-terminus is already searched. The search's own initiator-Met removal does not apply to chains. A chain is used
when it, or a processing feature cut at its new terminus, meets --uniprot-evidence. Swiss-Prot
gives most chain and processing features no evidence code. There they are manual annotation and
count as curated.
Decoys. The annotation travels with each decoy through the permutation that builds the decoy.
Sites stay on the same residues, so a decoy has as many annotated sites as its target, on the
same residue types. A site UniProt ties to a terminus also stays terminal. If the target carries
it at its protein terminus (an N-acetylation or pyro-Glu of residue 1, an N-acetylation of
residue 2 after the initiator Met, a C-terminal amidation), the decoy is built with that residue,
and the initiator Met before it, at its own terminus. A residue-specific protein-terminal mod
restricted to annotated sites (pyro-Glu on Q) thus has decoy competition. A variant part maps to
its image when that is contiguous (a part between two fixed residues of a segment, under
reverse). Otherwise it maps to a part of the same length at the image of its first residue,
moved back from the end if needed. Variant entries thus get decoy twins with the same kind of
variant parts, and the target and decoy search spaces grow together. An isoform's decoy is
built like any protein's, so it shares the decoy peptides of the segments it shares with its
canonical entry. A chain is cut into segments like any protein, from its own new termini, so
each terminal peptide of the target chain has a decoy peptide of the same length and mass.
Provenance columns. Each PIN row says which kinds of entry its peptide comes from:
IsVariant, IsIsoform, IsChain are 1 when any protein of the peptide is such an entry, also
when the canonical entry has the same peptide. (Counting a shared peptide as canonical would set
the flags on decoys more often than on targets, because decoy entries do not always keep the
peptides their targets share, and Percolator would learn the label.) A peptide is specific evidence for an
alternative sequence only when its Proteins column names no canonical entry. RefCompetitor
is 1 when a canonical candidate with another peptidoform was scored for the spectrum, and
DeltaRefScore is a flagged row's score minus the best such candidate's: how much better the
alternative explains the spectrum than the best canonical peptide. IsotopeMimic is 1 when the precursor fits better if the row's isotope-like deltas
(deamidation, or an N→D / Q→E substitution) are read as isotope steps, the signature of an
unmodified canonical peptide picked on its M+1 peak. Decoy rows carry their target entry's
class. All six are Percolator features. With --rescore or --rescore-native, andes also
estimates FDR within each class: every PSM gets the class of its most canonical protein
(canonical, else isoform, variant, chain or a + combination). Each target gets an
empirical p-value against every decoy of the run, and Storey q-values are computed within its
class. Pooling the decoys gives a small class the power of the canonical decoy set, and it is
conservative as long as the class's own decoys score no higher than the pool. andes checks this:
a class whose decoys fall in the pool's top 5% significantly more often than 5%, or that has
fewer than 20 decoys, uses target-decoy competition inside the class instead,
q = (decoys + 1) / targets. A target's q-value becomes the larger of its global and its class
q-value, so the filtered TSV, the QPX q-value and quantification accept a PSM only if it passes
both. The run log prints each class's target and decoy counts, method and Storey null fraction.
A search with a single class, any FASTA search, is unchanged. Chimeric co-isolation rows (Pass 2 of
--chimeric) get RefCompetitor and DeltaRefScore = 0; the glyco PIN leaves all six out.
--refine Pass-2 rows keep the provenance of the entry their backbone came from. The TSV adds ProteinFamily, EntryKind and FeatureIds for the named
protein.
Proteoform table. With rescoring and a database that holds isoform, variant or chain entries,
andes writes <stem>.proteoforms.tsv: for each protein family (a UniProt entry), the canonical
entry first and then each isoform, variant and chain entry with a specific target PSM at
q ≤ --fdr.
Within its family, a peptide is specific to a member when no other member explains it, and
shared otherwise. A member's confidence is 1 − q of its best specific PSM, the class-aware
q-value above (evidence specific), or of its best shared PSM when it has no specific one
(evidence shared). For a variant, alternative_explanation says whether its substitution
weighs the same as deamidation, methylation or oxidation, and whether the canonical counterpart
peptide (the substitution reverted) was identified: decoys cannot measure that error, so these
rows are candidates rather than claims. With --lfq or --tmt, quantity_<run or channel> is
the summed abundance of the member's specific peptides (NA where none was measured).
--decoy-strategy shuffle is refused for a non-specific search when the database holds isoform
or mature-chain entries: shuffled whole-sequence decoys do not keep the peptides those entries
share with their canonical entry.
With --uniprot-variants, restricting an artefact mod (deamidation, oxidation, carbamylation,
methylation) to annotated sites removes the canonical explanation that competes with a variant
of the same mass. andes warns when a mods file does that.
Evidence (--uniprot-evidence, from the features' ECO codes):
| Level | Admits |
|---|---|
experimental |
ECO:0000269 (experimental), ECO:0007744 / ECO:0007829 (combined experimental and computational, e.g. large-scale proteomics) |
curated |
the above plus manual assertions without direct experimental support: combinatorial (ECO:0000244), by similarity (ECO:0000250), sequence model (ECO:0000255), author statement (ECO:0000303), curator inference (ECO:0000305), imported (ECO:0000312) |
any |
everything, including automatic annotation and features without evidence (most dbSNP-only variants) |
The run log reports the entries, the sites, variant, isoform and chain entries kept, the isoforms it could not rebuild, and the features left out (below the evidence level, not fitting their sequence, on isoforms not searched, or with no known mass).
Limits.
- Variants and chains are not combined. A variant peptide at a chain terminus is not searched, and chains are only read on the canonical sequence, not on isoforms.
- A proprotein (signal peptide removed, propeptide not yet) is not an entry of its own. Only
chainandpeptidefeatures are. - The
--refinesecond pass searches peptide mini-proteins that carry no annotation. It leaves the restricted mods out, so they apply in the main search only, and the refinement tier's own mods apply there as usual. andes trainreads FASTA only.
Validation. The variant, isoform and chain options grow the search space, which can raise identifications and error together. Measure them before relying on them: PSMs at 1% FDR on the standard datasets, plus an entrapment FDP.
Annotation files (--annotations)¶
An annotation file adds claims about the reference proteins of any target database, FASTA or
UniProt XML, from any source: variant catalogues, a sample's own variant calls, site databases.
Each row is one claim, made of sequence edits, modification sites or a mature chain. andes builds
it into entries exactly as it builds UniProt's own. Knockoffs, the class FDR, --proteoform-fdr
and the proteoform table then treat both alike. A UniProt XML variant written as an annotation
of the matching FASTA gives the same entries and the same PIN.
| Flag | Type | Default | Description |
|---|---|---|---|
--annotations |
path, repeatable | (none) | A Parquet annotations file. Several files are merged: equal claims become one entry, and an annotation_id must name one claim of its reference across all files. |
File format. Parquet with the key-value metadata andes.annotations.schema = 1. One row per
claim. Integer fields may be of any integer type, and other columns are ignored.
| Column | Type | Required | Meaning |
|---|---|---|---|
annotation_id |
utf8 | yes | Stable id. The entry's accession is the reference accession with _<id> added to its middle field (sp|P02769_VAR_A214T|ALBU_BOVIN). |
reference_accession |
utf8 | yes | Accession (first header token) of the reference protein. |
reference_md5 |
utf8 | yes | md5 (hex) of the reference sequence (uppercase residues) the claim was written against. A row whose reference differs is left out. |
source_db |
utf8 | yes | Where the claim comes from, e.g. COSMIC, dbSNP, sample_vcf. |
edits |
list<struct |
no | Sequence edits on the reference, 0-based half-open, sorted and disjoint; ref must equal the reference residues, and an empty alt is a deletion. Edits on one row occur together (in cis). |
mods |
list<struct |
no | Modification sites, in the coordinates of the entry (after the edits, or within the chain). anchor: residue (default), protein_n_term or protein_c_term. |
processing |
struct |
no | A mature chain: reference residues [start, end). It must make a new terminus. |
genotype |
utf8 | no | The sample's own call (0/1, 1/1, 1\|0, …). |
population_af |
float | no | Allele frequency in a reference population. |
evidence |
utf8 | no | experimental, curated or any, as in --uniprot-evidence. |
clinical_significance |
utf8 | no | Clinical class, e.g. benign, likely benign, pathogenic. |
Prior tiers. Each claim gets a tier from what its source says. The first rule that holds wins:
| Tier | Rule |
|---|---|
sample |
a genotype other than reference or missing (0/0, ./.) |
common |
population_af ≥ 0.01 |
curated |
evidence experimental or curated, or a benign clinical_significance |
catalogue |
anything else |
UniProt XML variants are curated, or catalogue when only --uniprot-evidence any admits them.
PEFF claims are catalogue. When equal claims are merged, the best tier is kept, sample
first. An annotation claim equal to an entry the database already holds (a UniProt
XML or PEFF variant or chain that reads the same) joins that entry instead of adding a second
one, and the entry and its knockoffs take the better tier. The run log counts these merges. Knockoff q-values are estimated per substitution class within each tier, so claims of
different reliability do not share one estimate. A class with fewer than 50 tested PSMs at the
cutoff in a tier joins the class's other small tiers. Classes are never pooled with each other,
and a run with one tier is stratified by class alone. The proteoform table reports each member's
source and prior_tier.
A row needs edits, mods or processing:
- edits make a variant entry, with knockoffs for a single-residue substitution;
- processing makes a chain entry;
- mods alone add sites to the reference protein itself (for sites=annotated mods), and every
entry built from that reference inherits them.
Edits together with processing are not supported yet. Rows are checked on load:
- the reference must exist and its md5 must match;
- each edit's ref must match, and the edits must be sorted and disjoint;
- each mod residue must match;
- an edit set that only swaps I and L is left out, because no spectrum tells it from the
reference.
The run log counts every row left out, by reason.
Site claims (knockoff sites, experimental)¶
An annotated modification site is a claim: residue r of protein P carries modification M. Sites
come from UniProt XML (modified residue and similar features), annotation-file mods rows and
PEFF \ModRes* keys. For each one, andes adds up to --knockoffs knockoff sites. A knockoff
site carries the same modification on a matched residue of the same protein that no annotation
names. It must have:
- the same residue;
- the same digestion: a modified K or R blocks cleavage, so the merged peptide is compared;
- the next-residue rule of variant knockoffs;
- the same motif status:
| Modification | Status matched |
|---|---|
| Phospho S/T | proline at +1, R/K at −3, D/E at +3 |
| Phospho Y | D/E at −1..−4 |
| Methyl or dimethyl R, deamidated N, hydroxy P | G at +1 |
| N | the N-x-S/T sequon |
- another peptide whose length is within 3 of the real one's;
- at least 5 residues from every real site of that modification.
Terminal sites get none.
A sites=annotated mod may also be placed on knockoff sites. A PSM that puts the modification on
an annotated site supports that site's claim. One that puts it on a knockoff site is a false
claim. Within each modification × residue × tier, the knockoff PSMs estimate the false claims, as
for variants, and each site gets a site q-value. A PSM's own q-value is unchanged: a peptide
identification does not depend on which residue carries the modification.
Placements on knockoff sites are never reported for a sites=annotated mod, because they exist
only for the estimate. For an unrestricted mod, which may sit anywhere anyway, they stay ordinary
PSMs.
Some knockoff sites are really modified, because annotation is incomplete, which makes the estimate reject some true site claims. It does not err only that way: see the next paragraph. A PSM whose peptide several genes hold counts once in its stratum, as a knockoff PSM does, and a placement that is a knockoff site in one protein but an annotated site in another (a paralog annotated differently) supports that claim.
Experimental. The knockoff sites control whether a residue is modified, not which modification it carries: on synthetic peptides whose lysines each carry one known modification, a third of the site claims passing site q ≤ 0.05 name the wrong modification at the right site (acetyl for trimethyl, two methyls for one dimethyl). The site identity columns resolve these.
Measured on a mouse-liver phospho run (PXD007653, reviewed mouse UniProt, 51,906 annotated sites,
42,985 knockoff sites):
- Phospho placed freely (the usual search): identifications are identical with and without
knockoff sites, and 1,403 annotated sites pass a site q-value of 0.05.
- Phospho restricted to annotated sites (sites=annotated): the knockoff placements cost 0.7%
of PSMs and 0.8% of phosphopeptides, and only 119 sites pass, because many knockoff residues are
really phosphorylated.
Site claims are therefore most useful with freely placed mods. 31,165 of the sites had no residue matching all their context rules, so no knockoff.
With rescoring, <stem>.sites.tsv lists the accepted claims, one row per site whose best PSM
passes --fdr and whose site q-value passes --proteoform-fdr. Its columns are family
accession position residue modification_mass prior_tier psms best_psm site_q confidence occupancy
identity identity_alternatives identity_spectra.
Site identity: occupancy and which modification¶
Each accepted site also gets two probabilities:
- occupancy: the share of the posterior on the site's peptides that places a modification
on the residue.
- identity: given a modification there, the share of the row's one. identity_alternatives
lists the others as mass:share (for example +42.0470:0.29 beside acetyl at 0.71), and
identity_spectra is the posterior mass placing a modification there, in spectra.
The posterior is taken over every row of a spectrum, targets and decoys, so the explanations of
one spectrum compete. A row's logit combines:
- its rescored score;
- its precursor isotope offset;
- its fragment ions, and those no other form of the same peptide among the spectrum's rows has;
- the diagnostic= ions and loss= neutral losses of its modifications.
The weights of the first three are fixed. How much a diagnostic ion or a loss says is learned from the run: among the rows the other evidence already makes confident, how often it is present when the row carries the modification and when a competing row carries another. Declare the signatures in the mods file; a modification without them is told apart by its fragments only.
Measured on synthetic peptide pools (PXD009449), one lysine modification per pool, six lysine modifications claimed at every site, on the half of the peptides the fixed weights were not fitted on:
| Accepted sites | Claims | Wrong modification |
|---|---|---|
| site q ≤ 0.05 | 753 | 245 (33%) |
| and identity ≥ 0.5 | 522 | 26 (5.0%; 28.5 expected) |
| and identity ≥ 0.95 | 412 | 11 (2.7%) |
| and identity ≥ 0.95, occupancy ≥ 0.5 | 404 | 3 (0.7%) |
In the unmodified-lysine pool, every site that passed its site q-value had occupancy below 0.5 (mean 0.01). The remaining wrong claims look like synthesis side products: one peptide is seen with another modification in three pools. PSMs at 1% FDR do not change.
PEFF databases¶
--database x.peff (or .peff.gz) reads a PSI Extended FASTA Format file: FASTA whose headers
carry \Key=value annotations. The accession is the header's first token (sp:P04637), whose
primary accession (P04637) names the family; \PName is the description. The variant,
modification and processing keys become claims about the entry, checked and built exactly like
the rows of an annotation file. Positions are 1-based and inclusive.
| Key | Value | Becomes |
|---|---|---|
\VariantSimple |
(pos\|aa[\|tag])… |
a one-residue substitution, named by its tag or VAR_<change> |
\VariantComplex |
(start\|end\|seq[\|tag])… |
an edit; an empty seq is a deletion |
\ModResUnimod |
(pos\|UNIMOD:n\|name)… |
a site, with the mass of that UniMod accession |
\ModRes, \ModResPsi |
(pos\|accession\|name)… |
a site, when name is in UniProt's modification vocabulary |
\Processed |
(start\|end[\|accession]\|name)… |
a mature chain; signal peptides, transit peptides, propeptides and initiator Met are left out |
PEFF keys are always used: the file was written to carry them. Other keys are ignored, as are
sites with no known mass, which the log counts. --knockoffs applies to PEFF variants as to any
other.
Chimeric cascade¶
Needs MS1: mzML or Thermo .raw only (MGF/.d warn and run a normal search).
| Flag | Type | Default | Description |
|---|---|---|---|
--chimeric |
flag | (off) | Pass 2 finds co-isolated precursors in the MS1 isolation window (averagine match) and searches the residual spectrum for a second peptide. Pass 1 keeps the same --top-n candidates per scan as a plain search; Pass 2 takes the best one as the primary peptide. Forces MS2. Experimental. |
--chimeric-max-coisolated |
u32 | 4 |
(advanced) Max co-isolated precursors per scan. |
--chimeric-max-kl |
f64 | 0.3 |
(advanced) Max isotope-envelope KL divergence to accept a co-isolated precursor. |
Refine — secondary chemistry cascade¶
A second pass over confident proteins, on the spectra pass 1 missed, with a fixed tier: oxidation on M/P/K, deamidation on N/Q, the two pyro-Glu losses and protein N-terminal acetyl, at most two per peptide. It does not discover modifications; andes has no open search.
| Flag | Type | Default | Description |
|---|---|---|---|
--refine |
flag | (off) | Enable the PTM-refinement cascade (Pass-2 over confident proteins). |
--refine-config |
path | (tier default) | (advanced) YAML tier config: the mod set, max variable mods, the high-res-only gate and entrapment: true (one shuffled entrapment anchor per Pass-2 anchor, to count Pass-2 false discoveries; diagnostic only). |
--refine-select-psm-fdr |
fraction | 0.01 |
(advanced) PSM-FDR of the confident set that seeds Pass-2. |
FDR for refined PSMs. Pass-2 rows (IsRefinement = 1) share the PIN. Under one merged 1%
threshold they measured ~2.8% FDP on Astral; thresholded on their own, 0.8–1.1%. Threshold
them separately when you need 1% on modified PSMs.
Rescoring & FDR filtering¶
andes does not compute FDR itself; these flags run a rescorer in-process. Leave them off
when a pipeline (e.g. quantms) owns rescoring. --fdr/--pep alone are ignored with a warning.
| Flag | Type | Default | Description |
|---|---|---|---|
--rescore |
flag | (off) | Run Percolator and write rescored, filtered output. |
--rescore-native |
flag | (off) | (advanced) Built-in rescorer: Percolator's semi-supervised linear algorithm in process (3-fold CV folded by spectrum, target-decoy competition per scan and precursor mass as -Y); fallback when Percolator is not available. |
--fdr |
fraction | 0.01 |
q-value threshold applied by a rescoring run. With UniProt isoform, variant or chain entries, a PSM must also pass it within its class (§1a, provenance). |
--proteoform-fdr |
fraction | max(--fdr, 0.05) |
q-value threshold for proteoform claims: variant-class PSMs (every protein a UniProt variant entry) in the filtered TSV, the proteoform table and quantification; every other PSM keeps --fdr. |
--protein-fdr |
fraction | (off) | Protein-group q-value cutoff: the filtered TSV keeps only PSMs whose peptide counts for a group at or below it, and the proteoform table only families of such groups. <stem>.protein_groups.tsv is written either way (protein groups). With --mbr --output-parquet, the QPX pg and feature views keep the groups at or below it, 0.01 unless given. |
--pep |
fraction | (off) | (advanced) PEP threshold, ANDed with --fdr (and with --quant-fdr for quantification targets and --mbr donors). |
--percolator-bin / --percolator-docker / --percolator-image / --percolator-args |
— | auto | (advanced) Backend: --percolator-bin, else $PATH, else Docker; --percolator-args passes flags through. Percolator runs with target-decoy competition (-Y: the best PSM of each spectrum competes, and q-values come from the winners), as every andes benchmark was measured, and with --num-threads set to --threads unless --percolator-args sets it: about twice as fast as its default of three threads on a 16-core node, with the same result (the PSM count can differ by about 0.2% between thread counts). --subset-max-train <N> trains on N PSMs and is 5–10 times faster, but on an entrapment benchmark it raised the true FDP at q ≤ 0.01 from 1.00% to 1.09–1.16%, so it is not the default. |
--keep-pin |
bool | true |
(advanced) With --rescore/--rescore-native and no --output-pin: true writes the PIN (and Percolator's result files) as <base>.pin, where <base> is --output-tsv, else --output-parquet, else ./andes; false uses a temporary PIN in --temp-dir, deleted at exit. An explicit --output-pin is always kept. |
--predict |
list | rt |
Predicted-spectrum and retention-time features added to the PIN (§3a, predicted features): rt (bundled andes-rt model), ms2 (bundled andes-ms2 fragment-intensity model), peptdeep-ms2 (AlphaPeptDeep fragment intensities), peptdeep-rt, deeplc-rt, comma-separated, or none. They are Percolator features; andes computes no FDR from them. |
--predictor-models |
dir | (none) | Directory with the exported AlphaPeptDeep (peptdeep_mods.json, peptdeep_{ms2,rt}_dynamic.{json,onnx}) and DeepLC (deeplc_rt.{json,onnx}) models that peptdeep-ms2, peptdeep-rt and deeplc-rt load. A requested model that is missing stops the run before the search. |
--predictor-backend |
tract|ort |
tract |
(advanced) ONNX engine for those models: tract (pure Rust, built in) or ONNX Runtime (a build with --features ort; the runtime library is found through ORT_DYLIB_PATH). |
--predictor-nce / --predictor-instrument |
— | auto | (advanced) Collision energy and instrument given to the MS2 model (andes-ms2 and AlphaPeptDeep). andes-ms2 takes the NCE from the run (HCD outside a TOF) or leaves it unknown, the activation (HCD-FTMS, HCD-ITMS, CID-ITMS...) from the activation and analyser of the run, and an instrument family from the analyzer (Q Exactive; Astral runs use the Exploris family, as the training spectra hold no Astral run; unknown otherwise). AlphaPeptDeep defaults to NCE 28 on Q Exactive, 30 otherwise, and the instrument from the resolved one (Q Exactive → QE, timsTOF → timsTOF, TOF → SciexTOF, otherwise Lumos). |
--predictor-batch |
int | 256 |
(advanced) Peptides per predictor batch. |
Protein groups and protein FDR¶
Every rescored run writes <stem>.protein_groups.tsv, independent of quantification. Inference
works on protein families: a canonical entry with its isoform, variant and chain entries is one
family (with a FASTA database, each entry is its own family), so alternative entries never split
a protein's evidence. Targets and decoys are treated alike:
1. every peptide gets the best rescored score among its PSMs;
2. families with the same peptides form one group;
3. a group whose peptides are a strict subset of another group's is subsumed: listed in that
group's subsumed column, not scored;
4. a peptide still shared between groups counts for the group with the most peptides (razor),
ties to the better best score, then the name;
5. a group scores its best unique or razor peptide.
The protein q-value comes from picked target-decoy competition: each target family competes with
the decoy of the same family (<decoy prefix>_<accession>), and only the better of the two
counts; q = decoys / targets at or above a group's score, with a running minimum. A target that
loses to its own decoy gets q = 1.
Columns: protein_group lead accessions families peptides unique_peptides razor_peptides psms
best_score protein_q subsumed. lead is the group's reference entry; peptides,
unique_peptides and razor_peptides count the peptides with a PSM at its cutoff (--fdr, or
--proteoform-fdr for variant-class PSMs), and psms those PSMs. The filtered TSV gains
protein_group and protein_q. With --protein-fdr, the filtered TSV and the proteoform table
keep only groups at or below it. The proteoform table's members are then claims inside an
accepted protein: each still needs its own specific evidence at its class-aware cutoff.
Measured with entrapment databases (a group is false when every accession in it is an entrapment entry; true FDP = entrapment groups / accepted × (1 + target : entrapment proteins)):
| Run | PSMs at 1% | Groups at protein q ≤ 0.01 | True FDP | At q ≤ 0.05 | True FDP |
|---|---|---|---|---|---|
| Astral, whole-protein shuffled entrapment (1 : 1 proteins) | 42,605 | 4,332 | 1.3% | 4,929 | 5.6% |
| UPS1 in yeast, E. coli entrapment (1.49 : 1) | 16,147 | 852 | 5.0% | 924 | 8.1% |
The UPS1 entrapment proteins at 1% are led by EF-Tu (36 PSMs), DnaK and ornithine
carbamoyltransferase: proteins that co-purify with recombinant proteins expressed in E. coli,
so most are likely present, and the shuffled-entrapment run is the better measure. Re-measured
on three peptide-level shuffled entrapments (native rescorer, 0629cd8), the Astral groups at
protein q ≤ 0.01 read 0.7–1.1% and at 0.05 5.6–6.7%. Taking only
the best row of each spectrum, or only rows at PSM q ≤ 0.01, gives 1.1–1.4% at 1% on Astral:
within the entrapment's counting noise.
Several runs: andes inference¶
andes inference runs the same inference once over the PSMs of several runs and writes the QPX
protein-group view, so the groups and their q-values are dataset-level:
andes inference \
--psms run1.percolator.target.psms.txt run2.percolator.target.psms.txt \
--decoy-psms run1.percolator.decoy.psms.txt run2.percolator.decoy.psms.txt \
--database db.fasta -o out/PXD000001
# writes out/PXD000001.pg.parquet
- The scores must be on one scale: give the results of one Percolator run over all the runs (one file, or one file per run from that run). Separate Percolator runs are not comparable.
- Give the search's database options (
--annotations,--uniprot-*,--knockoffs,--enzyme,--decoy-prefix/--decoy-suffix), so families, knockoff entries and decoy pairs are the search's. Knockoff entries are evidence for no protein. - The class and knockoff q-values are applied as in a search;
--fdrand--proteoform-fdrset the PSM cutoffs for the counts. --protein-fdr(default 0.01) is the protein q-value cut:pg.parquetkeeps the target groups and the decoy groups at or below it, so the decoys show the FDR at the cut.--protein-fdr 1keeps every group.pg.parquet: one row per target group and per decoy group that won its family's competition (is_decoy), at or below the cut, withpg_accessions,anchor_protein,grouped_runs(--runs, else one name per--psmsfile),global_qvalue(the protein q-value),pg_qvalue(only for one run),peptides(peptides per accession),peptide_counts, the best peptide score inadditional_scoresand families, PSMs, razor peptides and subsumed families incv_params.
Glycopeptide search¶
See §9.
Isobaric labeling (TMT / iTRAQ)¶
See §7 for the search (label as a fixed mod, TMT model) and
§10 for reporter-ion quantification (--tmt).
Quantification (reporter ions, label-free)¶
| Flag | Type | Default | Description |
|---|---|---|---|
--tmt |
enum | (off) | Isobaric reporter quantification of the confident PSMs: tmt6, tmt10, tmt11, tmt16, tmt18, tmt32, tmt35, itraq4, itraq8. Writes <stem>.tmt.parquet (§3f). |
--tmt-level |
2\|3 |
auto | Reporter scan: the identified MS2, or the SPS-MS3 whose precursor references it (mzML <precursor spectrumRef>, .raw Master Scan Number trailer). When not given: 3 if the MS2 is low-resolution, cannot separate the plex's channels and the MS3 scans carry reporters (SPS-MS3), else 2; the log says when it picks 3. |
--tmt-tol |
tolerance | 20ppm (10ppm for tmt32/tmt35) for high-res MS2 and for --tmt-level 3; 0.3da for ion-trap MS2 |
Most intense centroid within this window of each reporter m/z. A window that reaches half the spacing of the kit's closest channels (6.3 mDa for the TMT 10/11/16/18-plex N/C pairs, 2.9 mDa for the TMTpro 32/35-plex deuterated channels, so at most 10.8 ppm) is refused, so ion-trap MS2 cannot quantify those kits at --tmt-level 2. |
--tmt-correction |
path | (none) | Isotope impurity sheet of the kit lot (§10); corrected intensities by NNLS, raw kept alongside. Refused for tmt32/tmt35. |
--tmt-min-purity |
0..1 | 0.7 |
Quantification gate (§10): PSMs below this precursor isolation purity fail the gate (quant_gate_passed false, quant_weight 0 in tmt.parquet). 0 turns it off. |
--tmt-max-pep |
0..1 | 0.05 |
Quantification gate: PSMs whose Percolator PEP is above this fail the gate. Needs --rescore; 1 turns it off. |
--tmt-min-sn |
≥ 0 | 0 (off) |
Quantification gate: PSMs whose summed reporter signal-to-noise is below this fail the gate. Applies only where the reporter scan records noise (Thermo .raw, mzML with noise arrays). |
--tmt-unmix |
flag | off | Estimate each MS2 scan's co-isolation interference and write the reporter intensities with it removed (interference and an unmix_ channel block in tmt.parquet; unmixed in the quantms feature parquet). High-resolution MS2 reporters only; refused at --tmt-level 3. |
--lfq |
flag | off | Label-free MS1 quantification of the confident precursors (§10). Writes <stem>.lfq.parquet. mzML / .raw with high-resolution MS1; the MS2 may be ion-trap. |
--quant-dir |
dir | (none) | Per input file writes <stem>.ms1.parquet (the run's MS1 scans, every FAIMS voltage), <stem>.ms2.parquet (every MS2 scan reduced to its 100 most intense centroids, with its precursor and isolation window) and <stem>.quant.parquet (every rank-1 PSM, targets and decoys, no FDR filter; spec_id is the PIN SpecId, search_score the PIN RawScore) for the multi-run quantification stages. Implies MS1 capture; same inputs as --lfq. Two inputs with the same stem, or an input without MS1 scans, are an error. Other outputs are unchanged. |
--lfq-tol |
tolerance | 10ppm |
MS1 tolerance of the isotope chromatograms, and of the --mbr template fit (ppm only there). |
--lfq-rt-window |
seconds | 60 |
Half-window around the identifying PSM's retention time. |
--lfq-gaussian-width |
seconds | (none) | Experimental Gaussian smoothing width (eight sigma) for peak detection; areas use raw traces. Validate feature-q coverage for the acquisition. Default detection uses five-scan Savitzky–Golay. |
--lfq-min-cosine |
0..1 | 0.7 |
Minimum isotope-envelope cosine for both target and decoy eligibility before feature competition. All target features remain in lfq.parquet (with accepted false when they fail a cut) and the QPX parquet. |
--lfq-feature-fdr |
0..1 | 0.05 |
Feature FDR for accepted in lfq.parquet, from the target/decoy competition of each feature against its +11 Th twin. |
--quant-fdr |
0..1 | --fdr |
PSM q-value cut for quantification targets when --rescore/--rescore-native ran. With --mbr, also the PSM q-value at which a run identifies a precursor (§10). |
--mbr |
flag | off | Match-between-runs label-free quantification across two or more inputs (§10). Needs --lfq and --rescore (or --rescore-native); not with --tmt. Writes <stem>.mbr.parquet and <stem>.mbr_validation.parquet; with --output-parquet, quantms.feature.parquet holds these rows (without the below-threshold transfers) instead of the per-run features. Uses --quant-dir, or a temporary directory removed at exit. With --rescore (Percolator), same result as andes quant library, extract and finalize on the same Percolator results. |
--mbr-library-fdr |
0..1 | 0.01 |
Internal library entry gate (precursor-level), not a reported identification FDR: best-PSM-per-precursor target/decoy q-value over the rescored PSM scores of all runs. |
--mbr-fdr |
0..1 | 0.01 |
(advanced) Transfer quality q-value: transfers at or below it are transfer_ms2 (an MS2 scan of the run supports them) or transfer_ms1. An internal quant-quality q-value, not an identification FDR. It counts both wrong peaks and the transfers of false donor identifications (identity decoys, §10). On the ProteoBench QExactive module (2026-10-23, defaults), 0.05 adds about 6k precursors quantified in at least 3 runs to 0.01 (65.0k to 71.2k) at an accuracy cost (median |eps| 0.163 to 0.176, E. coli 0.28 to 0.44, CV 0.118 to 0.132); in an earlier measurement the wrong-peak rate rose from 1.7% to 2.7%. Use 0.05 for a deeper, less accurate set. |
--quant-feature-fdr |
0..1 or off |
off |
Identified-row quality q-value (quant_q): with a value, an identified row whose MS1 feature has a quant_q above it (or none) is identified_below_threshold and left out of quantms.feature.parquet (§10); off reports every identified row and keeps quant_q as an annotation. An internal quant-quality q-value, not an identification FDR. |
With --output-parquet, both modules add quantms.feature.parquet (the QPX feature view) to the bundle.
1b. Configuration file (--config)¶
andes --config run.yaml sets any parameter from YAML, in sections io, search, scoring,
decoys, chimeric, refine, rescoring, glyco, quant (template:
config.example.yaml). Keys are optional; CLI flag > --config >
built-in default; unknown keys are a hard error. The UniProt XML options are io keys
(uniprot_variants: true, uniprot_isoforms: true, uniprot_chains: true,
uniprot_evidence: curated). Values use the CLI strings
(precursor_tol: 20ppm, charge: "2..5", isotope_error: "-1..2", enzyme: gluc,trypsin).
Every search flag has a key; a key is the flag name without its section prefix
(--glyco-species is glyco.species, --glyco-glycan-gdb is glyco.glycan_gdb,
--fragment-index is search.fragment_index, --gbdt-max-trees is
scoring.gbdt_max_trees, --lfq-gaussian-width is quant.lfq_gaussian_width). Four flags
are CLI-only: --config, and the diagnostics --cal-min-spec-keys, --precursor-mono-dump
and --glyco-scans. glyco.enabled: true needs glyco.glycan_gdb or glyco.species (or the
CLI flag), checked after the merge; setting either glycan source on the command line replaces
the config's choice.
# run.yaml (minimal)
io:
spectrum: [sample.mzML]
database: human.fasta
output_pin: out.pin
search:
precursor_tol: 20ppm
enzyme: trypsin
glyco:
enabled: true
1c. Splitting a search across cluster tasks¶
One andes search run takes one parameter set. Files that need different settings (mods,
tolerances, TMT against label-free) are separate runs, and the files of one settings group can
go into one run (--spectrum repeated) or into several tasks of an array.
What each task pays again. Before the first spectrum a task reads the database (0.1 s),
builds the decoys and the SearchIndex (1 s), builds or opens the base-peptide index (2 s
cold, 0.4 s cached) and loads the model (0.3–3 s); these are the times for databases of 26–32k
proteins with 8 threads. The fragment-ion index is built per spectrum chunk from that chunk's
spectra and cannot be shared between tasks; on a phospho search it is about two thirds of the
run. Other per-run fixed work, such as the retention-time anchor fit of --predict rt, also
repeats per task. Running the files of a group in one task pays this once, at the cost of a
longer critical path and a peak memory that grows with the number of files in the task (the
spectra of all its files are held until the PIN is written).
The index cache. The base-peptide index is cached as andes-candidx-<key>.bin in
--index-cache-dir, by default the directory of the output files; the directory is created if
missing and a directory that cannot be written is an error. The key is a SHA-256 over the
database content, digestion, fixed modifications and decoy settings, the cache format version
and the andes version, so it is the same on every node and toolchain and a file written by
another andes version is never reused. With a node-local directory every task builds its own
file. Pass the same shared directory to all tasks and the file is built once: tasks that find
the file missing take <file>.lock, one builds, the others wait and open the result. A lock
whose modification time has not changed for 60 s (a builder that died) is taken over. A
prebuilt shared file can also be copied to node-local storage and used through a node-local
--index-cache-dir; the file is a plain mmap-able file and is read-only after the build.
Combining the tasks. The PINs of tasks with the same settings have the same header and can be
concatenated (the header once); SpecId carries the run name, so rows do not collide. Run
Percolator once per settings group, not across groups with different search spaces: the
chargeN columns follow the charge range, glyco PINs have another schema, and one Percolator over
groups with different search spaces controls the FDR only overall. Combine groups at the
peptide, protein or quantification level.
2. Mods.txt format¶
The Java MS-GF+ format (crates/model/src/modification.rs). Each line has five fields:
<mass>,<aa>,<fix|opt>,<location>,<name>
| Field | Rule |
|---|---|
<mass> |
Monoisotopic mass delta in Da. Composition strings (C2H3N1O1) are not supported. |
<aa> |
One uppercase letter or *. STY is not supported; use one line per residue. |
<fix\|opt> |
fix = fixed, opt = variable. Case-insensitive. |
<location> |
any, N-term, C-term, Prot-N-term, Prot-C-term (case-insensitive; hyphens optional). |
<name> |
Name used in logs. |
NumMods=N sets the maximum variable mods per peptide (default 3). # starts a comment. A
fixed and a variable mod on the same (residue, location) is rejected.
Optional key=value attributes may follow the name, comma-separated:
| Attribute | Meaning |
|---|---|
loss=<m1;m2;…> |
Neutral-loss masses (Da) predicted for this mod's fragment ions. |
class=<glyco\|phospho\|sulfo\|generic> |
Pool the mod's loss ions with others of the class. |
accession=<CURIE> |
Ontology accession reported in the outputs, e.g. UNIMOD:21. |
sites=<annotated\|any> |
annotated restricts a variable mod to the residues a UniProt XML database annotates with a modification of the same mass (§1a). any (default) applies it to every matching residue. |
cleavage=<blocked\|kept> |
blocked: the enzyme does not cut at a residue carrying this mod, so it is never placed on the residue a peptide was cleaved at (the C-terminal K/R under trypsin), except at the protein's own terminus. Use it for modifications present before digestion that block the protease: acetyl-, succinyl- and other lysine acylations, GlyGly, di- and trimethyl-K. Not for labels added after digestion (TMT, reductive dimethylation). kept (default): no restriction. |
diagnostic=<mz1;mz2;…> |
Singly charged ions the mod gives off when fragmented, such as the immonium ion of the modified residue. Not used in scoring: evidence for which modification a site carries (see site identity). Examples (HCD): acetyl-K 143.1179;126.0913, trimethyl-K 143.1543 (with loss=59.0735, trimethylamine), succinyl-K 201.1234;184.0968, GlyGly-K 215.1502;198.1237, phospho-Y 216.0426. |
Example (a) — Carbamidomethyl C + Oxidation M¶
NumMods=3
57.02146,C,fix,any,Carbamidomethyl
15.99491,M,opt,any,Oxidation
The built-in defaults when --mods is omitted are these two plus a variable Acetyl
(+42.010565) on the protein N-terminus, i.e. the equivalent of adding
42.010565,*,opt,Prot-N-term,Acetyl. Pass a --mods file to search without it.
Example (b) — TMT 10-plex on K and peptide N-term¶
NumMods=2
57.02146,C,fix,any,Carbamidomethyl
229.162932,K,fix,any,TMT10plex
229.162932,*,fix,N-term,TMT10plex
Add --protocol TMT to select hcd_qexactive_tryp_tmt (§7).
Example (c) — Phosphorylation on S, T, Y¶
NumMods=3
57.02146,C,fix,any,Carbamidomethyl
79.966331,S,opt,any,Phospho
79.966331,T,opt,any,Phospho
79.966331,Y,opt,any,Phospho
Add --protocol phospho to prefer a phospho model (e.g. hcd_qexactive_tryp_phosphorylation).
Example (d) — Phosphorylation only at UniProt-annotated sites¶
NumMods=3
57.02146,C,fix,any,Carbamidomethyl
15.99491,M,opt,any,Oxidation
79.966331,S,opt,any,Phospho,sites=annotated
79.966331,T,opt,any,Phospho,sites=annotated
79.966331,Y,opt,any,Phospho,sites=annotated
42.010565,*,opt,Prot-N-term,Acetyl,sites=annotated
Search it against a UniProt XML database: --database uniprot_sprot_human.xml.gz. Add
--uniprot-evidence curated to also use sites UniProt annotates by similarity. Against a FASTA,
the restricted mods are never placed and andes warns.
3. Output formats¶
Percolator .pin (always), plus optional .tsv and QPX parquet. Implementation:
crates/output/src/pin.rs, crates/output/src/tsv.rs.
3a. PIN columns¶
Tab-separated, one row per PSM, best-first within each spectrum by RankScore. Positional
isomers are collapsed: of the PSMs a spectrum keeps at one precursor and charge that share their
residues and their multiset of modification masses (the same peptide with a modification on
another site), only the best-ranked row is written; a decoy never collapses into a target, and the
kept row's Proteins lists the proteins of the rows collapsed into it. The TSV and QPX outputs
carry the same rows. Rank is assigned after the collapse. The chargeN
one-hots follow --charge only, never the data, so separately searched runs share one header;
the default 2–5 gives the columns below. Do not confuse
RankScore (col 8, orders candidates; historically RawScore) with RawScore (col 63,
the fused strong score Percolator weights most; historically StrongScore; it also ranks under
--score strong).
| # | Column | Type | Range | Description |
|---|---|---|---|---|
| 1 | SpecId |
string | — | {stem}/{specID}_{scan}_{rank}, where {stem} is the input file name without its extension; multi-row scans add _{rowIdx}. The stem keeps SpecIds unique when the PINs of separately searched runs are pooled. |
| 2 | Label |
int | {−1, +1} | +1 target, −1 decoy (by source protein). |
| 3 | ScanNr |
int | ≥0 | MS2 scan number. |
| 4 | ExpMass |
float | >0 | Experimental neutral precursor mass (Da). |
| 5 | CalcMass |
float | >0 | Theoretical neutral peptide mass (Da). |
| 6 | FileName |
string | — | The input file of the spectrum: the SpecId stem. Percolator's target-decoy competition (-Y) keeps one PSM per FileName, ScanNr and ExpMass, so PSMs of different runs never compete when the PINs of separately searched runs are pooled by concatenation. Percolator then writes a filename column after PSMId in its result files; readers take columns by header name. |
| 7 | mass |
float | >0 | Duplicate of ExpMass. |
| 8 | RankScore |
int | unbounded | Rank-LLR ranking score. |
| 9 | isotope_error |
int | [−1, 2] | Winning ¹³C isotope offset. |
| 10 | peplen |
int | ≥6 | Residue count + 2 (flanks). |
| 11 | dm |
float | signed | Precursor mass error (Da) after isotope correction. |
| 12 | absdm |
float | ≥0 | \|dm\|. |
| 13–16 | charge2…charge5 |
0/1 | one-hot | One-hot precursor charge. A declared charge outside --charge sets the nearest edge column (a 6+ sets charge5). |
| 17 | enzN |
0/1 | one-hot | N-terminus fits the enzyme rule. |
| 18 | enzC |
0/1 | one-hot | C-terminus fits the enzyme rule. |
| 19 | enzInt |
int | ≥0 | Internal cleavage sites. |
| 20 | NumMatchedMainIons |
int | [0, peplen−1] | Matched charge-1 b/y fragment positions. |
| 21 | longest_b |
int | [0, peplen−1] | Longest contiguous matched b-ion run. |
| 22 | longest_y |
int | [0, peplen−1] | Longest contiguous matched y-ion run. |
| 23 | longest_y_pct |
float | [0, 1] | longest_y / peplen. |
| 24 | ExplainedIonCurrentRatio |
float | [0, 1] | Matched b+y intensity / total MS2 ion current. |
| 25 | NTermIonCurrentRatio |
float | [0, 1] | Matched b-ion intensity / total MS2 ion current. |
| 26 | CTermIonCurrentRatio |
float | [0, 1] | Matched y-ion intensity / total MS2 ion current. |
| 27 | MS2IonCurrent |
float | ≥0 | Sum of all MS2 peak intensities (not log-scaled). |
| 28 | IsolationWindowEfficiency |
float | 0.0 | Always 0.0. |
| 29 | MeanErrorTop7 |
float | ≥0 | Mean absolute ppm error, top-7 matched ions. |
| 30 | StdevErrorTop7 |
float | ≥0 | Population stdev of absolute ppm errors (top-7). |
| 31 | MeanRelErrorTop7 |
float | signed | Mean signed ppm error (top-7). |
| 32 | StdevRelErrorTop7 |
float | ≥0 | Population stdev of signed ppm errors (top-7). |
| 33 | matchedIonRatio |
float | [0, 1] | NumMatchedMainIons / peplen. |
| 34 | EdgeScore |
int | unbounded | Per-bond edge-score sum (ion-existence + error); additive. |
| 35 | PrecursorIsotopeKL |
float | ≥0 | Precursor envelope KL vs averagine. Always 0.0 (kept for positional consumers). |
| 36 | PrecursorSNR |
float | ≥0 | Precursor SNR from the MS1 envelope. 0.0 unless --chimeric. |
| 37 | DeltaRankScore |
float | ≥0 | RankScore(best) − RankScore(2nd-best distinct peptide); rank-1 row only, else 0.0. |
| 38 | TailorScore |
float | ≥0 | RankScore ÷ spectrum's top-1% quantile; cross-spectrum comparability. |
| 39 | PpmGaussianScore |
float | ≥0 | Σ exp(−½(ppm/7)²) over matched ions. |
| 40 | NeutralLossIonCount |
int | ≥0 | Matched b/y ions with −H₂O/−NH₃ partner peaks. |
| 41 | LongestComplementaryLadder |
int | [0, peplen−1] | Longest run of bonds where both bᵢ and y₍ₙ₋ᵢ₎ matched. |
| 42 | ComplementaryIonBalance |
float | ≥0 | Σ 1/(1+\|rankᵦ−rankᵧ\|) over complementary bonds. |
| 43 | MeanMatchedIntensityRank |
float | ≥1 | Mean intensity rank of matched ions (1 = most intense). |
| 44 | DoublyChargedMatchedIonCount |
int | ≥0 | Matched charge-2 b/y ions. |
| 45 | UniqueMatchFraction |
float | [0, 1] | Within-peptide peak-explanation uniqueness. |
| 46 | ChanceMatchSurprise |
float | ≥0 | Σ max(0, −ln(ρ·Δ)): improbability of chance matches. |
| 47 | IntensitySignal |
float | [0, 1] | Predicted vs observed intensity cosine. 0.0 without an intensity model. |
| 48 | FragPredExplained |
float | [0, 1] | Σ(matched·pred)/Σpred. 0.0 without a frag-intensity model. |
| 49 | FragPredChanceLLR |
float | ≥0 | Σ matched·pred·max(0,−ln p_chance). 0.0 without a frag-intensity model. |
| 50 | FragTopKObserved |
float | [0, 1] | Top-K predicted-most-intense ions observed. 0.0 without a frag-intensity model. |
| 51 | RichIonLLR |
float | unbounded | Decoy-aware per-annotated-ion LLR sum. 0.0 without a rich-ion model. |
| 52 | IsRefinement |
0/1 | one-hot | 1 for a Pass-2 refinement PSM. 0 without --refine. |
| 53 | NumMods |
int | ≥0 | Variable modifications on the peptide. |
| 54 | RefinementModClass |
int | [0, 99] | Mod-class id for subgroup-FDR grouping. 0 without --refine. |
| 55 | ModSiteShiftedMatched |
int | ≥0 | Matched mass-shifted b/y ions. 0 for unmodified peptides. |
| 56 | ModSiteShiftedFrac |
float | [0, 1] | Matched shifted ÷ total shifted ions. |
| 57 | ModSiteIntensFrac |
float | [0, 1] | Shifted-ion intensity ÷ all matched-ion intensity. |
| 58 | ModSiteLocalized |
0/1 | one-hot | 1 if a bracketing ion pair localizes the mod. |
| 59 | ModSiteDetCount |
int | ≥0 | Site-determining (bracketing) ions over all sites. |
| 60 | MassCompetitionEvidence |
float | ≥0 | Σ 1/(1+ambiguity+ρ), alternative-mass competition. |
| 61 | CandidateRankEntropy |
float | ≥0 | Softmax entropy of the retained top-K scores. |
| 62 | ListwiseScoreGap |
float | signed | Top-1 − top-2 RankScore in the retained queue. |
| 63 | RawScore |
float | unbounded | Fused strong score signal − null; the primary feature. |
| 64 | RawScoreCal |
float | signed | Per-spectrum z-scored RawScore. |
| 65 | RankScoreFloat |
float | unbounded | Unrounded RankScore (continuous split-sum). |
| 66 | DeltaRT |
float | signed | Observed − predicted retention time (min); 0 without RT. |
| 67 | AbsDeltaRT |
float | ≥0 | |DeltaRT|. |
| 68 | DeltaRTNorm |
float | signed | DeltaRT divided by the run's gradient span. |
| 69 | IsVariant |
0/1 | — | A protein of the peptide is a UniProt natural-variant entry (§1a, provenance). |
| 70 | IsIsoform |
0/1 | — | A protein of the peptide is an isoform entry. |
| 71 | IsChain |
0/1 | — | A protein of the peptide is a mature-chain entry. |
| 72 | RefCompetitor |
0/1 | — | A canonical (or FASTA) candidate with another peptidoform was scored for the spectrum. |
| 73 | DeltaRefScore |
float | signed | Flagged rows: RankScore minus that best canonical candidate's; 0 otherwise. |
| 74 | IsotopeMimic |
0/1 | — | The precursor fits better when isotope-like deltas (deamidation, N→D, Q→E) are read as isotope steps. |
| 75 | Peptide |
string | — | pre.SEQUENCE.post with +mass mod annotations. |
| 76 | Proteins |
string | — | Protein accession(s), tab-separated; decoys carry --decoy-prefix. A UniProt variant entry names its change (sp\|P04637_VAR_005851_D7H\|P53_HUMAN), an isoform its id (sp\|P04637-2\|P53_HUMAN), a mature chain its feature id (sp\|P02769_PRO_0000001058\|ALBU_BOVIN). |
Columns marked 0.0 unless/without stay in the header and are zero when their condition does not hold.
Predicted features (--predict). After the PIN is written, each requested predictor scores
every row, targets and decoys alike, and its columns are inserted before Peptide
(<p> = andesrt for rt, andesms2 for ms2, pdeep for peptdeep-ms2 / peptdeep-rt, deeplc for deeplc-rt; the MS2 columns are named <p>_<feature>, shown here with pdeep):
| Column | Type | Description |
|---|---|---|
pdeep_spec_pearson_norm, pdeep_ionb_pearson_norm, pdeep_iony_pearson_norm |
float | Pearson correlation of predicted and observed b/y (z ≤ 2, ≤ precursor charge) intensities: all ions, b ions, y ions. Both vectors are normalised to unit sum; an ion's observed intensity is the most intense peak within the fragment tolerance (20 ppm high-res, 0.5 Da low-res, or --fragment-tol-ppm/--fragment-tol-da). |
pdeep_spec_mse_norm, pdeep_dotprod_norm, pdeep_cos_norm, pdeep_spectral_angle |
float | Mean squared error, dot product, cosine and normalised spectral angle of the same vectors. |
pdeep_pred_explained |
float | Predicted intensity share of the ions that matched a peak. |
<p>_rt_diff |
float | |observed − predicted RT| (min). Predicted RT is mapped to minutes per input file by an isotonic fit (a line below 20 anchors) on anchor PSMs: per spectrum the row with the highest RawScore, kept when it is a target at target-decoy q ≤ 0.01 within its file. |
<p>_predicted_rt |
float | Calibrated predicted RT (min). |
<p>_missing |
0/1 | 1 when the predictor could not score the row (a modification or length the model does not encode, no observed RT, no calibration); the row's other columns of that predictor hold the median over scored rows. |
The columns follow --predict only, never the data, so the PINs of separately searched runs
share one header. A predictor that scores no row (e.g. an input without retention times) still
adds its columns, with 0 and <p>_missing 1 on every row.
--glyco writes its own .glyco.pin without predicted features.
andes-rt encodes modifications by elemental composition (35 Unimod entries); AlphaPeptDeep
encodes the modifications its peptdeep_mods.json lists; DeepLC encodes CAM, oxidation, acetyl,
TMT, phospho, deamidation and pyro-Glu by composition.
3b. TSV columns¶
MGF header (decoys included):
| Column | Type | Description |
|---|---|---|
#SpecFile |
string | Input file name. |
SpecID |
string | MGF title or scan=N. |
ScanNum |
int | Scan number. |
Title |
string | MGF TITLE= field. |
FragMethod |
string | Activation (HCD, CID, …) or UNKNOWN. |
Precursor |
float | Precursor m/z (4 decimal places). |
IsotopeError |
int | Same as PIN isotope_error. |
PrecursorError(ppm) |
float | Mass error; PrecursorError(Da) in Da mode. |
Charge |
int | Assigned precursor charge. |
Peptide |
string | Peptide with modifications. |
Protein |
string | Primary protein accession. |
RankScore |
int | Rounded rank score (the only score column). |
Modifications |
string | ;-joined {1-based position}:{CV accession}. |
ProteinFamily |
string | Family accession of the Protein entry (its own accession for a FASTA or canonical entry). |
EntryKind |
string | reference, isoform, variant or chain. |
FeatureIds |
string | The feature that made the entry (VAR_…, P04637-2, PRO_…); empty for reference. |
mzML header: the same without Title (15 columns).
3c. PIN vs TSV — which to use¶
PIN for FDR and rescoring (Percolator, MS²Rescore, Mokapot, quantms); TSV for inspection.
3d. Run summary (<stem>.statistics.log)¶
Calibration and model choice can change the tolerances mid-run, so every search ends by
printing the final tolerances, the pre-FDR rank-1 target/decoy split and a
per-modification tally to stderr and to <stem>.statistics.log next to the PIN (<stem> is the
PIN's name without its extension, so per-run searches into one directory keep one summary each).
──────── andes run summary ────────
Final precursor tolerance : Symmetric(10.0 ppm) (calibration: Auto)
Final fragment tolerance : 0.5 Da
Spectra with a match : 48210
Rank-1 PSMs (pre-FDR) : 31204 target, 17006 decoy
PTM report (rank-1 target PSMs carrying each modification):
Carbamidomethyl : 28933
Oxidation : 6120
Acetyl : 341
(unmodified) : 2150
───────────────────────────────────
3e. QPX .idparquet bundle (--output-parquet)¶
--output-parquet <DIR> writes a QPX 1.0 bundle (psms.parquet, proteins.parquet,
protein_groups.parquet, search_params.parquet, and with --tmt or --lfq
quantms.feature.parquet) matching the OpenMS QPX schema, for
quantms. psms.parquet carries the peptidoform,
modifications, charge, m/z, is_decoy, scan/rt, proteins, the spectrum arrays, score
(andes:RawScore) and the other features in additional_scores. PEP and q-value are null
until rescoring. Each modification lists its sites in the QPX layout: position is an int32 (the 1-based residue index, 0 for the peptide N-terminus, length + 1 for the C-terminus) and amino_acid the residue, null on a terminus; the feature view uses the same layout. protein_groups.parquet is empty (protein groups are in
<stem>.protein_groups.tsv, §1). With --rescore, every target row must join a
Percolator result, except where target-decoy competition (-Y, which andes always passes)
kept another PSM of its input file, scan and ExpMass group: those target rows are left out of the bundle, as
they are out of Percolator's output; so are the decoys that lost that competition, and knockoff
PSMs, which are never reported. --rescore-native competes in the same groups, with the same
result for the bundle. Scan numbers repeat across inputs, so the groups are per input: Percolator reads
the input from the PIN FileName column. In the match-between-runs feature parquet (andes quant finalize),
posterior_error_probability is null on every row: a row is a precursor in a run, not a PSM.
andes --spectrum spectra.mzML --database db.fasta \
--output-pin out.pin --output-parquet out.idparquet
3f. Quantification tables (--tmt, --lfq)¶
Written next to the PIN (or next to --output-tsv/--output-parquet under --rescore without
--output-pin), see §10 for the methods. The quantification tables are Parquet (ZSTD) with typed
columns: missing values are null, proteins is ;-joined, retention times are in seconds. Each
file carries its schema version in the key-value metadata (andes.tmt.schema,
andes.lfq.schema, andes.mbr.schema, andes.mbr_validation.schema, currently 1.0); a new
major version marks an incompatible layout, a new minor version only adds columns. Read them with
pandas.read_parquet or pyarrow.parquet.read_table.
| File | Rows | Columns |
|---|---|---|
<stem>.tmt.parquet |
one per PSM with reporter ions, including the PSMs the quantification gate (§10) failed | filename scannr spec_id quant_scan quant_ms_level rt peptide charge proteins is_decoy psm_q_value psm_pep purity, then one tmt_<channel> (or itraq_<channel>) per channel (float32, corrected when a correction matrix was applied), then raw_<...> when one was, then reporter_intensity_sum reporter_sn_sum quant_weight quant_gate_passed, then one sn_<prefix>_<channel> per channel (null without noise values), then with --tmt-unmix interference and one unmix_<prefix>_<channel> per channel (null for a scan the interference model could not cover; §10). A gated PSM has quant_gate_passed false and quant_weight 0. Metadata andes.tmt.plex (kit), andes.tmt.corrected and andes.tmt.unmix |
<stem>.protein_groups.tsv (--rescore/--rescore-native) |
one per target protein group, best score first | protein_group lead accessions families peptides unique_peptides razor_peptides psms best_score protein_q subsumed (protein groups) |
<stem>.proteoforms.tsv (--rescore/--rescore-native with UniProt isoform, variant or chain entries) |
one per family member with a target PSM at q ≤ --fdr (a specific one for isoform, variant and chain entries) |
family member kind feature confidence q_value evidence specific_peptides shared_peptides psms best_psm alternative_explanation source prior_tier, then quantity_<run or channel> with --lfq/--tmt (§1a, proteoform table) |
<stem>.lfq.parquet |
one per feature per run (all target features, with their q-value) | filename peptide charge proteins scannr spec_id psm_q_value calc_mz rt_anchor rt_apex rt_start rt_stop n_scans area apex_intensity isotope_areas cosine n_isotopes score decoy_score feature_q_value accepted; isotope_areas is a list (monoisotope first); accepted is feature q ≤ --lfq-feature-fdr and cosine ≥ --lfq-min-cosine. The per-run wide view is the accepted rows pivoted on (peptide, charge) × filename over area (the highest score if a run has two). Metadata andes.lfq.runs (input stems, newline-joined, including runs without features), andes.lfq.feature_fdr, andes.lfq.min_cosine |
<stem>.mbr.parquet (--mbr, andes quant finalize -o <stem>) |
one per (precursor, run) identified in or transferred into the run, every evidence type | precursor_key run evidence_type quantity quantity_se quantity_normalized transfer_q quant_q apex_rt_s library_q; quantity_normalized is quantity with the run's level and RT-local offset removed (§10, null when the run is not normalised); evidence_type is identified (the run has a target PSM of the precursor at PSM q ≤ --quant-fdr and, with --pep, PEP ≤ --pep), identified_below_threshold (identified, but quant_q above --quant-feature-fdr or missing; quantity kept, in this table only), transfer_ms2 (transfer q ≤ --mbr-fdr, and an MS2 scan of the run matches the precursor's library spectrum: at least 4 fragments at a spectral angle of at least 0.6), transfer_ms1 (transfer q ≤ --mbr-fdr, without that MS2 support) or below_threshold (quantity kept, as censored; in this table only); quant_q is the quality q-value of the row's MS1 feature against its decoys (the identified-row estimate for identified rows, equal to transfer_q for transfers); transfer_q, quant_q and library_q are internal quant-quality values, not identification FDR; text columns are strings, the others float64, null for missing values, including the quantity of a row whose MS1 fit found no signal (its intensity list in the feature parquet is empty). Precursors that fail the library entry gate have no rows |
<stem>.mbr_validation.parquet |
one | counts are uint64, thresholds and rates float64 (null when undefined): pseudo-transfer validation (identified pairs re-measured as transfers: acceptance rate pseudo_acceptance_*, wrong-peak rate) and accepted transfer counts for the pooled and per-decoy-family estimates, at q ≤ 0.01 (_q01) and at --mbr-fdr (_at_threshold); then --quant-feature-fdr (null when off) and the identified rows: all, with a quant_q, at quant_q ≤ 0.01, and identified_below_threshold; then the MS2 evidence of the transfer rows: per hypothesis (ms2_target_*, ms2_mass_decoy_*, ms2_rt_decoy_*) and for the pseudo-transfers (ms2_pseudo_*) the rows with MS2 evidence (_rows) and those an MS2 scan supports (_supported), and the accepted transfers reported as transfer_ms2; then, at q ≤ 0.01 and at --mbr-fdr, the transfers the wrong-peak decoys alone would accept (accepted_wrong_peak_only_*) and the accepted decoy transfers per family (accepted_mass_decoys_*, accepted_rt_decoys_*, accepted_identity_decoys_*); then the target and identity-decoy library entries, the decoy transfers with a q-value per family, and the transfer targets not transferred because their entry's best donor PSM is above q 0.001 (donor_gated; counts the extracted rows only, so 0 when extract skipped those pairs, the default) |
<dir>/quantms.feature.parquet |
one per feature (LFQ) or quantified PSM (isobaric) | the QPX feature view (intensities as [{label, intensity}] with LFQ or TMT126-style labels; additional_intensities with the raw/apex/isotope values; purity, cosine, q-values in additional_scores; psm_ids pointing at psms.parquet rows) |
With --mbr (or andes quant finalize --output-parquet <dir>), quantms.feature.parquet holds
one row per identified, transfer_ms2 or transfer_ms1 row of <stem>.mbr.parquet instead of the per-run
--lfq features, with four extra columns: evidence_type, transfer_q, quant_q and
quantity_se. below_threshold transfers and identified_below_threshold rows are left out
(they stay in <stem>.mbr.parquet with their quantity and standard error), so every LFQ intensity
in the parquet passed the transfer q-value or, for an identified row, the identified quality
q-value (when --quant-feature-fdr is set; by default every identified row is kept); precursors
that fail the library entry gate are absent. Modifications come from the peptidoform's tags (the
accession is the name), proteins carry no positions, and psm_ids is empty.
Files in the quant directory (--quant-dir), all Parquet: per run <stem>.ms1.parquet,
<stem>.ms2.parquet and <stem>.quant.parquet (search), <stem>.extract.parquet (extract);
once library.parquet and alignment.parquet (library). Each file carries a schema version
(andes.<table>.schema); a reader rejects another major version and names the file.
<stem>.extract.parquet is at schema 4: schema 3 added the MS2 evidence of the transfer and
pseudo-transfer rows (ms2_n_scans, ms2_n_matched, ms2_angle, ms2_explained) and
schema 4 the per-isotope heights (iso_h0 to iso_h3) and tailing of every fit; an older
extract table is rejected (run andes quant extract again). library.parquet is at schema 4:
schema 3 added each entry's fragment spectrum (frag_mz, frag_intensity, frag_ion such as
y7 or b5^2) and its donor PSMs' runs and RTs (donor_scan_run, donor_scan_rt), and
schema 4 the identity-decoy flag (identity_decoy) and the best donor PSM's score, q-value
and PEP (donor_score, donor_psm_q, donor_psm_pep) of every entry.
<stem>.ms1.parquet holds one row per
MS1 scan in retention-time order: native_id, rt_s, faims_cv (null without FAIMS), and the
centroids as two equal-length lists mz (Float64) and intensity (Float32), stored bit for bit;
its metadata also names the run (andes.ms1.run). <stem>.ms2.parquet holds one row per MS2
scan in retention-time order: native_id, spec_key (the PIN SpecId of its matches without
the _<rank> suffix), rt_s, precursor_mz, charge (0 when unknown), the isolation window
isolation_target, isolation_lower, isolation_upper (offsets in Th; null when the source
has none, then the precursor m/z ± 1 Th is used), faims_cv, and the 100 most intense
centroids as mz and intensity (Float32, sorted by m/z); its metadata names the run
(andes.ms2.run), the search's fragment tolerance (andes.ms2.fragment_tol, e.g. 20ppm) and
the centroids kept (andes.ms2.top_peaks). <stem>.quant.parquet records the mass delta of
every modification accession its peptidoforms use (andes.run_quant.mods).
4. Auto-detection¶
With --fragmentation auto, andes takes the dominant <activation> cvParam over the first 64
MS2 spectra of an mzML (mixed methods warn) and the dominant analyser (none → low-res).
.raw carries both in vendor metadata (HCD on an Orbitrap → hcd_qexactive_tryp); .d
(timsTOF, beam-type CID) routes to the high-res HCD model hcd_qexactive_tryp (or its protocol
variant) with a 20 ppm feature tolerance, as in §1. MGF has none: andes counts 13C isotope
pairs (1.00335/z apart within 10 ppm) among the most intense peaks of the first 1,000 spectra and
compares the count with control spacings 0.07-0.21 Da away. An enrichment of at least 4x reads as
high resolution (HCD high-res model, 20 ppm features), below 1.8x as low resolution; in between,
or with fewer than 5,000 peaks, andes assumes CID / low-res / 0.5 Da (cid_lowres_tryp) and
warns. --fragment-tol-ppm implies a high-res instrument and --fragment-tol-da a low-res one,
whatever the peaks show. --protocol applies on top.
The resolution class found here (from the metadata, the flag or the peaks) is the run's
acquisition class, printed on the acquisition line of the resolved-parameter banner with the
recorded collision energy, isolation width and FAIMS voltages. It is kept apart from the selected
model, which routing may substitute (high-res ETD, CID and UVPD use low-res models; TOF runs use
an Orbitrap HCD model). Feature tolerances, the fragment-index vote mode, --score auto, the
--refine high-res gate, isobaric-label detection, --tmt-level auto and the --predict
instrument and NCE follow the acquisition class.
Per-run fragment tolerance. On a high-resolution acquisition without --fragment-tol-*,
each input file's fragment mass error is measured before it is scored: up to 3,000 spectra around
the median precursor mass are searched, the rank-1 targets at 1% target-decoy q have their seven
most intense b/y ions re-matched within 50 ppm, and the median and robust spread of those errors
are logged (Fragment error: ...). The file's fragment peaks are then scored at their
offset-corrected m/z (a copy; the spectra written to the quantification and the outputs are not
changed), so the fragment-index retrieval, the rank scoring and every feature see peaks centred
on zero. The PIN match features (match counts, ion-current ratios, error statistics,
chance-match and mass-competition terms) use 4 x spread, clamped to 5-40 ppm, and the
ppm-Gaussian term has a width of 1.5 x spread. The features fed to the trained
fragment-intensity and rich-ion models and the fragment index keep the 20 ppm class window,
now around the corrected peaks. --fragment-tol-ppm / --fragment-tol-da replace the
measurement (no correction) on any input format.
Activation CV mapping (mzML <activation> cvParam accession → method)¶
| CV accession | Name (PSI-MS) | andes method | Notes |
|---|---|---|---|
MS:1000133 |
collision-induced dissociation | CID | |
MS:1000422 |
beam-type collision-induced dissociation (HCD) | HCD | |
MS:1000598 |
electron transfer dissociation | ETD | |
MS:1000599 |
pulsed Q dissociation | CID | PQD is scored as CID |
MS:1000435 |
photodissociation | UVPD | |
MS:1000250 |
electron capture dissociation | ETD | Mapped to ETD (no dedicated ECD variant) |
Instrument detection (analyzer cvParam → class)¶
| Analyzer family | Examples | Instrument class |
|---|---|---|
| Ion trap / linear ion trap | MS:1000264, Velos, LTQ |
low-res |
| Orbitrap / Fusion | MS:1000480, Fusion Lumos |
QExactive |
| FT-ICR | MS:1000480 (FT) |
high-res |
| TOF | MS:1000128 |
TOF |
Bundled model store (resources/models/)¶
17 models in resources/models/protocol=<Automatic|TMT|Phosphorylation|iTRAQ>/models.parquet
(9 / 3 / 4 / 1 models), listed in README.md. If detection
fails, andes falls back to hcd_qexactive_tryp or the closest regime and names it in the run
summary.
5. Building from source¶
Rust 1.85+ (rust-toolchain.toml pins 1.87.0).
git clone https://github.com/bigbio/andes
cd andes
cargo build --release
# Binary: target/release/andes (mzML + MGF; pure Rust)
Native vendor formats are feature-gated:
# Thermo .raw — needs rustc >= 1.88 and, at run time, the .NET 8 runtime (point DOTNET_ROOT
# at an install that contains shared/Microsoft.NETCore.App, or "One of the dependent
# libraries is missing" is the error you get).
RUSTUP_TOOLCHAIN=stable cargo build --release -p andes --features thermo
# Bruker timsTOF .d — pure Rust, no vendor runtime
cargo build --release -p andes --features timstof
# Both at once (what the release archives ship for desktop/server targets)
RUSTUP_TOOLCHAIN=stable cargo build --release -p andes --features "thermo timstof"
Tests:
cargo test --release --workspace
CI runs the whole workspace suite with no skips; the out-of-core PIN goldens
(cargo test -p andes --release --test out_of_core_pin_goldens -- --ignored) and
cargo test -p onnx-infer --release --features tract run as separate steps.
6. Training new scoring models¶
andes train-from-search searches your data with a seed model, keeps PSMs at q ≤
--train-fdr, and writes a model into a Parquet store; search with it via
--model-store <path> --model <id>. Incremental updates (--update --add /
--remove-source / --reweight / --decay) pass a held-out acceptance gate. andes train
--in <parquet> trains from externally labelled PSMs instead. See TRAIN.md.
Models trained outside andes (tree ensembles, ONNX predictors): docs/PORTING_MODELS.md.
7. Isobaric labeling¶
Set --protocol TMT or --protocol iTRAQ (selects hcd_qexactive_tryp_tmt /
hcd_qexactive_tryp_itraq) and declare the label as a fixed mod.
TMT (10-plex example)¶
TMT10plex = 229.162932 Da on K and peptide N-terminus (Unimod); mods.txt as in §2 example (b).
andes \
--spectrum tmt_spectra.mzML \
--database hsapiens.fasta \
--output-pin out.pin \
--mods tmt_10plex_mods.txt \
--protocol TMT
iTRAQ (8-plex example)¶
iTRAQ8plex = 304.20536 Da on K and peptide N-terminus.
NumMods=2
57.02146,C,fix,any,Carbamidomethyl
304.20536,K,fix,any,iTRAQ8plex
304.20536,*,fix,N-term,iTRAQ8plex
andes \
--spectrum itraq_spectra.mzML \
--database hsapiens.fasta \
--output-pin out.pin \
--mods itraq_8plex_mods.txt \
--protocol iTRAQ
For phospho-enriched iTRAQ use --protocol iTRAQ-phospho plus phospho mods (§2 example c).
8. Legacy numeric values & behavior notes¶
Legacy MS-GF+ numeric values (e.g. --fragmentation 3, --protocol 4) are no longer
accepted; use the names (case-insensitive, --fragmentation hcd ≡ HCD). The MS-GF+
-ntt setting is --enzyme-specificity fully|semi|non-specific.
Behavior notes¶
- No mzIdentML output.
- Bundled models are trypsin-trained except the three low-res LysC/ArgC/GluC models.
- Spectra stream in chunks of 5000, so large mzML files are not loaded whole.
9. Glycopeptide search (experimental) & advanced knobs¶
--glyco searches intact glycopeptides and writes a .glyco.pin. By default it
searches N-glycopeptides (N-X-S/T sequon backbones, trimannosyl core). With
--glyco-core o it searches O-glycopeptides instead: any Ser/Thr is a
glycosite (no sequon), the core is a single reducing-end GalNAc, and the glycan
database must be an O-glycan .gdb (e.g. a published O-glycan database). The
defaults are validated; most knobs are hidden.
--glyco raises --max-missed-cleavages to at least 3: sequon-bearing tryptic
peptides often need a third missed cleavage to reach a scoreable length. An explicit
--max-missed-cleavages below 3 is honoured (with a note), because the floor of 3 grows
the candidate index by about 40% (13.2M to 18.8M candidates on a 20k-protein human FASTA,
about +4.4 GB resident), the difference between running and being killed for memory on a
16 GB machine. A value of 3 or more is used as given.
Main flags:
| Flag | Default | Purpose |
|---|---|---|
--glyco-tol-ppm |
20 | Fragment tolerance for glyco matching (oxonium, core-Y, backbone mass, c/z). Raise it on ion-trap MS2 or the oxonium gate never fires. |
--glyco-glycan-gdb <FILE> |
— | .gdb glycan database (canonical-string trees, the format of the bundled databases); structure (core- vs antenna-fucose) is kept. Wins over --glyco-species. |
--glyco-species <NAME> |
— | Bundled database: human, mouse, mouse-large, high-mannose. One of the two is required. |
--glyco-core <n\|o> |
n |
Glycan core: n = N-glycan (N-X-S/T sequon gate, trimannosyl core), o = O-glycan (any S/T gate, single GalNAc core). An o run needs an O-glycan .gdb. |
--precursor-mono |
auto |
Correct each precursor to the monoisotope its MS1 envelope supports (mzML or .raw): tests "recorded = M+k", k = 0..6, and moves down k−1 isotopes when k > 0 clearly wins. On the pGlyco2 mouse-liver reference (PXD005553) 88 of 3,824 scans were recorded exactly +4 high (issue #64). Fitted spectra are searched with a 0..1 isotope window instead of 0..2. Adds MonoShift/MonoFit/MonoFitGain/MonoSNR. An explicit --isotope-error is honoured; without MS1 the search is as with off and the Mono columns are 0. The Mono columns follow the flag and the input format only (mzML or .raw under --glyco), so separately searched runs share one PIN header. Under --glyco the isotope-error window defaults to 0..=2 (dropping −1 measured +81 backbone-correct @1%). |
--glyco-max-peaks |
0 (no cap) | Peaks the generation stage considers; 300–500 helps very dense scans. |
--glyco-retrieval-tol-ppm |
tol-ppm on high-res, the model's Da window on low-res | Retrieval window only; 20 ppm on high-res measured 6.9x faster at no identification cost. |
--glyco-y-max-charge |
3 | Maximum glycan-Y fragment charge. |
--glyco-cz-max-charge |
derived | Maximum c/z charge on ETD. |
--glyco-hcd-pair |
on | ETD, single-file runs: backbones from the paired HCD scan, c/z scored on the ETD scan (+153 backbone-correct @1%). Disabled with a warning for multi-file runs. |
--glyco-min-core-y |
0 | Require N trimannosyl-core Y ions before reporting. |
--glyco-scans <FILE> / --debug-glyco |
off | Diagnostics. Never feed a --debug-glyco PIN to an FDR tool. |
Tuning knobs (hide = true):
| Flag | Default | Purpose |
|---|---|---|
--glyco-index-sequon-only |
off | Index only N-X-S/T peptides: the mouse entrapment recipe needs 3.4 GB instead of ~27 GB; 16 of 7,113 rows (0.2%) differ in RawScore/CandidateRankEntropy only. |
--precursor-mono-dump <FILE> |
off | Write every --precursor-mono envelope fit as TSV. |
--glyco-peptide-first |
off | Peptide-first b/y fragment-index fallback instead of the default mass-driven full-glycan-list branch: ~2% more glycoPSMs on a mouse-liver fraction at ~13x the run time (79 vs 6 min) and 29.7 vs 19.9 GB peak memory. |
Fixed settings (validated; no flag): selector rank + K·ladder + J·core_y + H·hyper with
K/J/H = 10/5/1, ETD c/z weight 15 and isotope-offset penalty 1; 150 backbone candidates per
spectrum after the DB/de-novo union; ETD c/z evidence can rescue a backbone from truncation;
on ETD the rank/edge/hyperscore path scores the backbone with its intact glycan (+33
backbone-correct @1%); the best enumerated candidate is promoted over a de-novo winner;
peptide-first index at fragment charges 1..=2 with at most 1,024 candidates per spectrum;
--precursor-mono uses max shift 6, minimum fit 0.90, minimum gain 0.15, minimum SNR 3.0,
10 ppm MS1 tolerance and holds back 1 isotope; --glyco-core o additionally gates Hex>0
(galactose) candidates on a Hex diagnostic ion (163.060 or 366.139) in the HCD spectrum —
the GalNAc oxonium every O-glycopeptide carries cannot justify adding galactose (N-glycan is
unaffected).
Deleted after A/B tests (2026-09-05): --glyco-split-election, --glyco-gp-g,
--glyco-gp-m, --glyco-isobar-rep, --glyco-y-index, --glyco-decorated-features,
--glyco-cz-intensity, --glyco-per-spectrum-model, --tight-highres-scoring,
--glyco-y-tree, --glyco-oxonium-llr, --glyco-rank-masked, --glyco-chance-llr-masked,
--glyco-transfer (and its five knobs) and --glyco-decoy
(refuted).
Removed 2026-10 (defaults fixed as above, or diagnostics/opt-ins that were never adopted):
--glyco-gp-k/-j/-h/-cz/-iso, --glyco-backbone-top-k, --glyco-pf-charge,
--glyco-max-pf, --glyco-retrieval-tol-da, --glyco-isotope-error,
--glyco-elect-top-k, --glyco-cz-gate, --glyco-cz-multisite, --glyco-etd-rank-glycan,
--glyco-enum-fallback, --glyco-pair-y-on-gen, --glyco-etd-require-oxonium,
--glyco-min-raw-score(-quantile), --glyco-min-matched-ions,
--glyco-sialic-oxonium-min-frac, --glyco-pin-curated, --glyco-diag-splits and the
--precursor-mono-* tuning flags.
Run Percolator on a glyco PIN with --trainFDR 0.05. A glyco run has too few positives
at the default 1% training threshold for a stable fit. Pooled human-plasma glyco PIN, same
rows, five seeds each:
--trainFDR |
PSMs @1% | agreeing with the reference identifications |
|---|---|---|
| 0.01 (Percolator's default) | 210.6 ± 50.9 | 171.0 ± 37.6 |
| 0.05 | 384.6 ± 19.9 | 301.4 ± 11.1 |
| 0.10 | 348.8 ± 9.6 | 283.2 ± 6.1 |
The default is worse and erratic (112 vs 256 PSMs across seeds on identical input). Do not filter low-scoring rows out of the PIN instead: they are about half decoys and Percolator needs them to place a threshold.
10. Quantification¶
andes quantifies at the feature level inside the search run and leaves normalization across
samples, peptide-to-protein roll-up and statistics to quantms (with MSstats / MSstatsTMT),
which knows the experimental design. Both modules run after rescoring, on
the rank-1 target PSMs at q ≤ --quant-fdr (default --fdr, 0.01); without --rescore /
--rescore-native every rank-1 target PSM is quantified and the tables must be filtered
downstream (a warning says so).
MS1 scans are stored losslessly in private temporary files after each input run is
searched, then loaded one run at a time for quantification and precursor purity.
They do not remain in memory while Percolator runs. Scratch space is approximately
12 bytes per centroid plus scan headers; files are removed when the run exits
normally or returns an error. --temp-dir chooses the scratch directory (default: the
directory of the output files). A
single input run's MS1 scans still need to fit in memory while it is processed.
Isobaric reporter ions (--tmt <plex>)¶
- Extraction. For each quantified spectrum, the most intense centroid within
--tmt-tolof each reporter m/z of the kit (the quantms channel tables: TMT 6/10/11/16/18/32/35-plex, iTRAQ 4/8-plex); 0 when absent.--tmt-level 2reads the identified MS2;--tmt-level 3reads the MS3 whose<precursor spectrumRef>(mzML) orMaster Scan Numbertrailer (.raw) is that MS2 — the MS3 scans are kept by the reader for this and are never searched. The default--tmt-tolfollows the reporter scan: SPS-MS3 reporters are read in the Orbitrap even when the MS2 is an ion-trap scan, so--tmt-level 3defaults to 20 ppm (10 ppm for TMTpro 32/35-plex). A reporter scan whose recorded scan window (scan window lower/upper limit, the.rawacquisition range) does not reach every reporter m/z is not quantified — an automatic first mass above 126 Th never measured the low channels, and their zeros are not ratios — and is counted in the log. - Impurity correction (
--tmt-correction <file>). The kit lot sheet in the quantms text form, one line per channel in kit order,-2/-1/+1/+2percentages separated by/(eight columns-2C13/-N15-C13/-C13/-N15/+N15/+C13/+N15+C13/+2C13for TMTpro 16/18), optional<channel>:prefix,#comments,NA/-1for no spill. Labels must match kit order; rows whose percentages sum to more than 100 are rejected. The observed vector is solved asobserved = M · trueby non-negative least squares, so corrected intensities agree with OpenMS IsobaricAnalyzer for the same matrix. Without a sheet, no correction. The raw values are always kept (raw_columns, parquetadditional_intensities). - TMTpro 32/35-plex (
tmt32,tmt35). The deuterated reagents put a 2H in place of a 13C in the reporter, so eachDchannel sits 2.9 mDa above its non-deuterated twin; the default--tmt-tolis 10 ppm instead of 20 ppm (a window must stay below 10.8 ppm at 135 Th). Channels are reported in OpenMS's kit order (128N,128C,128ND,128CD, ...), not by m/z. There is no impurity correction: OpenMS ignores a sheet for these kits and applies the identity, and--tmt-correctionis refused before the database is loaded. Peptides are searched with the TMTpro tag mass (304.2071 Da); each deuterated tag is 2.9 mDa heavier, which the default 20 ppm precursor window absorbs. Below 8.8 ppm (three deuterated tags on a 1000 Da peptide) andes warns that the deuterated channels' precursors can fall outside--precursor-tol. - Precursor purity. The share of the MS1 isolation window's ion current that belongs to the
precursor's isotope envelope, from the MS2's survey scan — the scan its precursor references
(mzML
<precursor spectrumRef>,.rawMaster Scan Number), else the last MS1 before it at the same FAIMS compensation voltage, else the last MS1 before it. (On FAIMS runs the last MS1 in time is usually a survey scan at another voltage, which does not contain the precursor.) The most intense peak within 10 ppm of the precursor m/z anchors a ¹³C isotope ladder (10 ppm per step, both directions); the isotopes that fall inside the strict isolation window count as envelope. The window is centred on the isolation target m/z (mzMLisolation window target m/z), not on the precursor m/z: when the instrument isolated a heavier isotope than the monoisotopic m/z it assigned, the window covers what was actually isolated. Without a recorded target the window is centred on the precursor m/z. The native.rawreader reports the window around the monoisotopic m/z, so on.rawinput the window stays centred there. This is the ladder of OpenMS's precursor purity without its fuzzy window border and next-scan interpolation, and with ¹³C spacing and a two-sided nearest-peak match (stepping by the neutron mass misses the upper isotopes). Reported per PSM. Needs the isolation window offsets in the file, and is not computed under--chimeric. - Signal-to-noise. Orbitrap reporter scans carry the instrument's noise estimate: per
centroid in a Thermo
.raw, and as thesampled noise m/z array/sampled noise intensity arrayof an mzML converted with noise data (ThermoRawFileParser's noise-array option). The noise at each reporter m/z (linear between the sampled points) divides the reporter intensity (the corrected one when--tmt-correctionis given) into a per-channel signal-to-noise, written as thesn_columns and their sumreporter_sn_sum. Both noise sources give the same values on the same scans. Ion-trap reporter scans, and mzML written without noise arrays (the converters' default), have none: thesn_cells stay empty and--tmt-min-sndoes not apply to them. - Quantification gate. A PSM passes the gate (
quant_gate_passedintmt.parquet) only if its precursor purity is at least--tmt-min-purity(0.7), its Percolator PEP is at most--tmt-max-pep(0.05) and its summed reporter signal-to-noise is at least--tmt-min-sn(off). A PSM without the value a criterion needs (no MS1 or isolation window for purity, no--rescoreor--rescore-nativefor PEP, no noise for S/N) is not held to that criterion. The gate only chooses which PSMs are quantified: every PSM stays in the identification output (PIN, TSV,psms.parquet), andtmt.parquetand the quantms feature parquet keep every quantified PSM withquant_weight0 for the gated ones. The log counts the PSMs each criterion removed. On SPS-MS3 data low-purity and high-PEP PSMs carry most of the interference and ratio compression of the PSMs a deep search adds; purity barely matters for high-resolution MS2 reporters, where the remaining interference is co-isolation inside a pure-looking window. Set all three to their off values to quantify every confident PSM. - Interference unmixing (
--tmt-unmix, off by default). The reporter ions of a high-resolution MS2 scan come from everything the quadrupole isolated, and purity misses most of it: much of the co-isolated ion current is below the MS1 detection limit. Each quantified scan gets an isolation-window record from the MS1 scans before and after it (same FAIMS voltage), linearly interpolated to the MS2 retention time: the window's ion current, the part of it on the identified precursor's isotope envelope (calculated m/z, 10 ppm), and an estimate of the ion current hidden below the detection limit (the scans' 5th-percentile centroid intensity × the centroid density within ±5 Th × the window width). Per run, a robust non-negative regression explains each scan's summed reporter intensity by the precursor's share of the MS2 peptide ion current (one yield per charge class: ≤ 2, 3, ≥ 4), the co-isolated share and the hidden ion current. The coefficients are the run's scale factors; no ground truth or reference channel is used. A scan'sinterferenceis the predicted reporter share that is not the precursor's (capped at 0.8), andunmix_<channel>= intensity − interference × summed intensity × the run's mean channel profile (floored at 0). A run with fewer than 100 modelled scans gets emptyunmix_cells and a WARN. The existing channel columns are unchanged. Removing interference decompresses ratios at the price of a higher CV (the remaining signal is smaller and keeps its noise). SPS-MS3 reporters (--tmt-level 3) are not modelled. - Roll-up weight (
quant_weight). For a PSM that passed the gate, purity² (1 without a purity); 0 for a gated PSM. A protein or peptide sum weighted by it lowers the influence of the PSMs most exposed to co-isolation. The corrected intensities are never rescaled by it. - Not done here: reference-channel ratios, channel normalization, protein aggregation.
andes --spectrum tmt_run.mzML --database human.fasta --mods tmt10_mods.txt \
--rescore --fdr 0.01 --tmt tmt10 --tmt-correction lot_XY.txt \
--output-parquet run.idparquet
Label-free MS1 features (--lfq)¶
- Targets. One per (peptidoform, charge, run) from the best confident PSM, with its retention time as the anchor; the theoretical isotope envelope comes from the peptide's elemental formula (standard residues exact; modification deltas as averagine atoms).
- Extraction. The first four isotope chromatograms at
--lfq-tolover ±--lfq-rt-windowaround the anchor, from every MS1 scan of the run (16 bytes per centroid in memory; kept in a temporary MS1 table, the<stem>.ms1.parquetformat, between search and quantification). The monoisotopic trace is smoothed (5-point Savitzky–Golay by default).--lfq-gaussian-width <seconds>instead uses an RT-weighted Gaussian kernel (width = eight sigma, finite support ± four sigma), following the common eight-sigma width convention. This optional setting can reduce premature boundaries in noisy traces; choose it for the chromatography and validate accuracy and coverage on held-out runs. It also changes the feature-score distribution: a width that improves dilution ratios on one acquisition can sharply reduce acceptance at the same feature-q cutoff on another. Audit both diagnostic features and accepted features; this is an experimental opt-in setting. Both targets and decoys use the same smoothing; integrated intensities always come from raw traces. The peak that contains the anchor is climbed to its apex, and each boundary is placed two half-widths at half maximum from the apex (±2.35 σ of a Gaussian, 98 % of its area) or at an earlier deep valley (the trace fell below half the apex and rose 1.5x again). Every isotope is integrated (trapezoid, intensity·seconds) over the same boundaries. Intensity = summed isotope areas; the apex height and per-isotope areas are reported too. - Envelope check. Cosine between the per-isotope areas and the theoretical envelope;
targets and decoys below
--lfq-min-cosineare excluded before competition. The diagnostic outputs retain all target features. A feature whose monoisotopic area holds less than 30 % of its theoretical share is not reported at all: its M+1.. traces belong to an isotope-shifted species (a deamidated form, say), not to the target. - Resolution check. Each run's MS1 scans are tested once: Orbitrap/TOF survey scans give 70–95 % of their most intense centroids an isotope partner within 10 ppm. A run far below that is reported as low-resolution in a warning; label-free areas from it are not meaningful.
- Feature q-value (experimental). Each target gets a decoy twin extracted 11 Th above it;
unrelated real peptides can occur there. The score
cosine³ · (1 − |Δrt|/window)^⅓ · √(apex/max apex)uses the maximum apex over both targets and decoys. After applying the same cosine cutoff to both, picked competition retains the higher score (exact ties go to the decoy). The(decoys + 1)/targetsestimate is evaluated over complete equal-score groups and converted to monotone q-values.lfq.parquetmarks target winners at q ≤--lfq-feature-fdrasaccepted; it and the QPX parquet retain all target features, with no feature q-value for ineligible targets or targets that lost competition. These are decoy-based estimates, not a demonstrated error-rate guarantee: the shifted-decoy model still needs calibration on representative negative-control/entrapment datasets. PSM identification FDR and quantitative accuracy are separate from this estimate. Feature competition does not replace PSM confidence: an incorrect peptide assignment can still anchor a convincing MS1 trace and survive the feature filter. - FAIMS. On runs with several FAIMS compensation voltages the survey scans of each voltage form their own chromatographic series, and each precursor is extracted from the series of the voltage its PSM was acquired at. (Interleaved voltages otherwise put zeros between every measured scan, which truncates the peak boundaries: on a three-voltage Lumos run the mixed series gave a median integration window of 4 s instead of 22 s and areas ~10x too small.)
- Runs. Each
--spectrumfile is quantified on its own identifications;lfq.parquetholds the features of every file (filename). Transfers between runs are--mbr(below). - Not done here: normalization, delayed-normalization or top-N protein intensities.
andes --spectrum run1.mzML --spectrum run2.mzML --database human.fasta \
--rescore --fdr 0.01 --lfq --output-parquet runs.idparquet
Match between runs (--mbr, andes quant)¶
A precursor (peptidoform and charge) identified in some runs is measured in every run, so
each (precursor, run) gets one consistent quantity. Four stages, run in one process by
andes search --lfq --mbr --rescore on two or more inputs, or one at a time over a quant
directory (for a pipeline that scatters runs over machines):
# per run (any machine): MS1 + PSM tables into the shared directory, PIN, Percolator
andes --spectrum a.mzML --database db.fasta --output-pin a.pin --quant-dir q/
andes --spectrum b.mzML --database db.fasta --output-pin b.pin --quant-dir q/
# once: the library, from each run's Percolator results
andes quant library --quant-dir q/ [--quant-fdr 0.01] [--pep 0.05] \
--psms a=a.target.psms --psms b=b.target.psms \
--decoy-psms a=a.decoy.psms --decoy-psms b=b.decoy.psms
# per run (any machine that sees q/); --lfq-tol as in the search, default 10ppm
andes quant extract --quant-dir q/ --run a [--lfq-tol 10ppm]
andes quant extract --quant-dir q/ --run b [--lfq-tol 10ppm]
# once
andes quant finalize --quant-dir q/ -o experiment [--mbr-fdr 0.01] [--quant-feature-fdr off] \
[--output-parquet dir --database db.fasta --psms all.target.psms --decoy-psms all.decoy.psms \
[--protein-fdr 0.01]]
With --output-parquet, finalize writes the QPX views of the experiment and needs the protein
database and the Percolator results of every run (--database, --psms, --decoy-psms and
the database options, as andes inference). It infers the
protein groups once over every run's PSMs, then:
quantms.pg.parquetholds the groups at or below--protein-fdr(default 0.01), with the decoy groups at the cut, andfeature_counts(features of the group's unique peptides, and all).contaminantis true for groups with aCONTAMINANT_accession.- Each row of
quantms.feature.parquetcarries its peptide's group:pg_accessions(the group's accessions),anchor_protein(its lead),unique(false for a peptide another group also contains, assigned to this one as razor) andpg_global_qvalue. Features whose peptide counts for no group at or below the cut are left out (they stay in<stem>.mbr.parquet). - Only identifications are protein evidence: transfers quantify precursors identified in other runs and never add a group.
Protein quantities are left to the downstream roll-up, which reads the two views.
PSMs are joined to the run tables on (run, SpecId). Percolator results come either per run
(--psms <stem>=<file> and --decoy-psms <stem>=<file> for every run) or as one merged pair
of files (--psms <file> --decoy-psms <file>, e.g. Percolator over the concatenated PINs of
all runs). Every SpecId starts with its input's file stem, so SpecIds of separately searched
runs do not collide. Two inputs with the same stem do, and a merged pair over them is
rejected, naming the SpecId and the runs. Where possible, prefer one
Percolator run over the PINs of all runs (one merged pair, or split per run) to a separate
Percolator run per input: the library ranks each precursor by its best score across runs, and
scores from separately trained Percolator models are not on one scale.
Percolator is the slowest stage of this pipeline on a full-size run (a PIN of about one
million PSMs). Two settings shorten it; neither is applied by andes, which passes only
--seed 42 --only-psms:
- Threads. Percolator trains its three cross-validation folds in parallel when it has
three CPUs (
--num-threads, default 3); on one CPU the same run takes about 2.5 times as long. Its results depend on the thread count (deterministically: the same count gives the same output), so keep--num-threadsfixed when results are compared. -
--subset-max-train 300000. Trains the SVM on a random subset of 300,000 PSMs (seeded) and scores all of them; about a third of the CPU time. On the ProteoBench HF-X runs it moved PSMs at q 0.01 per run by -0.1 to +0.2 % and the precursors quantified in at least three runs by +0.4 % (transfer q 0.01; median |ε| 0.162 and CV 0.119 before and after, E. coli |ε| 0.273 to 0.274). With a whole-protein shuffled entrapment database the PSM entrapment FDP went from 0.97 % to 1.01 %, entrapment-only quantified precursors (at least three runs) from 0.24 % to 0.23 %, and entrapment rows among about 70,000 accepted transfers from 88 to 107. 100,000 cost about 0.5 % of the quantified precursors. -
Library (
quant library). Reads every run's<stem>.quant.parquet(--runs, or every table in the directory; a missing PSM, MS1 or MS2 table is an error) and joins Percolator'sscore,q-valueand PEP to its PSMs on (run, SpecId) (target and decoy result files; every PSM needs a finitescore). Each joined PSM is checked against the result's residue sequence (flanks and modification notation ignored) and target/decoy file. A PSM that disagrees is skipped (counted in the log), and a run where more than 1 % of the joined PSMs disagree fails, naming the run and the file (swapped or foreign results). Runs are indexed by sorted stem, precursors by sorted key. A precursor enters the library when its best-PSM target/decoy q-value over all runs is at most--mbr-library-fdr(an internal entry gate over every PSM's score, not a reported FDR) and it has a target PSM at PSM q-value ≤--quant-fdr(and PEP ≤--pepwhen given, as for the per-run quantification). A run identifies the precursor (is a donor) when it has such a PSM there: only those PSMs give the identified RT, the alignment anchors and theidentifiedrows. A run where the precursor has only PSMs above--quant-fdr(or above--pep) gets a transfer, with a transfer q-value, like a run without any PSM. A decoy precursor that passes the same entry gate with a decoy PSM at the same donor thresholds becomes an identity decoy: an entry measured exactly like a target entry (identified rows in its donor runs, transfers with their decoy probes elsewhere) but never reported. Its observations are added to the alignment after the run maps are fitted, so they never move a map but predict its transfers as a target's would. Each entry records its best donor PSM (highest score) with its score, q-value and PEP. In process,--quant-fdrand--pepare the search's (default--fdr, no PEP cap);quant library --quant-fdrdefaults to 0.01 andquant library --pepto no cap. A joint, reference-free retention-time alignment maps every run onto a consensus scale and gives each transfer a predicted apex and its uncertainty. The per-run residual width is floored at 0.5 s. The uncertainty combines the receiving run's residual width with the donor runs' own widths (carried through each run's map), so a precursor seen only in a run with noisy RTs gets a correspondingly wide search window everywhere. A run with fewer than 50 anchors, or whose anchors cover less than 30 % of the RT range, is unaligned: it is logged, neither gives nor receives transfers, and keeps theidentifiedrows of its own identifications. Two aligned runs that share at least 20 identified precursors are linked; the groups of runs connected this way are alignment components. Only shared precursors fix the relative RT scale of two runs, so each component is fitted on its own anchors, and a precursor is transferred into a run only from donor runs in the same component. A run that shares fewer than 20 precursors with every other aligned run is unaligned. When the aligned runs form several components the library log lists them, and each extract log counts the transfers and pseudo-transfers skipped because every donor run lies in another component. Each entry also gets an empirical fragment spectrum from its best donor PSM (highest rescorer score): the peptidoform's b and y ions of at least 3 residues (charge 1, and 2 for precursors of charge 3 or more) are matched on that PSM's scan in the run's<stem>.ms2.parquetat the search's fragment tolerance, and the 12 most intense matches are kept (intensities relative to the most intense). An entry with fewer than 3 matches has no spectrum; the library log counts them. - Extract (
quant extract, one run, readslibrary.parquet,alignment.parquetand the run's<stem>.ms1.parquet). Every library precursor is measured, at the--lfq-tolm/z tolerance (ppm; default 10 ppm), by a likelihood template fit of its isotope pattern and elution profile around the predicted apex (around the PSM's RT where it was identified), with two decoy hypotheses per transfer and per identified pair: a mass-defect-preserving m/z shift and an RT shift, each with the target's prior (an identified pair's decoys keep its PSM-centred prior, so the decoys search as broadly as the target). Identified pairs are also measured as if they were transfers (pseudo-transfers). FAIMS runs are fitted on the series of the precursor's compensation voltage (its most frequent voltage among the donor PSMs). A precursor whose voltage the FAIMS run lacks is not measured there and is counted in the log; it is never fitted on another voltage or on the mixed series. Precursors without a voltage, and every precursor in a run without FAIMS, are fitted on the whole run. The time scales of the fit come from each run's own MS1, before any row is fitted: the median spacing of its MS1 scans (of one FAIMS voltage) is the floor of every RT prior width, and the run's elution-width scale is the median fitted width of up to 500 identified pairs spread over the library (width free over 0.5–2 times the current scale, re-estimated until a round moves it by less than 10 %, starting from 6 s; 6 s when fewer than 20 fits are usable). The width prior along the gradient is then fitted on every identified pair around that scale, and an identified pair's apex is searched within three of the run's elution widths at its RT (at least three scan spacings) of its PSM. Narrow-peak instruments are thus not held to a wide fixed width, and a larger neighbouring peak of the same m/z a few widths away is not taken for the identified one. The extract log and the table'sandes.extract.run_scalestag record the scan spacing and the width scale of each run;quant finalizelogs them. Transfer pre-selection (advanced,--mbr-preselect, defaulttrue): before any fit, extract skips the transfers of an entry whose best donor PSM is above q 0.001, which finalize never transfers (on a six-run benchmark 19 % of the transfer fits; the reported rows are identical), and, with--mbr-min-donor-fraction F(default 0), the transfers with fewer other aligned donor runs than F × (aligned runs − 1), rounded up. The rule reads the library only and skips a pair whole (target and decoys alike, identity decoys by the same rule from their own donors), so every kept pair keeps its full set of decoys and the transfer FDR stays a K-decoy estimate over the kept pairs. Skipped pairs get no rows and are counted in the extract log; the table then carries anandes.extract.transfer_selectiontag. On a 500-run study F = 0.004 (two donor runs) left out 13 % of the transfer fits (12 % of the extract CPU time); the accepted transfers changed less than between two finalize seeds, but 1.1 % of those accepted at q 0.05 without it fell in skipped pairs, hence off by default.falsemeasures every transfer pair (identical to earlier releases). Every transfer row (target and both decoys) and every pseudo-transfer with a fit then gets MS2 evidence from the run's<stem>.ms2.parquet: among the MS2 scans whose isolation window holds the row's probe m/z (the decoy's own shifted m/z for a mass-shift decoy) and whose RT lies within one fitted width of the fitted apex (the width bounded to 1–20 s), so the scan belongs to the quantified peak, at the entry's FAIMS voltage, each is scored against the entry's library spectrum: matched fragments, the normalised spectral angle of the square-root intensities (unmatched fragments count as 0) and the share of the library intensity matched. The best scan by angle gives the row's MS2 evidence; no scan gives zeros. Decoys use their target's library spectrum, so a decoy on another peptide's peak is scored against that peptide's MS2, as a false transfer would be. A pseudo-transfer leaves out the scans of its run's donor PSMs, whose identification makes it a pseudo-transfer. The extract log gives, per role and hypothesis, the share of rows with a scan in the window and the share an MS2 scan supports. - Finalize (
quant finalize). A cross-validated GBDT (3 folds by precursor) separates transfer targets from their decoys; the transfer q-value is the K-decoy estimate over held-out scores,(D + 1) / (K · max(T, 1)) + D_id / max(T, 1)at each score threshold (capped at 1, then the running minimum), withDthe mass- and RT-shift decoys,Ktheir ratio to the targets andD_idthe identity-decoy transfers at or above the threshold; the wrong-peak part is also reported per decoy family. The two parts estimate two kinds of error. The mass- and RT-shift decoys model the right peptide quantified on the wrong peak. A false donor identification is different: the false PSM matched a spectrum of another analyte, whose MS1 feature recurs in every run with the same width, tailing and isotope heights, so its transfers look like true ones to every feature and to those decoys. Identity decoys model it. Under target-decoy competition (the assumption that the PSM q-values already rest on) a decoy PSM that passes the donor thresholds is as likely as a false target PSM that passes them, and it too sits on a real feature; so the identity-decoy entries stand one for one for the false target entries, and their accepted transfers estimate the accepted transfers of false donors at each threshold, without scaling. The parts overlap (a false donor's transfer can also land on a wrong peak), so their sum is conservative. Identity decoys are negatives of the classifier like the other decoys, held out by the same folds; only their target probe takes part (their mass- and RT-shift probes do not). An entry whose best donor PSM has a PSM q-value above 0.001 is not transferred (by default extract does not measure its transfers, so they have no rows; withquant extract --mbr-preselect falsethey arebelow_thresholdwithout a q-value; its identified rows are unchanged): a single marginal donor PSM is the commonest false donor. On a public LFQ benchmark (six runs, peptide-level shuffled entrapment, Percolator seeds 42, 1 and 2; 2026-10-23, defaults) the identity-decoy estimate of the false-donor share of accepted transfers was 0.33–0.34 % at transfer q 0.01 where the entrapment gave 0.24–0.33 %, and 0.45–0.47 % against 0.37–0.49 % at 0.05: it tracks the false-donor share but is not an upper bound. With a reviewed Arabidopsis entrapment (2026-10-21, an earlier build) the entrapment gave 0.24–0.30 % and 0.46–0.54 %. On 500 runs (PXD042233, peptide-level shuffled entrapment, one seed, 2026-10-21) the entrapment gave 0.40 % at transfer q 0.05 against an estimate of 0.26 % (5.07 million accepted transfers); at 0.01 both are near zero (3 and 2 of 240,000 transfers). The identity term alone can therefore read low; the transfer q-value it is part of also carries the wrong-peak term, which is more than ten times larger at 0.05. On the earlier whole-protein shuffled entrapment, the identity decoys and the donor gate (#172) cut the transfers of entrapment donors from 728 to 88 at 0.01 (48 before the peak-shape features), with 62.8k precursors quantified in at least 3 runs (67.6k, and 54.9k before the peak-shape features). The validation file and the finalize log count the decoys accepted per family (mass shift, RT shift, identity) and the transfers the wrong-peak decoys alone would accept. The transfer features are the fit evidence, the donor-run count, the library q-value, the neighbour donor rate (the share of the 50 runs most like the transfer's run, by the Jaccard index of the target entries each run identified, that identified the precursor; computed from the donor runs alone, the same way for target and decoy precursors, and never counting the row's own run) and the agreement of the row's peak shape with the precursor's identified fits in the other runs (the median over those runs, leaving out the row's own run so a pseudo-transfer is not compared with its own identification): the log ratio of the fitted width carried onto the consensus RT scale, the difference in tailing (the isotope-0 residual after the apex minus before it, within three widths, over the fitted profile), and the cosine between the per-isotope heights scaled to sum 1. A decoy is compared with its target's donors. Width, tailing and isotope ratios describe the peak, not its size: no quantity measured in another run is a feature. Without a donor fit in another aligned run the three are 0, 0 and −1. The MS2 evidence is not a feature: it shows that the peptide was isolated near the fitted peak, but the decoys cannot imitate the right peptide quantified on the wrong peak, so as a feature it accepted many such transfers. It labels the accepted ones: an accepted transfer whose best MS2 scan (inside its fitted peak) matches at least 4 fragments at an angle of at least 0.6 istransfer_ms2, any other accepted transfertransfer_ms1. The+1keeps a small fold from giving q = 0 to targets that merely outscore a few decoys. It is a quant-quality q-value, not an identification FDR. The identified pairs and their decoys go through the same estimate with their own models (same folds), which gives every identified row a quality q-valuequant_q: how far its MS1 feature stands out from what a decoy probe at the same prior finds. Their features are the fit evidence only (LLR, apex offset, width, isotope-envelope cosine, elution coherence, M−1 ratio, mass error, scan count, isotopes used, amplitude signal-to-noise): no donor count, library q-value or measurement from another run. Separate models keep the transfer q-values unchanged (an identified pair's prior is centred on its own PSM, so its targets look nothing like transfers'). By default (--quant-feature-fdr off)quant_qis an annotation only: at matched coverage, flagging at 0.05 improves accuracy and CV only slightly, and it removes close to a tenth of the least abundant precursors. With a threshold, identified rows above it areidentified_below_threshold. A transfer'squant_qis itstransfer_q. Pseudo-transfers give an acceptance rate and the wrong-peak rate, which decoys cannot see. The acceptance rate is optimistic as a transfer sensitivity: a pseudo-transfer's predicted apex leaves the run's own observation out of the consensus RT, but the run's alignment map was fitted with that observation as one of its anchors. - Reported quantity. The extract fit gives each (precursor, run) an apex amplitude
Aand an elution widthw;A · w · √(2π)is the area under the fitted profile. Fitting the width separately in each run lets one precursor's area jump between runs with the width pick alone, so<stem>.mbr.parquetandquantms.feature.parquetreportA · w_p · √(2π)(standard erroramplitude_se · w_p · √(2π)) with one width per precursor:w_pis the median, over the runs that identify the precursor (aligned runs, fits withA > 0), of the fitted width divided by the run map's local slope at the apex, carried back into each run by that run's slope at the row's apex (the slope is about 1 when the runs share a gradient; the mapping keeps the area right when one run elutes more slowly). Transfers use the samew_p. A precursor without such an identified fit, and every row of an unaligned run, reports its own per-run area.<stem>.extract.parquetkeeps the per-runquantityandquantity_se(A · w · √(2π)) for provenance. - Normalised quantity.
<stem>.mbr.parquetalso reportsquantity_normalized: the reported quantity divided by2^o, whereois the run's log2 offset at the row's apex RT. The offsets are fitted across runs on the identified rows withquant_q ≤ 0.01of precursors seen in at least 3 runs (all runs when there are only 2): starting from 0, each of 5 rounds sets a precursor's level to the mean over its runs oflog2 quantity − o, gives each cellz = log2 quantity − level, and sets a reference cell'soto the medianzof the 50 reference cells of its run just before and the 50 just after it in apex RT order (its ownzleft out). Any other row takes the medianzof the 50 reference cells before and after its apex. The offset carries the run's loading and the changes of its ion signal along the gradient (electrospray drifts of seconds to minutes, which a single factor per run cannot follow). It assumes most precursors do not change between runs. A run with fewer than 20 reference cells, and every run when fewer than two runs have them, reports null.quantityandquantity_sestay the raw measurement, andquantms.feature.parquetcarries the rawquantity(normalisation there is the downstream tool's choice);quantity_sescales by the same factor asquantity_normalized / quantity.
library.parquet records a fingerprint of the library (and of alignment.parquet, checked on
read); each extract table records that fingerprint and its --lfq-tol. quant finalize fails,
naming the file, on an extract made from another library (stale) or at another tolerance than
the other runs. The fingerprint covers the library, not the MS1 tables or the run tables: after
searching a run again, run quant library, every quant extract and quant finalize again.
Memory of the global stages. quant library reads the run tables one run and one record
batch at a time, twice (the precursor keys first, then the PSMs joined to that run's scores),
and keeps per-precursor aggregates only: its memory follows the precursors and their donor runs
plus one run's scores, not the PSMs of every run. With one merged pair of Percolator files the
whole pair, and every SpecId, is held. quant finalize is a map-reduce over the runs: each
run's extract table is read on its own, in record batches cut at precursor boundaries, and the
runs are processed in parallel (--threads). What a row needs from other runs is either a
per-precursor summary (the consensus width, and the sorted donor fits around their median,
which give the shape features' median over the other runs) or a global model, so every
reduction is exact: training rows, held-out scores (merged run by run; the K-decoy q-values are
built from the sorted scores), normalisation cells and validation counts. Each run's output
rows go to a temporary table, and the tables are merged by (precursor, run) into
<base>.mbr.parquet. Temporary tables go next to the output, or to --temp-dir (for example
a local disk); they are removed at the end. Two caps bound the models: each GBDT trains on at
most --max-train-rows rows (default 3,000,000; above it, the rows of a hashed sample of
(precursor, run) pairs, so every precursor still trains in some runs and a target stays with
its decoys; all rows are still scored), and the run normalisation fits at most
--max-norm-cells reference cells (default 20,000,000, about 150 bytes each; above it, a
hashed sample of whole precursors, which coarsens the RT-local offsets). Below the caps the
outputs are byte-identical to an all-in-memory run, whatever the number of threads. Two more
limits only trade memory for passes over the tables: --max-donor-fits (identified rows
summarised per pass, default 12,000,000) and --max-held-scores (held-out scores in memory
per classifier, default 64,000,000; above it they are sorted into temporary tables). --seed
(default 42) sets the folds, the models and the samples. The transfer classifier is bagged:
each fold's score is the mean margin of --transfer-models GBDTs (default 8), each trained on
its own hashed sample of at most --max-train-rows rows with its own seed (--transfer-models
1 is the single model of earlier versions). One model trained on a sample of a few million of
hundreds of millions of rows moves the scores near the threshold more than their evidence
does: on 500 runs, two seeds of one model shared 49 % of the transfers accepted at transfer q
0.01 (82 % at 0.05), and more training rows did not help (4 and 16 times the rows: 64 % at
0.01, 86–87 % at 0.05). With the neighbour donor rate and 8 members they share 79 % (94 % at
0.05), with 16 members 84 % (96 %); the neighbour donor rate alone shares 57 % (86 %) and
accepts 2.9 times the transfers at 0.01 (1.5 times at 0.05), with the pseudo-transfers accepted
at 0.05 rising from 26.5 % to 38.3 % at a wrong-peak rate of 4.3 % (4.0 % before). Each run's held-out transfer scores are kept in a temporary table between the scoring
pass and the output pass, so the members score every row once. Memory follows the library, these
limits and one run's output rows per thread; the term that grows with the data is the pooled
q-value step function of each classifier, 12 bytes per step (74 million steps, 0.9 GB, on 500
Orbitrap runs with 464 million extract rows). On those 500 runs with 16 threads finalize took
18.0 minutes at 11.7 GB peak with 8 transfer classifier members (2026-10-23, transfer q 0.05;
about 3.5 GB of it the library; the members' training samples add about 4.7 GB), against 9.7
minutes at 7.5 GB with one, and 1.9 to 2.7 hours at 10.3 GB for the earlier precursor-shard
version. With --output-parquet, the protein inference over the 30.6 million PSMs at q ≤ 0.01
takes finalize to 25.8 minutes at 45.2 GB.
--mbr without --quant-dir writes the per-run artifacts to a temporary directory (under
--temp-dir, by default next to the output): each run's <stem>.ms1.parquet (about 5–6 bytes per centroid; about 200 MB for an
Orbitrap run with 38 million MS1 centroids) and <stem>.ms2.parquet (at most 100 centroids
per MS2 scan) plus its tables. For large runs pass
--quant-dir on a disk with room. Any search that quantifies also keeps a temporary copy of
each run's MS1 table under --temp-dir until quantification ends. In-process, the stages run
after every search output is written and the search state is freed, one run's MS1 at a time.
--mbr is label-free only and is refused with --tmt. With --mbr --output-parquet,
quantms.feature.parquet holds the match-between-runs rows (identified, transfer_ms2 and transfer_ms1)
and replaces the per-run --lfq feature rows, which stay in <stem>.lfq.parquet;
below-threshold transfers and precursors that fail the library
entry gate are not in it (§3f). With --rescore, the search's protein groups give each feature
its group and quantms.pg.parquet holds the groups at --protein-fdr (0.01 unless given), as
quant finalize --output-parquet does.
The stage path and the in-process run use the same code on the same files. With --rescore
(Percolator) they give identical <stem>.mbr.parquet and <stem>.mbr_validation.parquet rows
for the same Percolator results,
--quant-fdr, --pep and --lfq-tol: pass the search's <pin>.percolator.target.psms.txt and
<pin>.percolator.decoy.psms.txt to quant library. With --rescore-native the scores
(held-out scores and q-values of the native rescorer) are only available in process;
quant library has no input for them.
Known limits (memory). The library stage holds every run's rank-1 PSMs (targets and decoys) with their joined scores at once, so its memory grows with the total PSM count across runs, about 0.5-0.7 KB per rank-1 PSM (around 100 GB for 1,000 long runs). Finalize holds every run's extract rows at once, and each extract loads the whole alignment. Streaming these stages is planned.
Relation to other quantifiers¶
- IonQuant (FragPipe) estimates match-between-runs error using multiple
scores and a semiparametric mixture model. Its shifted decoy search starts at
+11 × 1.0005 Th and tries smaller shifts when no peak can be traced. Andes
--mbruses its own estimator (above): a classifier over template-fit features with mass-shift and RT-shift decoys. - Sage uses aligned RT grids, a normalized spectral-angle score, and +11.06 Th / shifted-RT decoys. Its hybrid score inspired the powers above; Andes uses cosine, a run-wide height scale and independent within-run extraction, so the scores and thresholds are not interchangeable. Sage applies its envelope threshold during peak selection, before estimating precursor q-values.
- OpenMS FeatureFinderIdentification combines extraction with chromatographic coelution/shape and mass-error scores, and supports elution-model fitting. Those checks are useful next steps for Andes; matching integrated isotope areas alone does not establish coelution.
Limits in this release¶
--lfq and --tmt-level 3 need mzML or Thermo .raw (MS1 / MS3 scans; MGF carries MS2 only
and Bruker .d MS1 frames are not read yet) and cannot be combined with --chimeric;
--tmt <plex> at MS2 works with every input and with --chimeric, without precursor purity
there (no purity, so --tmt-min-purity has no effect). Purity is unavailable without MS1 scans or isolation
offsets. Neither module runs in --glyco mode.
11. License and citation¶
andes is licensed under the Apache License 2.0. See LICENSE for the full text and NOTICE for attribution and the project's origin in MS-GF+. The software is provided "as is" without warranty.
Citation¶
If you use andes in published work, please cite both andes and the foundational MS-GF+ paper:
bigbio (2026). andes: a data-driven peptide search engine for the quantms ecosystem. https://github.com/bigbio/andes
Kim, S. and Pevzner, P.A. (2014). MS-GF+ makes progress towards a universal database search tool for proteomics. Nature Communications, 5:5277.
andes originated from MS-GF+ (https://github.com/MSGFPlus/msgfplus); see NOTICE.