bigbio / bigbio/qpx

pg: a (group, quantification unit, label) with no quantity — labelled row with null intensity, or no row?

Open
#280 8 comments 0 reactions 1 assignee Claimed by @ypriverol View on GitHub
Dominant language
Python
Stars
26
Forks
8
Avg merge
5h 13m
Merged PRs (30d)
27

Description

*Updated 2026-08-11. The original body argued from a NaN-propagation consequence; that was wrong, is retracted in the comments, and has been removed along with everything resting on it. What remains is the cardinality question, narrowed to confirm the answer already given below.*

When a protein group has a measured quantity elsewhere but none in one particular `(grouped_runs, label)` cell, must a producer emit a row with that `label` populated and `intensity = null`, or may it omit the row?

(Line numbers on `dev` @ `4227fb4`.)

**The label axis looks already covered**, by `pg.md:346`: "`label` is null **only** for identification-only protein groups that carry no quantity … when a quantity exists, `label` is non-null and there is one row per label." So in a unit where the group is quantified, the row keeps its label and the intensity is null — consistent with @jpfeuffer's comment below. OpenMS will follow that.

What that leaves open is the **unit** axis, which we have to decide at the same time.

**The one thing still to confirm.** Is the intended rule:

1. In a quantification unit where the group **has** identification evidence, emit one row per applicable label; where no quantity was measured for a label, keep the label and write a null `intensity`.
2. In a quantification unit where the group has **no** identification evidence, emit no rows at all.

That is roughly what `openms_consensus` does — it restricts to units where the group was identified (`pg_adapter.py:337-342`) and null-fills across labels within each retained unit (`:343-355`). One caveat we should flag ourselves: `:342` ends `… or list(units)`, so a group with no attributable runs anywhere falls back to *every* unit and does emit the full labelled-null set. Whether that fallback is intended is part of the same question.

**Why it isn't obvious from the text.** `pg.md:81` and `:86` define the grain as one row per label / per `(pg_accessions, grouped_runs, label)`, which reads as a dense grid. `pg.md:48-52` describes a missing feature→pg softlink match as "identified but not quantified in that channel/fraction", which makes absence an expected representable state — though it is written as softlink behaviour, not as a producer rule. Validation has no completeness invariant over the grid, so both shapes pass.

**Producers currently differ.** `openms_consensus/pg_adapter.py:343-355` keeps labelled nulls, deliberately (`39123dc`, `docs/guide/convert.md:851`), and `writers/pg.py:43-47` preserves the shape. `maxquant/pg_adapter.py:457`/`:471`/`:478-479` and `fragpipe/pg_adapter.py:341-342` omit non-positive cells. `spectronaut/pg_adapter.py:208-209` turns a null/zero quantity on an existing aggregated row into `0.0` — that is a value choice on a row that already exists, not a policy for absent combinations.

**Why we're asking.** OpenMS `-out_qpx` currently writes `0.0` as a placeholder for these cells and is replacing it. Since qpx's `openms-consensus` converter writes the same view from the same data, we would rather both follow one rule than diverge. If (1) + (2) is the intent, we will implement it and are happy to contribute the one-sentence clarification to `pg.md` and the schema.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.