Python API
Install the application, analysis modules, and bundled data with pip install starplast.
The analysis modules can be used without creating a Qt application.
The generated module reference includes signatures, docstrings, and source links. This page covers common entry points. Starplast is pre-1.0, so pin the package version when preserving a reproducible analysis.
Read the bundled gene table
import pandas as pd
from starplast import paths
nodes = pd.read_parquet(paths.cache_file("nodes.parquet"))
print(nodes[["gene_id", "product"]].head())
Use pf_nodes.parquet for P. falciparum. paths.cache_file() honours
STARPLAST_CACHE. This example imports no GUI modules and does not download data.
Keep the table row order when aligning coordinates or cluster labels with genes.
Build a feature matrix and embedding
from starplast.embedding import EmbeddingSpec, build_matrix, embed
spec = EmbeddingSpec(
name="fitness-and-expression",
blocks=("fitness_screens", "expression_summary"),
na_policy="median",
scaling="robust",
method="pca",
n_components=3,
random_state=42,
)
X, feature_names, kept_rows = build_matrix(nodes, spec)
coords, feature_names, kept_rows = embed(nodes, spec)
mapped_genes = nodes.loc[kept_rows, "gene_id"].to_numpy()
assert len(mapped_genes) == len(coords)
recipe = spec.to_dict()
kept_rows is a boolean mask into the input table, including when a missing-value
policy drops genes. coords has one row per retained gene. The number of PCA
components is limited by the available matrix dimensions. Use method="umap" or
method="tsne" for nonlinear projections. embed(..., return_matrix=True) appends
the exact feature matrix as a fourth return value.
Use embed(..., strict=True) to refuse a failed backend or algorithm. In ordinary
display mode a failed UMAP call can return PCA; the log and saved execution record
identify that fallback. return_metadata=True appends a dictionary containing the
requested and executed methods, backend, recipe, ordered gene IDs, matrix and
coordinate hashes, and library versions. Coordinates also carry this record into
tuning.EmbeddingStore.save(), which checks gene order and coordinate integrity.
A seed alone does not ensure identical coordinates across software or hardware.
embedding.default_spec(nodes) selects the balanced display recipe used by the
packaged map builder. The shipped archives record that execution and the exact
node-file hash. Rebuild just their layouts with python scripts/rebuild_layouts.py;
this preserves the existing edge arrays. The display is exploratory: it is not a
held-out prediction of any trait used to construct it.
Cluster and examine held-out evidence
from starplast.clustering import cluster, battery
labels = cluster(coords, algorithm="hdbscan", min_cluster_size=25)
subset = nodes.loc[kept_rows].reset_index(drop=True)
summary, detail = battery(subset, labels, used_features=feature_names)
Noise has label -1. summary reports feature-level associations; detail
contains category or cluster-level results. Supplying used_features lets the
battery distinguish input features from held-out evidence. Statistical association
with a cluster does not establish a mechanism.
For label recovery, use search.excluded_detail() to inspect excluded inputs and
search.search() to run a search with target exclusions. Use
validate.masked_recovery() or validate.validate_all() to evaluate categories
hidden from cluster selection. Pass used_columns and target_column so the
circularity checks can run. holdout_cv.nested_structure_cv() selects among
precomputed clusterings using inner validation labels and scores untouched outer
folds. A search winner still needs independent confirmation.
Score labels on the pregenerated maps
from starplast import umap_gallery as G, strategies as S
g = G.shipped()
g.maps("Tg") # recipes, sizes and cluster counts
m = g.load("Tg", "family_fitness") # rows, xyz (float32), clusters
scores = g.scores("Tg") # the label x map table
print(G.describe(scores, "compartment"))
# The same scores for your own clustered map:
ctx = S.shipped("Tg")
table = G.score_map(ctx, rows, clusters, columns=feature_names)
G.per_category(ctx.truth("compartment").iloc[rows], clusters)
rows are positions in the organism's gene table, and clusters holds one
label per placed gene, with -1 for noise. label_scores returns the
size-weighted category F1, precision and recall. It also returns the best
single category, scored on the F1 of the Wilson 95% lower bounds of its
precision and recall, and chosen by skill over its own shuffled-label chance.
Both headline scores come with their chance level and skill. Pass columns to
flag maps built from a column in the label's leakage closure.
Import a screen table
from starplast.importer import read_any, preprocess, merge_into
screen = read_any("screen.csv")
imported, provenance = preprocess(
screen,
gene_column="gene_id",
columns=["score"],
quantification="none",
scaling="none",
na_policy="median",
duplicates="mean",
prefix="spacr_",
)
nodes_with_screen = merge_into(nodes, imported)
The current importer recognizes Toxoplasma accessions (TGME49_, TGGT1_,
and TGVEG_). Plasmodium table import is not yet supported. The input must
contain the chosen identifier and measurement columns. Check the
returned provenance and matched genes before interpreting results. Choose a
missing-value policy deliberately; median estimates absent values. To include
an imported column in an embedding, add it to EmbeddingSpec.extra_columns.
Run discovery without a window
starplast-discover --task guilt:compartment_best --budget 10 --name first-pass
starplast-discover --list
starplast-discover --read first-pass
Use --nodes /path/to/pf_nodes.parquet for a different node table and choose a
layer present in that table. --out sets the results directory. Searches can be
long-running; --help lists budget, seed, and feature-selection options.
Evaluate a trait with held-out families
from starplast.prediction import TaskSpec, run
result = run(nodes, TaskSpec("compartment", method="linear", group_column="orthogroup"))
print(result.metrics)
print(result.per_class)
result.save("results/localization")
Use kind="regression" for continuous measured outcomes, or run_multilabel()
for separate observed binary columns. features and exclude declare inputs;
registered target-derived measurements are still excluded. Calibration and
preprocessing are fitted within training groups. Read the workflow guide
for uncertainty, abstention and evaluation limits.
from starplast.evidence import Observation, write_observations
records = [Observation(
entity_id="example_gene", trait="example_measurement", value=0.0,
source_id="study_accession", organism="example_organism",
source_version="v1", source_location="Table 2, row 3",
unit="relative abundance", context={"condition": "reference"},
replicate="1", evidence_status="measured",
)]
write_observations(records, "results/observations.parquet")
Each record preserves its source and missingness before aggregation. Numerical zero, measured False and unassayed None have distinct meanings.
Run, test and trust a strategy
starplast.strategies is the Strategies tab without the window: 39 named ways of turning the
tables into a claim, each with a self-test and a scorecard. Nothing here imports Qt, and the main
modules are reachable from the package itself -- import starplast, then
starplast.strategies, starplast.scorecard, starplast.techniques -- loaded on first use.
from starplast import strategies as S
table = S.overview("Tg") # one row per strategy: name (with its method), task, grade, skill
print(table[["number", "name", "task", "grade", "skill_default"]].head(10))
s = S.get("feature_knn") # one strategy: its question, explanation and settings
print(s.name, "|", s.method, "|", s.task)
print(s.parameters()) # every setting, its default and why it exists
print(s.techniques_table()) # what the method is built from, each technique explained
print(s.scorecard_table()) # the metrics its self-test reports, each explained
Run it, or test it. A run answers the strategy's question on the whole table. A test hides known information, asks for it back, and judges the answer against the same procedure on shuffled data:
result = S.run("feature_knn", "Tg", target="compartment")
print(result.summary)
calls = result.tables["calls"] # every output is a DataFrame; result.save(folder) writes them
test = S.test("feature_knn", "Tg", target="compartment")
print(test.summary()) # the verdict and the number it rests on
print(test.card()) # verdict block, then the task's metrics in their standard order
print(test.scorecard["macro_f1"], test.scorecard["mcc"], test.skill)
Every self-test returns the same verdict block (observed, chance, bar, p, skill, hidden) and then
its task's metrics, always in the same order: label calls (accuracy, coverage, precision of
calls, macro precision, macro recall, macro F1, weighted F1, Cohen's kappa, MCC, macro AUROC,
macro AUPRC), ranking (AUROC, AUPRC, AUPRC lift, prevalence, partial AUROC, R-precision,
precision and enrichment at the top 1%, recall at the top 10%, best F1, nDCG), set retrieval,
cluster recovery (weighted F1, ARI, NMI, homogeneity, completeness ...), values
(Spearman, Pearson, Kendall, R-squared, normalised RMSE, MAE, decile recall) and
replication. S.metrics() defines each one -- its range, its chance level and how to read
it -- and S.techniques() explains every technique. The scorecards page has
the same glossaries and every strategy's measured card.
How far to trust a strategy before running it comes from its calibration: its self-test run over a grid of settings, several held-out labels and five seeds.
cal = S.calibration("feature_knn", "Tg")
print(cal["grade"], cal["default"]["skill"], cal["default"]["skill_low"], cal["default"]["skill_high"])
print(cal["default"].get("scorecard", {}).get("macro_f1")) # {mean, low, high, runs}
best = S.tuned("feature_knn", "Tg") # the setting calibration chose
result = S.run("feature_knn", "Tg", target="compartment", **best)
To run several strategies on one table, or on your own table, keep a context. It caches maps and matrices, so a second strategy walking the same grid pays nothing:
ctx = S.Context.shipped("Tg").bound(log=print) # log= shows progress; should_stop= can cancel
for key in ("feature_knn", "supervised_classifier", "stacking"):
t = S.get(key).test(ctx, target="compartment")
print(key, t.verdict, round(t.scorecard["macro_f1"], 3))
mine = S.Context(my_nodes, organism="Tg") # any gene x column table with a gene_id column
The scorecard functions work on predictions from anywhere, so a method outside Starplast can be scored exactly as the strategies are:
from starplast import scorecard as SC
SC.label_calls(predicted_labels, true_labels, positions) # NaN = no call, counted as wrong
SC.ranking(scores, is_positive) # higher score = more likely positive
SC.values(predicted, measured)
Gene-gene links and their provenance
star_edges turns measured layers and strategy results into one kind of thing, a link
between two genes, and keeps with every link which layer, strategy and run stated it.
It is what the star map draws, and it needs no window:
from starplast import star_edges as E
from starplast import strategies as S
ctx = S.shipped("Tg")
idx = E.EdgeIndex(ctx.n)
idx.add(E.measured_edges(ctx.graph)) # the graph's layers, kind "measured"
idx.add(E.shipped_edges("Tg", ctx.gene_ids)) # the shipped strategy runs
result = S.run("link_prediction", ctx=ctx, layer="xlms")
idx.add(E.edges_from_result(result, ctx, run="mine")) # any run of your own
links = idx.touching(ctx.index["TGME49_208830"]) # one gene's links, with `other`
nodes, edges = E.star(idx, ctx.index["TGME49_208830"], depth=2, per_group=6)
Each link has kind, group, source, how (layer, pair, module, seed, label or
partner), setting, score, strength (percentile within its run), run, origin
and note. The bounds are module constants (MODULE_K, SEED_K, LABEL_K,
PARTNER_MAX, MAX_EDGES_PER_RUN). UserEdgeStore keeps the application's runs on
disk. scripts/build_star_edges.py rebuilds data/star_edges.parquet.
Module map
| Task | Modules |
|---|---|
| Resolve data and user state | paths |
| Inspect datasets and biological evidence groups | datasets, slots |
| Import gene-level measurements | importer |
| Select features and embed | embedding |
| Cluster and score associations | clustering |
| Pregenerated maps and label x map scores | umap_gallery, umapgallerypanel |
| Search and assess held-out recovery | search, validate, holdout_cv |
| Rank candidate findings | discovery, optimize |
| Store analyses and recipes | runs, searches, recipes |
| Embed the desktop interface | app, analysis_panel |
| Guided workflows | workflows |
| Group-aware classification, regression and multi-label evaluation | prediction |
| Weighted transductive networks | network_prediction |
| Typed observations and reviewed assertions | evidence |
| Explain results and compare measurements | prioritization |
| Named inference strategies, their self-tests and calibration | strategies, calibration |
| Standard metrics for every task, and their glossary | scorecard |
| What each strategy's method is built from | techniques |
| One neighbour space over every layer; learned edge strengths | graphspace |
| Gene-gene links with provenance (the star map) | star_edges, star_map |
| Deposited datasets, derived into columns | deposits |
app.Window(species=...) requires an existing PyQt6.QtWidgets.QApplication.
Call it on the GUI thread. app.main() owns the application event loop and is
intended as the console entry point. Scripts that only need tables and analyses
should import the relevant modules directly.