Skip to content

Deduplicate the calibration reference by peptidoform (v4.2.0) - #116

Merged
RobbinBouwmeester merged 4 commits into
mainfrom
feat/deduplicate-reference
Aug 28, 2026
Merged

Deduplicate the calibration reference by peptidoform (v4.2.0)#116
RobbinBouwmeester merged 4 commits into
mainfrom
feat/deduplicate-reference

Conversation

@RobbinBouwmeester

Copy link
Copy Markdown
Member

Problem

A user ran MS²Rescore on a PSM list where the same peptidoform appears in many spectra, each with its own observed retention time (matrix_fix_deeplc/psm_list.tsv: 201,593 PSMs, one run, 177,905 peptidoforms). The reference that auto-calibration selects from it holds 6,331 PSMs but only 2,623 peptidoforms, and the repeats of a peptidoform disagree on the observed RT by up to 236 min (median 0.89 min). The calibration therefore got several conflicting targets per prediction, and each peptidoform was weighed by how often it happened to be identified (up to 261 times).

Change

calibrate, finetune, predict_and_calibrate and finetune_and_predict now keep one PSM per peptidoform, the first observation, before fitting. New deeplc._reference_selection.deduplicate_psms(psm_list, ignore_charge=True).

  • Charge states count as repeats: retention time does not depend on precursor charge.
  • Opt out with deduplicate_reference=False, or --keep-duplicate-reference-psms on the CLI.
  • When repeats disagree by more than a quarter of the observed RT range, DeepLC logs a warning: that means the reference mixes runs or contains low-confidence PSMs, which deduplication hides rather than fixes.
  • Prediction is untouched — one prediction per input PSM, same order.
  • No changes to MS²Rescore; it picks this up through the new default.

Effect on the reported case

Both fits evaluated on the 2,623 first observations (the only self-consistent target):

MAE median 90th pct within 5 min
deduplicated (new default) 4.51 min 2.09 min 8.57 min 81.4 %
all reference PSMs (old) 9.69 min 5.98 min 21.85 min 44.9 %

Verification

  • pytest tests: 126 passed, including 9 new tests in tests/test_deduplication.py (first observation kept, order preserved, idempotent, charge handling, modified peptidoforms distinct, warning on conflicting RTs, missing RTs, calibration behaviour with the flag on and off, parameter forwarded end to end).
  • ruff check / ruff format --check clean on the changed files.
  • Version bumped to 4.2.0 (default behaviour changes), CHANGELOG and usage docs updated.

🤖 Generated with Claude Code

Calibration and fine-tuning now fit on one PSM per peptidoform, the first
observation in the reference. A reference built from a search result repeats a
peptidoform once per spectrum it was identified in, each time with a different
observed retention time, so the fit received conflicting targets and weighed
peptidoforms by how often they happened to be identified.

On the reported MS2Rescore case (201,593 PSMs, single run) the reference chosen
by auto-calibration held 6,331 PSMs but only 2,623 peptidoforms, and the repeats
disagreed on the observed retention time by up to 236 minutes. Measured on those
2,623 peptidoforms, fitting on the first observations improves the calibration
from 9.69 to 4.51 minutes mean absolute error (median 5.98 to 2.09; within five
minutes 44.9 % to 81.4 %).

Charge states of one peptidoform count as repeats, because retention time does
not depend on precursor charge. When repeats disagree by a large fraction of the
observed range DeepLC now warns: the reference then mixes runs or contains
low-confidence PSMs, which deduplication hides rather than fixes.

`deduplicate_reference=False` on calibrate/finetune/predict_and_calibrate/
finetune_and_predict, or `--keep-duplicate-reference-psms` on the command line,
restores the previous behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@RalfG RalfG left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Seems logical, but one question I had: Can this be set as default behavior without the need for keyword arguments and CLI options? That would keep the API relatively clean and removes the need for pipelining the option through multiple modules/layers of code.

If the dedup option would only need to be disabled in edge cases, we can rely on those users just using the reference selection API directly.

The reported case stores retention times in seconds (the psm_utils convention):
identifications run from 243 to 597 s of a roughly ten-minute acquisition, so the
worst disagreement between repeats of a peptidoform is 236 s, two thirds of the
354 s range, and the calibration improves from 9.69 to 4.51 s mean absolute
error. DeepLC calibrates in whatever unit the input uses and converts nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread deeplc/_reference_selection.py Outdated
Comment thread deeplc/_reference_selection.py
RobbinBouwmeester and others added 2 commits August 28, 2026 14:07
Review feedback on #116: keep the public signatures as they were rather than
threading a flag through four functions and the command line.

Deduplication now happens inside calibrate() and finetune() with no way to turn
it off. It has to live there rather than in the reference-selection layer,
because a caller that selects its own reference (MS2Rescore builds one from
targets with q <= 0.01, or the top N by score) never passes through
select_reference_psms, and that caller is exactly the reported case.

The rare caller who wants every reference PSM to count still has a path that
needs no parameter: fit a Calibration on its own targets and pass it to
predict_and_calibrate, which uses an already fitted calibration as given. train()
remains available for full control over a training set. Both are covered by
tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review feedback on #116. modified_sequence is the ProForma string without the
charge state, so psm_utils decides what identifies a peptidoform instead of this
module cutting the charge off the string. It keeps terminal, global and labile
modifications apart, and it cannot be tripped up by a modification label that
contains a slash, which the previous rsplit would have truncated. Charge adducts
(/2 against /2[+2H]) also collapse correctly now.

modified_sequence exists since psm_utils 1.1.0 and DeepLC already requires
>= 1.5, so no dependency change is needed. Two tests added: seven peptidoforms
that differ only in terminal, global or labile modifications stay distinct, and a
charge adduct does not create a second peptidoform.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@RobbinBouwmeester
RobbinBouwmeester merged commit 69c0b5c into main Aug 28, 2026
5 checks passed
@RobbinBouwmeester
RobbinBouwmeester deleted the feat/deduplicate-reference branch August 28, 2026 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants