Edit on GitHub

starplast.slots

Biological-question slots and the policies that combine datasets which answer them.

The generated catalog is package data: the application can use it offline, while scripts/generate_slot_table.py remains the editable source. A slot is not a filename pattern; it is a question plus a condition, its candidate measurements, and an explicit combination policy.

POLICIES = ('one', 'average', 'fill', 'separate')
UNITS = ('gene', 'host_gene', 'pair', 'ortholog_group', 'metabolite')
RESOLVABLE_UNIT = 'gene'
UNIT_TABLES = {'gene': 'nodes.parquet', 'metabolite': 'metabolites.parquet'}
HOST_TABLES = {'Hs': 'hs_host_proteins.parquet', 'Mm': 'mm_host_proteins.parquet'}
BRIDGE_TABLES = {'host': 'host_bridges.parquet'}
SPECIES_BRIDGE_TABLES = {'Tg': {'host': 'host_bridges.parquet'}, 'Pf': {'host': 'pf_host_bridges.parquet'}}
SPECIES_TABLES = {'Tg': 'nodes.parquet', 'Pf': 'pf_nodes.parquet'}
SPECIES_PREFIXES = {'Tg': ('TGME49_', 'TGGT1_'), 'Pf': ('PF3D7_',)}
def table_organism(nodes: pandas.DataFrame) -> str | None:

Which species' table this is, read off its accessions. None if it cannot be told.

Deliberately derived rather than declared. A flag passed alongside the frame is a second thing that has to be kept true; the accessions are the thing itself.

def bridge_names(slot: Slot) -> tuple:

The bridges a slot declares, if any.

CATALOG = '/home/runner/work/starplast/starplast/starplast/data/slots.json'
@dataclass(frozen=True)
class Slot:

One biological question and the measurements that can answer it.

Slot( organism: str, name: str, axis: str, context: str, unit: str, patterns: tuple, policy: str, candidates: tuple = (), evidence_path: tuple = (), biology_path: tuple = (), context_path: tuple = (), target_family: str = '', target_columns: tuple = (), role: str = 'feature')
organism: str
name: str
axis: str
context: str
unit: str
patterns: tuple
policy: str
candidates: tuple = ()
evidence_path: tuple = ()
biology_path: tuple = ()
context_path: tuple = ()
target_family: str = ''
target_columns: tuple = ()
role: str = 'feature'
key: str

The identifier stored in embedding recipes.

@dataclass
class SlotResult:

Resolved values plus per-gene provenance for one slot.

SlotResult( values: pandas.DataFrame, source: pandas.Series, source_columns: tuple)
values: pandas.DataFrame
source: pandas.Series
source_columns: tuple
def all_slots(organism: str | None = None) -> tuple:

The generated catalog, optionally restricted to Toxo or Pf.

def by_key(key: str) -> Slot | None:

Resolve a recipe key to its slot.

def source_columns(nodes: pandas.DataFrame, slot: Slot) -> tuple:

Raw numeric node columns matching a slot, in candidate order without duplicates.

EDGE_PREFIX = 'edge:'
def edge_types(slot: Slot) -> tuple:

The graph edge types a slot is filled by, if any.

def is_filled( slot: Slot, nodes: pandas.DataFrame = None, graph=None, tables: dict | None = None) -> bool:

Does this slot have data behind it -- in the node table, OR in the graph?

Both, and that is the whole reason this is a function rather than a line of whatever script happens to be asking. A slot measured per PAIR -- co-expression, co-fitness, shared compartment, crosslink MS -- is filled by an edge type in graph.npz and has no node column at all. Asking declared_columns alone reports every one of them as empty, which is exactly what happened: eight Toxoplasma slots were counted as unfilled and chased with downloads while their data was already in the graph, and the coverage figure went out twice before anyone noticed.

graph is anything with a files list, which is what numpy.load gives back for an npz.

A slot measured per METABOLITE is filled by a column of the metabolite table, which is a third place to look; tables maps a unit to its frame. Without it a metabolite slot reads as empty, which was true until that table existed and is a lie afterwards -- the same failure the pair slots had.

def unit_table( slot: Slot, tables: dict | None = None) -> pandas.DataFrame | None:

The one side table answering a slot, refusing to combine species for a host slot.

def coverage( organism: str = 'Tg', nodes: pandas.DataFrame = None, graph=None, tables: dict | None = None) -> dict:

How many of an arm's feature slots have data, and which do not.

The single place this question is answered, so a viewer, a report and a census cannot disagree about it -- which they did, before this existed.

def declared_columns( nodes: pandas.DataFrame, slot: Slot, numeric_only: bool = False) -> tuple:

Every column declared by a slot, including categorical targets and metadata.

source_columns is intentionally numeric because it builds matrices. Leakage closure needs the wider declaration: labels, confidence fields and numeric features all travel together.

def same_species(nodes: pandas.DataFrame, slot: Slot) -> bool:

Whether this table is the one this slot's organism is measured in.

Unknown counts as matching: a synthetic frame in a test carries no accessions, and refusing those would make the guard a nuisance rather than a protection. What it must catch is the definite mismatch -- a Pf_ slot against a table of TGME49_ rows.

HIERARCHIES = ('evidence', 'biology', 'context')
def hierarchy_path(slot: Slot, hierarchy: str) -> tuple:

Return one facet's path from broad group to the slot leaf.

def relationship_tree(organism: str = 'Tg', hierarchy: str = 'evidence') -> dict:

A nested, JSON-safe tree whose leaves are slot keys.

There are three trees rather than a single misleading tree. A slot is a leaf in every facet; callers can therefore omit an assay class, a biological subject, or an experimental context without pretending those are the same grouping.

def slots_in_group( path: tuple | list | str, organism: str = 'Tg', hierarchy: str = 'evidence') -> tuple:

Slots below a hierarchy path, useful for selecting or omitting a whole class.

def target_slots(target: str, organism: str = 'Tg') -> tuple:

Slots that explicitly declare target as one of their outcomes.

def family_slots(family: str, organism: str = 'Tg') -> tuple:

All slots estimating the same target quantity, across assays or transfers.

def resolve( nodes: pandas.DataFrame, slot: Slot | str, chosen: str | None = None, unit: str = 'gene') -> SlotResult:

Apply a slot's one, average, fill or separate policy.

source is one value per gene. For fill it is the exact candidate supplying that gene; for policies that deliberately combine measurements it is a +-joined record of every contributing candidate. This is provenance, not a feature, and is never placed in the matrix.