mantispy.tl.hit_calling#
- mantispy.tl.hit_calling(adata, groupby='Metadata_Perturbation', reference='negcon', method='mahalanobis', covariance='empirical', use_rep=None, n_permutations=1000, threshold=0.05, seed=0, key_added='hits', copy=False)#
Call hits by testing each group’s distance from the controls.
Each group is scored against the half of the reference rows that did not fit the centroid and covariance, and its null is the other ways to draw a group of its size from the group and those controls pooled. Under the null the two are exchangeable, so the null is calibrated; bootstrapping the controls alone is not, because it is centred on that sample’s own median rather than the population’s and leaves the error in that centre out of its spread.
This wants a well-replicated design. The statistic is a group’s median distance, so a group of one or two wells is dominated by whichever wells it holds and no number of permutations recovers that;
mantispy.tl.map()withmode="activity"ranks replicate pairs instead and is the usual readout on screens with little replication. JUMP-Target-2 read as a single plate gives every compound one well and is the common way to land in that regime, while the same plate map read across several of them gives one well per plate.Distance from the controls also rises when a treatment kills cells. Read the calls beside a cell count, or beside
mantispy.tl.cytotoxicity(), before taking them for morphology.- Parameters:
adata (
AnnData) – Object to score, at cell or well resolution.groupby (
str(default:'Metadata_Perturbation')) – Column defining the groups to test.reference (
str|None(default:'negcon')) – Which rows are the controls. They are split in half, one half to fit the covariance and the other to form the null (see Notes).method (
str(default:'mahalanobis')) –"mahalanobis"scores the median distance of the group’s rows from the control centroid, measured in the controls’ covariance so that directions the controls already vary in count for less."ks"scores the Kolmogorov-Smirnov statistic between the group’s and the controls’ distance distributions, which detects a shifted subpopulation that leaves the median unchanged. Use it at cell resolution. Its p-value comes fromscipy.stats.ks_2samp, son_permutationsdoes not apply.covariance (
str(default:'empirical')) – Scatter the Mahalanobis distance is measured in."empirical"uses every fitting control row."robust"uses the minimum covariance determinant subset, so a few stray control wells stop widening the covariance in their own direction and masking real hits there; it needs more control rows than features, so pair it withuse_rep.use_rep (
str|None(default:None)) – Scoreobsm[use_rep]instead ofX. When the covariance-fitting half of the controls has no more rows than there are features, the covariance is singular and a warning suggests a PCA representation.n_permutations (
int(default:1000)) – Size of the permutation null. Applies tomethod="mahalanobis"only.threshold (
float(default:0.05)) – q-value below which a group is called a hit inis_hit.seed (
int(default:0)) – Seed for the control split and the permutation null.key_added (
str(default:'hits')) – Name for the outputs.copy (
bool(default:False)) – Return a modified copy instead of mutating in place.
- Return type:
- Returns:
None, or the modified copy. Writesuns["mantispy"][key_added]withgroup,n_obs,distance,pvalue,qvalueandis_hit, wheren_obscounts the rows the statistic used rather than the rows the group has, and joinsobs[key_added + "_distance"]andobs[key_added + "_qvalue"]back onto the rows. Also writesobs[key_added + "_row_distance"], each row’s own distance from the control centroid rather than its group’s. That is the column a dose-response fit wants: the group statistic is one number repeated over the group’s rows, so the controls show no spread and nothing downstream can read a scale off them. Rows that fitted the centroid sit a little closer to it than the held-out controls do, by the same split the Notes describe. Which rows those are is written toobs[key_added + "_reference_held_out"], true for the controls that did not fit, so that a scale taken from the controls can be taken from the honest half.- Raises:
ValueError –
methodis not one ofMETHODS, orreferenceselects fewer than four rows.
Notes
The controls are split in half. One half estimates the centroid and the covariance, and the other supplies the null. A null drawn from the rows that defined the centroid would be in-sample while every tested group is out-of-sample, so it would come out too small and call pure noise as hits. Because only half the controls fit the covariance, the singular-covariance warning fires when there are fewer than about twice as many controls as features.
A row that fitted the centroid and the covariance sits closer to the centroid than any other group’s row can, so it stays off the tested side of every group. The controls carry a perturbation label of their own, and that group is therefore left with the held-out half, which is split once more, at random, so that the rows tested and the rows they are tested against are different rows. Its row of the table is a draw from the null rather than a sample compared with part of itself measured against a centroid half of it placed, and
n_obsreports about a quarter of the controls for it. Its null is the other ways to halve those rows rather than a bootstrap of them, which would be too wide because the sample is part of what it is drawn from. That row is therefore an honest draw from the null: its p-value is uniform and falls below any cutoff about as often as the cutoff says, so a control group appearing in a hit list is this test working rather than a fault.The null is drawn from the controls only, and asks whether a group is further out than the same number of control rows would be. Drawing from every row would put real hits into the null, and a screen with many hits would look like one with none.
Calibration degrades with few controls. On pure-noise screens of 12 groups of 12 rows with 10 features (the
pure_noise_screentest fixture), the false positive rate at a nominal 0.05 was 0.10 with 48 controls, 0.077 with 192 and 0.052 with 384. Across four configurations from 20 to 80 features and 48 to 200 controls, ten seeds each, it was 2.1% overall. Neither figure is a bound for another screen, and both were measured withmethod="mahalanobis". Undermethod="ks"there is no permutation null; each group’s distances are compared with those of the null half byscipy.stats.ks_2samp.To check the rate on your own screen,
diagnose_testing()relabels control wells as pseudo-treatments of your group sizes and reports the fraction called.