mantispy.tl.perturbation_score#
- mantispy.tl.perturbation_score(adata, groupby='Metadata_Gene', reference='negcon', use_rep=None, layer=None, replicate='Metadata_sgRNA', by=None, n_features=200, n_folds=5, min_cells=50, seed=0, key_added='perturbation_score', copy=False)[source]#
Score each cell for how clearly it shows its group’s phenotype, by a classifier against the reference cells.
In an optical pooled screen many cells carry a guide but show no phenotype, because the knockout did not take or the cell escaped it, and averaging them with the responders dilutes a real hit. This is the perturbation score of Brieflow [Di Bernardo and others, 2026]: for each group, its cells and an equal-sized random sample of the reference cells are told apart by a logistic regression on the
n_featuresfeatures with the largest ANOVA F statistic. Each cell’s score is its out-of-fold probability of belonging to the group, from a model that never saw it, and the group’s ROC AUC over those probabilities says how separable the group is at all. Feature selection is refitted inside every fold, so the held-out cells never inform which features are used.The features should already be centred and scaled on the reference cells within each batch, which
normalize()does once for the whole screen withmethod="standardize",reference="negcon"andbythe plate and well. Brieflow scales each group’s sample on its own sampled controls instead, which fits the scale to the very controls the model then sees, so its AUCs run slightly higher.To drop the non-responders, subset on the score, for example
adata[~(adata.obs["perturbation_score"] < 0.5)], which keeps the reference cells and the unscored groups, whose score is missing.- Parameters:
- adata
AnnData Cells by features, with the group label and the reference class in
obs.- groupby
str(default:'Metadata_Gene') obscolumn holding the group each cell is scored as, usually the gene.- reference
str(default:'negcon') The cells every group is told apart from:
"negcon"for the rows whoseMetadata_Control_Typeis"negcon", or the name of a booleanobscolumn.- use_rep
str|None(default:None) Read
obsm[use_rep]instead ofX; cannot be combined withlayer.- layer
str|None(default:None) Read this layer instead of
X.- replicate
str|Sequence[str] |None(default:'Metadata_sgRNA') obscolumn, or columns, whose groups are kept whole within a cross-validation fold, the guide by default ("Metadata_sgRNA", ascomposition_shift()also defaults to). The model is then always tested on guides it was not trained on, so a phenotype one guide carries alone, such as an off-target effect, does not raise the group’s score. A named column thatobslacks raises, naming the argument. Replicates are ordered by their labels as strings, so a string column and a categorical one draw the same folds.Nonedraws folds over cells, as Brieflow does, and warns on an object stamped object resolution, since non-targeting guides grouped into pseudo-genes can then score above one half.- by
str|None(default:None) obscolumn whose levels are scored apart, for example"Metadata_Class"to score interphase and mitotic cells separately, as Brieflow does. Each group is scored within each level against a reference sample drawn from that level, so a cell’s score and its group’s AUC come from its own level; a group too small within a level is left unscored there.Nonescores every cell in one pass.- n_features
int(default:200) How many features, ranked by the ANOVA F statistic within each fold, the model reads.
- n_folds
int(default:5) Cross-validation folds, capped by the cells, or with
replicatethe replicates, on the smaller side.- min_cells
int(default:50) Groups with fewer cells than this are left unscored. Brieflow’s minimum of 100 counts the group and its equal-sized reference sample together.
- seed
int(default:0) Seeds the reference sample and the fold split, together with the group’s name, so a group’s score does not depend on which other groups are present.
- key_added
str(default:'perturbation_score') obscolumn for the per-cell score and theuns["mantispy"]key for the per-group table.- copy
bool(default:False) Return a modified copy instead of mutating in place.
- adata
- Return type:
- Returns:
None, or the modified copy. Writesobs[key_added], each cell’s out-of-fold probability of its group, missing for the reference cells and for unscored groups. Writesuns["mantispy"][key_added], one row per group withgroup,n_cells,n_reference(the size of its reference sample),n_foldsandauc, which is missing for an unscored group; withby, one row per group and level and a column named forbyholding the level.- Raises:
ValueError –
n_featuresis not a positive integer,n_foldsis not an integer of at least two,referenceselects no cells, or a scored cell or a reference cell has a missing feature value.KeyError –
groupby,replicateorbynames a column thatobslacks.
Notes
The reference cells set the null each group is tested against and are not scored themselves. Cells of a reference class score as a group only when they are not in
reference, for example intergenic guides scored against non-targeting ones, which is a check of the calibration; intergenic guides cut the genome, so they can carry a mild phenotype of their own.Cells in one well share its plate position, seeding and focus, but in a pooled screen every well holds every group, so the well does not separate a group from the reference and needs no grouping here. The guide does: random folds let the model learn what a guide’s own cells share, and pass that off as the gene’s phenotype.
The cost grows with the number of cells, not with the number of groups, since each group’s fit sees its own cells and as many reference cells. Every fold of every group is fitted in parallel on numba’s threads, which
numba.set_num_threadscaps. The fit is a port of the liblinear solverLogisticRegression(solver="liblinear")runs, so the scores match scikit-learn’s as closely as liblinear matches itself when its input moves by rounding. A backed object is read a batch of groups at a time, each group’s rows and its reference sample, so it is never loaded whole.