| """Tests for per-cell-type beta estimation (groupby parameter).""" |
|
|
| import numpy as np |
| import pytest |
|
|
|
|
| def test_groupby_produces_varm(preprocessed_adata): |
| """groupby stores per-group betas in adata.varm['beta_groups'].""" |
| import scptr |
|
|
| preprocessed_adata.obs["cell_type"] = np.random.choice( |
| ["A", "B", "C"], size=preprocessed_adata.n_obs |
| ) |
| scptr.tl.estimate_beta(preprocessed_adata, groupby="cell_type") |
|
|
| assert "beta_groups" in preprocessed_adata.varm |
| assert preprocessed_adata.varm["beta_groups"].shape == ( |
| preprocessed_adata.n_vars, |
| 3, |
| ) |
| assert "beta" in preprocessed_adata.var.columns |
|
|
|
|
| def test_groupby_single_group_matches_global(preprocessed_adata): |
| """When there is only one group, groupby result matches global.""" |
| import scptr |
|
|
| |
| scptr.tl.estimate_beta(preprocessed_adata) |
| beta_global = preprocessed_adata.var["beta"].values.copy() |
|
|
| |
| preprocessed_adata.obs["one_group"] = "all" |
| scptr.tl.estimate_beta(preprocessed_adata, groupby="one_group") |
| beta_grouped = preprocessed_adata.var["beta"].values.copy() |
|
|
| np.testing.assert_allclose(beta_global, beta_grouped, rtol=1e-5) |
|
|
|
|
| def test_groupby_missing_column_raises(preprocessed_adata): |
| """groupby with nonexistent column raises KeyError.""" |
| import scptr |
|
|
| with pytest.raises(KeyError, match="Missing required obs columns"): |
| scptr.tl.estimate_beta(preprocessed_adata, groupby="nonexistent") |
|
|
|
|
| def test_groupby_consensus_is_clipped_median(preprocessed_adata): |
| """Consensus beta is median across group betas, then globally clipped.""" |
| import scptr |
|
|
| preprocessed_adata.obs["cell_type"] = np.random.choice( |
| ["X", "Y"], size=preprocessed_adata.n_obs |
| ) |
| scptr.tl.estimate_beta(preprocessed_adata, groupby="cell_type") |
|
|
| beta_groups = preprocessed_adata.varm["beta_groups"] |
| raw_median = np.nanmedian(beta_groups.values, axis=1) |
| |
| positive = raw_median[raw_median > 0] |
| if len(positive) > 0: |
| cap = np.percentile(positive, 99) |
| expected = np.clip(raw_median, 0, cap).astype(np.float32) |
| else: |
| expected = raw_median.astype(np.float32) |
| actual = preprocessed_adata.var["beta"].values |
|
|
| np.testing.assert_allclose(actual, expected, rtol=1e-5) |
|
|