mantispy.tl.subpopulation_hits#

mantispy.tl.subpopulation_hits(adata, cluster_key='leiden', groupby='Metadata_Perturbation', reference='negcon', use_rep=None, min_cells=3, seed=0, key_added='subpopulation_hits', copy=False, *, replicate=None, n_permutations=1000)[source]#

Test each perturbation against the controls within each cluster.

An effect confined to one cell state is diluted by the other states in a well median. Comparing cells within a cluster avoids that.

Parameters:
adata AnnData

Clustered single-cell object.

cluster_key str (default: 'leiden')

obs column holding the cluster label.

groupby str (default: 'Metadata_Perturbation')

obs column holding the perturbation.

reference str | None (default: 'negcon')

Which rows are the controls, "negcon" or the name of a boolean obs column.

use_rep str | None (default: None)

Measure in obsm[use_rep] instead of X.

min_cells int (default: 3)

Skip a (cluster, group) pair with fewer cells than this on either side. A cluster needs twice as many controls, and at least four, since half of them place the centroid and half supply the distances tested against. The reference group’s own row needs four times as many, since it comes from a second split of the held-out half.

seed int (default: 0)

Seed for the split of a cluster’s controls and the permutation null.

key_added str (default: 'subpopulation_hits')

Name for the output table.

copy bool (default: False)

Return a modified copy instead of mutating in place.

replicate str | Sequence[str] | None (default: None)

obs column, or sequence of columns, whose groups are the design’s exchangeable unit: the well in an arrayed screen, the guide ("Metadata_sgRNA") in a pooled one. The permutation null then draws whole replicates rather than cells, since cells within a well are not independent replicates (see Notes). Left None, it defaults to the physical well on an object stamped cell resolution with replicated wells; it warns and falls back to the analytic KS approximation when no complete well column is present, and raises on a pooled design, where some well holds both reference and tested cells.

n_permutations int (default: 1000)

Size of the replicate permutation null. Ignored on the analytic fallback, which has no permutation draw.

Return type:

AnnData | None

Returns:

None, or the modified copy. Writes uns["mantispy"][key_added] with cluster, group, n_cells, statistic (KS), pvalue and qvalue.

Raises:
  • KeyError – obs has no column cluster_key.

  • ValueError – replicate is not given on a pooled design at cell resolution.

Notes

Each cell is reduced to its Euclidean distance from the control centroid of its own cluster, and a KS test compares the treated cells’ distances with the controls’. This is close to hit_calling(method="ks"), restricted to comparable cells. A distance is used rather than a single feature so that the test means the same on every dataset.

Cells within a well are not independent replicates, so the p-value comes from a permutation over whole replicates: whole wells, or whatever replicate names, are drawn for the pseudo-treated side and the KS statistic recomputed, the same unit hit_calling() resamples. Without a usable well column at cell resolution it warns and falls back to the analytic KS approximation on cell counts, which is anti-conservative for the same reason. In a pooled screen every well holds every perturbation, so drawing whole wells cannot separate a group from the reference; the exchangeable unit is the guide, and the call raises until replicate names it.

A cluster’s controls are split in half, as in hit_calling(). One half places the centroid and the other supplies the distances tested against, so the null is out of sample. Controls measured against a centroid they defined themselves sit closer to it than any other group can. That bias grows with features per control, which is the shape of real Cell Painting data. On pure noise with few controls and many features, a null drawn from the rows that placed the centroid calls far more pseudo-treatments than the nominal rate at raw p < 0.05, and the split stays near it.

The controls carry a perturbation label of their own, so one row of the table is the reference group against itself. That row is computed from the held-out half alone, split once more, at random, so that the cells tested and the cells they are tested against are different cells. It is therefore a draw from the null, rather than a sample compared with part of itself measured against a centre half of it placed. A cluster with too few controls to split twice has no such row.