Filter
Filtering in msmu is split into two steps and is implemented in
msmu.pp.add_filter and
msmu.pp.apply_filter.
add_filter()creates a boolean mask and stores it as a named filter column.apply_filter()applies one or more stored masks to subset the modality.
Define filters
add_filter() supports multiple sources via on:
on="var": readcolumnfrom.var, store mask in.varm["filter"]on="obs": readcolumnfrom.obs, store mask in.obsm["filter"]on="varm": readcolumnfrom.varm[key], store mask in.varm["filter"]on="obsm": readcolumnfrom.obsm[key], store mask in.obsm["filter"]
key is required when on is "varm" or "obsm".
The keep argument accepts conditional operators such as eq, ne, lt, le,
gt, ge, contains, and not_contains.
Use name to give a filter a short, explicit identifier:
mdata = mm.pp.add_filter(
mdata, modality="psm", on="varm", key="qc",
column="score", keep="gt", value=0.01, name="qc_score",
)
mdata = mm.pp.apply_filter(mdata, modality="psm", columns=["qc_score"])
Names must be nonblank strings without / so their definitions can be saved in
HDF5. Explicit names are shared across the modality's observation and feature filters.
The source, column, operator, and value are recorded in
adata.uns["filter_conditions"][name]. Reusing a name with the same condition
recomputes its mask; a different condition raises an error. An existing mask
without a recorded definition cannot be overwritten with an explicit name.
When name is omitted, stored filter column names follow this pattern:
{column}_{keep}_{value} for obs and var. For matrix sources, the name
also includes the source and key, for example varm['qc'].score_gt_15.0.
Use that full name when selecting matrix filters with columns.
Missing source values never satisfy a filter, including ne and
not_contains. Missing masks introduced by concatenating datasets remain
unapplied to rows where that filter was not recorded.
# feature-level filter from .var
mdata = mm.pp.add_filter(
mdata,
modality="psm",
on="var",
column="q_value",
keep="lt",
value=0.01,
)
# sample-level filter from .obs
mdata = mm.pp.add_filter(
mdata,
modality="psm",
on="obs",
column="condition",
keep="not_contains",
value="BLANK",
)
Apply filters
apply_filter() controls target axis with on:
on="all"(default): apply both.varm["filter"]and.obsm["filter"]on="var": apply only.varm["filter"]on="obs": apply only.obsm["filter"]columns=[...](optional): apply only selected filter columns by name
When on="all" and one side does not have a stored filter table, that axis is
skipped. A warning is printed if neither side has filters. When on="var" or on="obs" and the requested
filter table is missing, an error is raised. Invalid on values also raise an error.
The function also prints which filter columns are applied. The provenance logger records
the successful call in mdata.uns["_log"], but does not capture printed output.
mdata = mm.pp.apply_filter(mdata, modality="psm", on="all")
# apply only selected filters
mdata = mm.pp.apply_filter(
mdata,
modality="psm",
on="var",
columns=["q_value_lt_0.01"],
)
Missingness
A quantification filter needs the missing counts on .var first.
pp.calculate_missingness records n_missing and
missing_ratio per feature and, given an .obs column, missing_ratio_<level> per group, so the
usual filter calls apply:
mdata = mm.pp.calculate_missingness(mdata, modality="protein", group_obs="condition")
mdata = mm.pp.add_filter(mdata, modality="protein", column="missing_ratio", keep="lt", value=0.5)
mdata = mm.pp.apply_filter(mdata, modality="protein")
Decoys carry no quantification, so they receive n_missing = 0 and missing_ratio = 0.0: a
"missing at most" filter keeps every decoy and the target/decoy counts behind the q-values stay
intact, while a filter in the other direction (ge/gt on missingness) would drop them. The
columns are a snapshot — recompute after removing samples, since apply_filter does not update them.
Contaminants and decoys
Readers record identification status rather than acting on it:
- Decoys are separated out of
.varat read time and kept in.uns["decoy"], where the q-value machinery needs them. Filters you add are applied to them in parallel, through.uns["decoy_filter"], so target and decoy features stay matched. - Contaminants stay in
.varwith acontaminantflag (1/0).msmurecords the flag and leaves the decision to you: a contaminant accession can also be a genuine protein of the sample (the parsed group keeps both, e.g.,Cont_P07339;P07339), so whether such a feature should go is a protein-level judgment rather than something to drop at the PSM level.
Contaminant markers differ per FASTA (contam_, Cont_, CON__, and each engine's decoy tag in
front of them), so the reader normalizes every accession to one canonical form
[rev_][Cont_]<accession> and sets the flag from the parse. If you do filter on contaminant
status, filter on the flag rather than matching a marker on the protein string — a string match
silently stops matching when the search uses a different contaminant FASTA.
Validation and compatibility
Requested filter names must all exist on the selected axis (either axis for
on="all"). Missing names raise an error; filters are not partially applied.
Variable filters must also exist in the decoy mask when decoys are present, so the
column a variable filter uses must exist in .uns["decoy"] as well; add_filter
refuses otherwise. Give decoys the column with a recorded edit rather than by hand —
dt.assign with on="decoy" for a constant, or
dt.map with target="<modality>.decoy" to carry a sample
annotation over by filename — so the step stays in provenance.
Applying a subset of filters preserves the other decoy conditions for later calls.
contains and not_contains retain pandas regular-expression semantics:
A.B also matches AxB. Escape regex metacharacters for literal matching.
Existing stored filter names are not renamed. Newly recorded matrix filters use the source-qualified names above; repeating the same condition replaces its mask. Old workflows that regenerate matrix filters using the former names, relied on missing values passing, or requested nonexistent columns may no longer replay unchanged. Verified replay can report a hash mismatch, and explicit old matrix filter names can raise an error. Re-record those workflows with the current API; do not disable verification to hide a changed result.