mantispy.pp.outliers#

mantispy.pp.outliers(adata, method='ecod', contamination=0.01, score_cutoff=None, key='selected', by=None, seed=0, ecod_aggregation='pyod', n_fit=None, key_added='qc_outlier', copy=False)[source]#

Flag outlying cells.

Every method produces a score where higher means more outlying, and the same thresholding applies to all of them, so contamination is the flagged fraction whichever method is used.

Parameters:
adata AnnData

Object to flag.

method str (default: 'ecod')

"ecod" [Li et al., 2023] needs no tuning and is interpretable per feature, "isolation_forest" catches outliers defined by feature interactions, "lof" is sklearn’s local outlier factor with its default 20 neighbours, which scores a cell against the density of its neighbourhood, and "mad" takes the largest robust z-score across features, which is easy to explain but sees each feature alone.

contamination float (default: 0.01)

Fraction of cells to flag, rounded up to a whole cell within each by group, so a non-empty group always flags its most outlying cell and the flagged fraction is higher than asked for in a group smaller than 1 / contamination. Ignored when score_cutoff is given.

score_cutoff float | None (default: None)

Threshold the score absolutely instead of by quantile. With method="mad" the score is a robust z-score, so score_cutoff=5 gives the usual rule.

key str | None (default: 'selected')

Input: the name of a boolean var column; only the features it flags are scored, usually "selected". Brieflow’s intensity filter is method="lof" with a key that flags the mean intensity of each channel. Falls back to every feature when the column is absent. Not the output name, which is key_added.

by str | None (default: None)

Threshold within each group of this obs column, e.g. per plate, rather than globally.

seed int (default: 0)

Seed for isolation_forest and the n_fit sample.

ecod_aggregation str (default: 'pyod')

How "ecod" combines features. "pyod" sums each feature’s larger tail, or both tails where its skewness is zero or undefined, as pyod and scmorph do, so no score depends on which way round a feature is measured. "paper" is Algorithm 1 of Li et al. [2023], the largest of the left-tail, right-tail and skew-directed sums.

n_fit int | None (default: None)

"lof" fits its density on a random sample of at most this many rows per by group and scores every row against that sample, rather than fitting on all of them, so its time and memory grow with the sample rather than the whole screen and stay bounded however large the screen is. A smaller sample sets a coarser scale for “normal” density, measuring each cell against a sparser neighbourhood than a full fit would, so the sampled run flags its most outlying cells by a different standard and can disagree substantially over which cells those are, more so the smaller the sample and the denser the data; it trades that fidelity for a run whose cost no longer rises with the number of cells. None, the default, fits on every row, the exact local outlier factor, which runs on every thread numba is allowed. A group with no more than n_fit rows is fit in full either way.

key_added str (default: 'qc_outlier')

Output: the prefix for the results, written to obs[key_added] and obs[key_added + "_score"].

copy bool (default: False)

Return a modified copy instead of mutating in place.

Return type:

AnnData | None

Returns:

None, or the modified copy. Writes the boolean obs[key_added] and the score itself to obs[key_added + "_score"]. The flagged cells are also added to obs["qc_fail"], the union of the qc_* flags that filter_cells() drops, which is created if absent; a cell already failing keeps failing.

Raises:

ValueError – If method or ecod_aggregation is unknown, contamination is outside (0, 1) and no score_cutoff is given, or n_fit is below 2.