mantispy.pp.feature_select_chatterjee

mantispy.pp.feature_select_chatterjee#

mantispy.pp.feature_select_chatterjee(adata, groupby='Metadata_Perturbation', threshold=0.1, m=1, seed=0, key_added='selected_chatterjee', copy=False)[source]#

Keep features whose values depend on the group, monotonically or otherwise.

Parameters:
  • adata (AnnData) – Object to select features on.

  • groupby (str (default: 'Metadata_Perturbation')) – obs column the features are tested against.

  • threshold (float (default: 0.1)) – Keep features scoring above this. xi is near zero under independence and approaches one when the feature is a deterministic function of the group, so the threshold is comparable across datasets in a way a correlation cutoff is not.

  • m (int (default: 1)) – Right nearest neighbors (Lin & Han 2023). m=1 is Chatterjee’s original coefficient; larger values lower the noise floor without changing what the statistic converges to.

  • seed (int (default: 0)) – Seed for the random tie-breaking.

  • key_added (str (default: 'selected_chatterjee')) – Name of the boolean var column written.

  • copy (bool (default: False)) – Return a modified copy instead of mutating in place.

Return type:

AnnData | None

Returns:

None, or the modified copy. Writes var[key_added] and the statistic itself to var["chatterjee_xi"].

Notes

Run it after feature_select(), which drops redundant or unmeasurable features; this keeps the features that carry information about the perturbation. Subset with mt.pp.subset_features(adata, key="selected_chatterjee").

xi reaches one only for a noiseless function of the group, so real values are much lower. Over pki’s 852 selected features and 38 treatments the largest was 0.32, and the default threshold of 0.1 kept about half of them. Check the distribution in var["chatterjee_xi"] before relying on a fixed cutoff.

With shuffled group labels on the same data, the largest xi is 0.035 at m=1 and 0.018 at m=5, while the largest real value barely changes (0.322 and 0.321). m=1 is the default because the threshold was calibrated there. scmorph uses m=5, and the two implementations agree to 1e-9 (tests/test_equivalence_scmorph.py).