Concepts¶
What EcoTyper does¶
EcoTyper works in two modes:
Discovery finds, for each cell type, the transcriptional cell states across samples (NMF on per-cell-type expression, with the number of states chosen by the cophenetic coefficient). It then groups states that co-occur across samples into ecotypes (Jaccard similarity of state assignments + hierarchical clustering). The result is a model.
Recovery takes a model (prebuilt, or your own discovery result) and assigns new bulk samples, single cells or Visium spots to its cell states and ecotypes (NMF with the model’s fixed W).
The prebuilt models are Carcinoma (12 cell types, 10 ecotypes CE1–CE10, discovered across 16 solid tumor types) and Lymphoma (DLBCL, 13 cell types, 9 ecotypes LE1–LE9).
API levels¶
Level |
Functions |
Input |
Output |
|---|---|---|---|
scverse |
|
|
results in |
|
pandas: genes x samples matrix + annotation |
Python result objects with every intermediate table |
|
|
the R wrappers’ input files |
the R output folder layout, file for file |
All three run the same engine and give the same numbers. The *_files functions and the
command line are drop-in replacements for the R scripts; their outputs can be
compared with R’s file by file (ecotyper.validate). The older AnnData wrappers in
ecotyper.workflows (recover_bulk, recover_scrna, discover_scrna) remain for
compatibility; new code should use ecotyper.recovery() / ecotyper.discovery().
Data types¶
ecotyper.infer_data_type() decides from the object:
Data type |
Signal |
Needs |
|---|---|---|
|
|
|
|
|
cell type labels named as in the model; |
|
neither |
nothing else (an annotation in |
|
a |
the same samples in every object |
Ambiguous objects raise an error that says which data_type= to pass: spatial coordinates
together with a cell type column (imaging-based single-cell data), or sparse data with many
observations but no cell type column.
Data orientation¶
pandas level: like R, matrices are genes x samples (rows: gene symbols; columns: sample or cell IDs). Annotations are data frames with an
IDcolumn.AnnData level: samples x genes.
obsholds the annotation, andobs_namesare the IDs. Convert withto_anndata()/from_anndata().
Expression must be on the linear scale (TPM/counts, not log). EcoTyper log-transforms internally.
Result keys¶
With the default key_added="ecotyper":
Slot |
Data types |
Contents |
|---|---|---|
|
all |
assigned state ( |
|
all |
state abundances (observations x states) |
|
bulk |
ecotype ( |
|
scRNA |
ecotype that contains the cell’s state |
|
bulk |
most abundant ecotype and its BH-adjusted p-value |
|
bulk, Visium |
ecotype abundances (observations x ecotypes) |
|
Visium |
fraction-weighted state abundances, columns |
|
Visium |
fraction of the malignant cell type per spot |
|
scRNA |
ecotypes per sample (samples x ecotypes; assignment table) |
|
all |
run parameters, NMF iterations per cell type |
Everything stored is h5ad-serialisable (adata.write_h5ad works after a run).
Models¶
An EcoTyperModel holds, per cell type, the NMF basis W of the cell states and
their marker genes, plus the ecotype definitions. Get one by:
import ecotyper as et
m = et.EcoTyperModel.load("Carcinoma") # built-in (downloaded and cached on first use)
m = et.discovery(adata) # discovered, in memory
m = et.EcoTyperModel.load("models/crc") # saved with m.save("models/crc")
m = et.EcoTyperModel.load("ecotyper/EcoTyper", "MyDiscovery", "Carcinoma_Fractions") # R output folder
m.cell_type_order # ['B.cells', 'PCs', 'CD8.T.cells', ...]
m.n_states # {'B.cells': 9, ...}: NMF rank chosen in discovery
m.ecotypes # ID, CellType, State, Ecotype: states of each ecotype
m.discovery_result # discovered models: every intermediate table
save() writes the R layout <path>/{Analysis,Cell_States,Ecotypes}/... with the files recovery
reads, so saved models also work with the R scripts. Built-in models are taken from a pinned
commit of the R repository; set ECOTYPER_MODELS to a local EcoTyper/ folder to use it
instead of downloading, and ECOTYPER_CACHE to move the cache (default ~/.cache/ecotyper).
Parallelism and reproducibility¶
n_jobs(CLI:-t) runs cell types and NMF ranks in parallel processes, like R’sNumber of threads. Keep BLAS single-threaded (OMP_NUM_THREADS=1) for best throughput.Discovery is deterministic, seeded exactly as in R (
set.seed(1234 + restart)per NMF run).Recovery takes
seed(default 42). R’s recovery scripts do not seedNMFpredict, so R’s own results vary slightly between runs. Started withset.seed(42), R gives the same output as the port.r_exact=True(default) round-trips intermediate values through R’s 15-digit text format, where the R pipeline writes a file and reads it back, so ties and ranks come out the same as in R.