Skip to main content

Module feature_rows

Module feature_rows 

Source
Expand description

Canonical feature-row (sparse-matrix row) convention for every modality.

NOT to be confused with the sibling crate::aux::feature_names, which is about a different problem. This module fixes the row-name grammar a producer emits and a consumer splits; feature_names canonicalizes an already-emitted name so the same gene or locus matches across files that spell it differently (FeatureNameKind). Rows here are built and parsed; names there are matched.

It lives in data_beans::aux rather than beside its producers because the grammar has readers on both sides of the BAM/model boundary: faba writes these rows, senna’s embedding and association steps split them back apart.

All per-cell matrices name their rows

{unit}/{modality}/{channel}              unit-level (no subunit)
{unit}/{modality}/{subunit}/{channel}    sub-unit (component or site)
  • unit — the modelling unit. For every gene-resolution modality this is the gene, {gene_id}_{gene_name} (gene_count::splice::format_gene_key). BAF is the exception: a variant is a coordinate, not a gene. It does not belong to one, and two overlapping genes would otherwise give the same variant two row names, so its unit is the {chr}:{pos} locus.
  • modality — the lowercase subcommand name: COUNT / M6A / ATOI / APA, or BAF.
  • subunit — optional sub-gene id: a single-base {chr}:{pos} site (m6A and A-to-I sites are one base pair) or an EM mixture {component} index. Omitted for gene-level pooled rows. It sits above the channel: a component/site is a position cluster fit once per (gene, modality) and shared by both channels, so the channel nests inside it.
  • channel — the innermost (last) field: the two read-states that modality contrasts (gene counts split SPLICED/UNSPLICED; m6A METHYLATED/UNMETHYLATED; ATOI EDITED/UNEDITED; APA PROXIMAL/DISTAL; BAF ALT/DEPTH). Omitted by the one producer whose contrast lives across the units rather than within the row — see unit_row.

Putting the channel last means a unit’s two channel rows share a contiguous prefix (the unit), and “strip the trailing field” recovers the unit.

Every channelized modality keeps both states in ONE matrix rather than in a pair of same-shaped files, so a ratio is a division within one unit’s rows and no consumer has to open two files and trust their row orders agree.

Most channel pairs PARTITION the coverage — the two states are exclusive and sum to the total. BAF is the exception: ALT is nested inside DEPTH (alt ≤ depth), so BAF is alt / depth and NOT alt / (alt + depth). Any consumer that sums a unit’s channels to recover coverage is wrong on this modality alone.

This module is the single source of truth. Consumers split rows with parse_feature_row; producers build them with feature_row / unit_row rather than hand-spelling the tokens. The gene-count, APA, SNP and quant producers all go through it; the editing / mixture / pileup producers still emit their rows inline and are the remaining migration.

One consumer still parses by hand: faba::quant::extract_gene_key strips a trailing /count/{channel} with rfind. That is safe there — it runs only over faba’s own gene matrices, and its job is to group every row of a gene (including the pooled total track) under one key for QC, which is what its callers want. Contrast senna::gem::rows, which must additionally decide WHICH track a row is: there the same shortcut put total in the spliced bucket and double-counted the gene, so that one goes through parse_feature_row.

The unit of a parsed row is FeatureRow::unit, and the gene is FeatureRow::gene — read those fields rather than re-splitting the string. unit.split('/').next() USED to recover the gene and no longer does: a unit may itself contain /, because gene symbols do (see parse_feature_row), so that recipe truncates such a gene at its first slash.

Structs§

CountRowMap
A count matrix’s feature axis interned onto a dense gene axis.
FeatureRow
A feature row split into its fields, borrowing from the source string.

Enums§

UnparsedRowPolicy
What intern_count_rows does with a row that is not {gene}/count/{spliced|unspliced}.

Constants§

ALT
BAF numerator: reads carrying the called alt allele.
APA
ATOI
BAF
Per-cell allele frequency at a called variant locus. Named for what the matrix measures (B-allele frequency), not for the calling step that chose the positions: the call set — genotype, GQ, rsid — is snp_sites.parquet / snp_sites.vcf.gz, and a row here carries none of it, only two read counts.
COUNT
DEPTH
BAF denominator: ALL reads over the locus, alt included. The only channel pair that nests rather than partitions — see the module docs.
DISTAL
EDITED
M6A
METHYLATED
NO_GENE
CountRowMap::row_to_gene entry for a row that was left off the gene axis. Only ever produced under UnparsedRowPolicy::Reject.
PROXIMAL
APA channels come from the 2-site PDUI decomposition (proximal vs distal poly-A in the 3’UTR). The K-component poly-A mixture is a separate count matrix, NOT channelized — it does not follow this convention.
SPLICED
TOTAL
Gene-count total (spliced + unspliced) — used by the pooled gene-QC track.
UNEDITED
UNMETHYLATED
UNSPLICED

Functions§

feature_row
Format a feature row. Pass subunit = None for a gene-level (pooled) row {gene}/{modality}/{channel}, or Some(site_or_component) for a sub-gene row {gene}/{modality}/{subunit}/{channel} (channel innermost). The subunit must not contain / (sites use the single-base chr:pos, components are integers), so the row round-trips through parse_feature_row.
intern_count_rows
Intern a count matrix’s feature axis onto a dense gene axis, pairing a gene’s two channel rows under one id.
parse_feature_row
Split a feature row into its fields. The channel is the innermost (last) field, so a 3-field row is gene-level ({gene}/{modality}/{channel}) and a 4-field row carries a subunit before the channel ({gene}/{modality}/{subunit}/{channel}).
split_count_row
Split a gene-level count row {gene}/count/{spliced|unspliced} into its gene key and whether it is the nascent (unspliced) track. None when the row is not a gene-level count row at all.
unit_row
Format a channel-less UNIT row {gene}/{modality}/{subunit}.