pertpy 0.11.5__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pertpy/__init__.py +4 -1
- pertpy/tools/_milo.py +77 -24
- pertpy/tools/_perturbation_space/_perturbation_space.py +1 -1
- {pertpy-0.11.5.dist-info → pertpy-1.0.0.dist-info}/METADATA +8 -2
- {pertpy-0.11.5.dist-info → pertpy-1.0.0.dist-info}/RECORD +7 -7
- {pertpy-0.11.5.dist-info → pertpy-1.0.0.dist-info}/WHEEL +0 -0
- {pertpy-0.11.5.dist-info → pertpy-1.0.0.dist-info}/licenses/LICENSE +0 -0
pertpy/__init__.py
CHANGED
@@ -2,10 +2,11 @@
|
|
2
2
|
|
3
3
|
__author__ = "Lukas Heumos"
|
4
4
|
__email__ = "lukas.heumos@posteo.net"
|
5
|
-
__version__ = "0.
|
5
|
+
__version__ = "1.0.0"
|
6
6
|
|
7
7
|
import warnings
|
8
8
|
|
9
|
+
from anndata._core.aligned_df import ImplicitModificationWarning
|
9
10
|
from matplotlib import MatplotlibDeprecationWarning
|
10
11
|
from numba import NumbaDeprecationWarning
|
11
12
|
|
@@ -13,6 +14,8 @@ warnings.filterwarnings("ignore", category=NumbaDeprecationWarning)
|
|
13
14
|
warnings.filterwarnings("ignore", category=MatplotlibDeprecationWarning)
|
14
15
|
warnings.filterwarnings("ignore", category=SyntaxWarning)
|
15
16
|
warnings.filterwarnings("ignore", category=UserWarning, module="scvi._settings")
|
17
|
+
warnings.filterwarnings("ignore", message="Environment variable.*redefined by R")
|
18
|
+
warnings.filterwarnings("ignore", message="Transforming to str index.", category=ImplicitModificationWarning)
|
16
19
|
|
17
20
|
import mudata
|
18
21
|
|
pertpy/tools/_milo.py
CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import random
|
4
4
|
import re
|
5
|
+
from importlib.util import find_spec
|
5
6
|
from typing import TYPE_CHECKING, Literal
|
6
7
|
|
7
8
|
import matplotlib.pyplot as plt
|
@@ -29,18 +30,6 @@ from sklearn.metrics.pairwise import euclidean_distances
|
|
29
30
|
class Milo:
|
30
31
|
"""Python implementation of Milo."""
|
31
32
|
|
32
|
-
def __init__(self):
|
33
|
-
try:
|
34
|
-
from rpy2.robjects import conversion, numpy2ri, pandas2ri
|
35
|
-
from rpy2.robjects.packages import STAP, PackageNotInstalledError, importr
|
36
|
-
except ModuleNotFoundError:
|
37
|
-
raise ImportError("milo requires rpy2 to be installed.") from None
|
38
|
-
|
39
|
-
try:
|
40
|
-
importr("edgeR")
|
41
|
-
except ImportError as e:
|
42
|
-
raise ImportError("milo requires a valid R installation with edger installed:\n") from e
|
43
|
-
|
44
33
|
def load(
|
45
34
|
self,
|
46
35
|
input: AnnData,
|
@@ -266,7 +255,7 @@ class Milo:
|
|
266
255
|
subset_samples: list[str] | None = None,
|
267
256
|
add_intercept: bool = True,
|
268
257
|
feature_key: str | None = "rna",
|
269
|
-
solver: Literal["edger", "
|
258
|
+
solver: Literal["edger", "pydeseq2"] = "edger",
|
270
259
|
):
|
271
260
|
"""Performs differential abundance testing on neighbourhoods using QLF test implementation as implemented in edgeR.
|
272
261
|
|
@@ -279,7 +268,9 @@ class Milo:
|
|
279
268
|
subset_samples: subset of samples (obs in `milo_mdata['milo']`) to use for the test.
|
280
269
|
add_intercept: whether to include an intercept in the model. If False, this is equivalent to adding + 0 in the design formula. When model_contrasts is specified, this is set to False by default.
|
281
270
|
feature_key: If input data is MuData, specify key to cell-level AnnData object.
|
282
|
-
solver: The solver to fit the model to.
|
271
|
+
solver: The solver to fit the model to.
|
272
|
+
The "edger" solver requires R, rpy2 and edgeR to be installed and is the closest to the R implementation.
|
273
|
+
The "pydeseq2" requires pydeseq2 to be installed. It is still very comparable to the "edger" solver but might be a bit slower.
|
283
274
|
|
284
275
|
Returns:
|
285
276
|
None, modifies `milo_mdata['milo']` in place, adding the results of the DA test to `.var`:
|
@@ -298,7 +289,6 @@ class Milo:
|
|
298
289
|
>>> milo.make_nhoods(mdata["rna"])
|
299
290
|
>>> mdata = milo.count_nhoods(mdata, sample_col="orig.ident")
|
300
291
|
>>> milo.da_nhoods(mdata, design="~label")
|
301
|
-
|
302
292
|
"""
|
303
293
|
try:
|
304
294
|
sample_adata = mdata["milo"]
|
@@ -425,13 +415,65 @@ class Milo:
|
|
425
415
|
res = pd.DataFrame(res)
|
426
416
|
# The columns of res looks like e.g. table.A, table.B, so remove the prefix
|
427
417
|
res.columns = [col.replace("table.", "") for col in res.columns]
|
428
|
-
|
418
|
+
elif solver == "pydeseq2":
|
419
|
+
if find_spec("pydeseq2") is None:
|
420
|
+
raise ImportError("pydeseq2 is required but not installed. Install with: pip install pydeseq2")
|
421
|
+
|
422
|
+
from pydeseq2.dds import DeseqDataSet
|
423
|
+
from pydeseq2.ds import DeseqStats
|
424
|
+
|
425
|
+
counts_filtered = count_mat[np.ix_(keep_nhoods, keep_smp)]
|
426
|
+
design_df_filtered = design_df.iloc[keep_smp].copy()
|
427
|
+
|
428
|
+
design_df_filtered = design_df_filtered.astype(
|
429
|
+
dict.fromkeys(design_df_filtered.select_dtypes(exclude=["number"]).columns, "category")
|
430
|
+
)
|
431
|
+
|
432
|
+
design_clean = design if design.startswith("~") else f"~{design}"
|
433
|
+
|
434
|
+
dds = DeseqDataSet(
|
435
|
+
counts=pd.DataFrame(counts_filtered.T, index=design_df_filtered.index),
|
436
|
+
metadata=design_df_filtered,
|
437
|
+
design=design_clean,
|
438
|
+
refit_cooks=True,
|
439
|
+
)
|
440
|
+
|
441
|
+
dds.deseq2()
|
442
|
+
|
443
|
+
if model_contrasts is not None and "-" in model_contrasts:
|
444
|
+
if "(" in model_contrasts or "+" in model_contrasts.split("-")[1]:
|
445
|
+
raise ValueError(
|
446
|
+
f"Complex contrasts like '{model_contrasts}' are not supported by pydeseq2. "
|
447
|
+
"Use simple pairwise contrasts (e.g., 'GroupA-GroupB') or switch to solver='edger'."
|
448
|
+
)
|
449
|
+
|
450
|
+
parts = model_contrasts.split("-")
|
451
|
+
factor_name = design_clean.replace("~", "").split("+")[-1].strip()
|
452
|
+
group1 = parts[0].replace(factor_name, "").strip()
|
453
|
+
group2 = parts[1].replace(factor_name, "").strip()
|
454
|
+
stat_res = DeseqStats(dds, contrast=[factor_name, group1, group2])
|
455
|
+
else:
|
456
|
+
factor_name = design_clean.replace("~", "").split("+")[-1].strip()
|
457
|
+
if not isinstance(design_df_filtered[factor_name], pd.CategoricalDtype):
|
458
|
+
design_df_filtered[factor_name] = design_df_filtered[factor_name].astype("category")
|
459
|
+
categories = design_df_filtered[factor_name].cat.categories
|
460
|
+
stat_res = DeseqStats(dds, contrast=[factor_name, categories[-1], categories[0]])
|
461
|
+
|
462
|
+
stat_res.summary()
|
463
|
+
res = stat_res.results_df
|
464
|
+
|
465
|
+
res = res.rename(
|
466
|
+
columns={"baseMean": "logCPM", "log2FoldChange": "logFC", "pvalue": "PValue", "padj": "FDR"}
|
467
|
+
)
|
468
|
+
|
469
|
+
res = res[["logCPM", "logFC", "PValue", "FDR"]]
|
470
|
+
|
429
471
|
res.index = sample_adata.var_names[keep_nhoods] # type: ignore
|
430
472
|
if any(col in sample_adata.var.columns for col in res.columns):
|
431
473
|
sample_adata.var = sample_adata.var.drop(res.columns, axis=1)
|
432
474
|
sample_adata.var = pd.concat([sample_adata.var, res], axis=1)
|
433
|
-
|
434
|
-
self._graph_spatial_fdr(sample_adata
|
475
|
+
|
476
|
+
self._graph_spatial_fdr(sample_adata)
|
435
477
|
|
436
478
|
def annotate_nhoods(
|
437
479
|
self,
|
@@ -674,6 +716,17 @@ class Milo:
|
|
674
716
|
self,
|
675
717
|
):
|
676
718
|
"""Set up rpy2 to run edgeR."""
|
719
|
+
try:
|
720
|
+
from rpy2.robjects import conversion, numpy2ri, pandas2ri
|
721
|
+
from rpy2.robjects.packages import STAP, PackageNotInstalledError, importr
|
722
|
+
except ModuleNotFoundError:
|
723
|
+
raise ImportError("milo requires rpy2 to be installed.") from None
|
724
|
+
|
725
|
+
try:
|
726
|
+
importr("edgeR")
|
727
|
+
except ImportError as e:
|
728
|
+
raise ImportError("milo requires a valid R installation with edger installed.") from e
|
729
|
+
|
677
730
|
from rpy2.robjects.packages import importr
|
678
731
|
|
679
732
|
edgeR = self._try_import_bioc_library("edgeR")
|
@@ -685,26 +738,27 @@ class Milo:
|
|
685
738
|
|
686
739
|
def _try_import_bioc_library(
|
687
740
|
self,
|
688
|
-
|
741
|
+
r_package: str,
|
689
742
|
):
|
690
743
|
"""Import R packages.
|
691
744
|
|
692
745
|
Args:
|
693
|
-
|
746
|
+
r_package: R packages name
|
694
747
|
"""
|
695
748
|
from rpy2.robjects.packages import PackageNotInstalledError, importr
|
696
749
|
|
697
750
|
try:
|
698
|
-
_r_lib = importr(
|
751
|
+
_r_lib = importr(r_package)
|
699
752
|
return _r_lib
|
700
753
|
except PackageNotInstalledError:
|
701
|
-
logger.error(
|
754
|
+
logger.error(
|
755
|
+
f"Install Bioconductor library `{r_package!r}` first as `BiocManager::install({r_package!r}).`"
|
756
|
+
)
|
702
757
|
raise
|
703
758
|
|
704
759
|
def _graph_spatial_fdr(
|
705
760
|
self,
|
706
761
|
sample_adata: AnnData,
|
707
|
-
neighbors_key: str | None = None,
|
708
762
|
):
|
709
763
|
"""FDR correction weighted on inverse of connectivity of neighbourhoods.
|
710
764
|
|
@@ -712,7 +766,6 @@ class Milo:
|
|
712
766
|
|
713
767
|
Args:
|
714
768
|
sample_adata: Sample-level AnnData.
|
715
|
-
neighbors_key: The key in `adata.obsp` to use as KNN graph.
|
716
769
|
"""
|
717
770
|
# use 1/connectivity as the weighting for the weighted BH adjustment from Cydar
|
718
771
|
w = 1 / sample_adata.var["kth_distance"]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pertpy
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.0
|
4
4
|
Summary: Perturbation Analysis in the scverse ecosystem.
|
5
5
|
Project-URL: Documentation, https://pertpy.readthedocs.io
|
6
6
|
Project-URL: Source, https://github.com/scverse/pertpy
|
@@ -155,7 +155,13 @@ pip install 'pertpy[tcoda]'
|
|
155
155
|
|
156
156
|
### milo
|
157
157
|
|
158
|
-
milo
|
158
|
+
milo requires either the "de" extra for the "pydeseq2" solver:
|
159
|
+
|
160
|
+
```console
|
161
|
+
pip install 'pertpy[de]'
|
162
|
+
```
|
163
|
+
|
164
|
+
or, edger, statmod, and rpy2 for the "edger" solver:
|
159
165
|
|
160
166
|
```R
|
161
167
|
BiocManager::install("edgeR")
|
@@ -1,4 +1,4 @@
|
|
1
|
-
pertpy/__init__.py,sha256=
|
1
|
+
pertpy/__init__.py,sha256=cZHJ7PIOhtLkxJMlHbJ2rzei5xhLB4vg0c8AaIShfzc,972
|
2
2
|
pertpy/_doc.py,sha256=j5TMNC-DA9yIMqIIUNpjpcVgWfRqyBBfvbRjnCM_OLs,427
|
3
3
|
pertpy/_types.py,sha256=IcHCojCUqx8CapibNkcYf2TUqjBFP2ujeELvn_IBSBQ,154
|
4
4
|
pertpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -21,7 +21,7 @@ pertpy/tools/_augur.py,sha256=tc1YKyc0BwzrEGgctsfyy7DsTNKxyvy7ZvWraTWCc1A,55262
|
|
21
21
|
pertpy/tools/_cinemaot.py,sha256=54-rS0AEj31dMe7iU4kEmLoAunq3jNuhsBE3IEp9hrI,38071
|
22
22
|
pertpy/tools/_dialogue.py,sha256=mygIZm5i_bnEE37TTQtr1efl_KJq-ejzeL3V1Bmr7Pg,52354
|
23
23
|
pertpy/tools/_enrichment.py,sha256=55mwotLH9DXQOhl85MCkxXu-MX0RysLyrPheJysAnF0,21369
|
24
|
-
pertpy/tools/_milo.py,sha256=
|
24
|
+
pertpy/tools/_milo.py,sha256=9yoB9gkBNujqYDTKOlH2v3wiWhs5PdCuB8RgZ3xVI0Y,48049
|
25
25
|
pertpy/tools/_mixscape.py,sha256=HfrpBeRlxHXaOpZkF2FmX7dg35kUB1rL0_-n2aSi2_0,57905
|
26
26
|
pertpy/tools/decoupler_LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
27
27
|
pertpy/tools/transferlearning_MMD_LICENSE,sha256=MUvDA-o_j9htRpI8fStVdCRuyLdPkQUuIH0a_EIc57w,1069
|
@@ -45,14 +45,14 @@ pertpy/tools/_perturbation_space/_clustering.py,sha256=pNx_SpPkZfCbgF7vzHWqAaiiH
|
|
45
45
|
pertpy/tools/_perturbation_space/_comparison.py,sha256=-NzCPRT-IlhJ9hOz7NQLSk0riIzr2C0yZvX6zm3kon4,4291
|
46
46
|
pertpy/tools/_perturbation_space/_discriminator_classifiers.py,sha256=a53-YmUwDHQBCT7ZWe_RH7PZsGXvoSHmJaQyL0CBJng,23383
|
47
47
|
pertpy/tools/_perturbation_space/_metrics.py,sha256=y8-baP8WRdB1iDgvP3uuQxSCDxA2lcxvEHHM2C_vWHY,3248
|
48
|
-
pertpy/tools/_perturbation_space/_perturbation_space.py,sha256=
|
48
|
+
pertpy/tools/_perturbation_space/_perturbation_space.py,sha256=Vyh15wWw9dcu2YUWhziQd2mA9-4IY8EC5dzkBT9HaIo,19457
|
49
49
|
pertpy/tools/_perturbation_space/_simple.py,sha256=AJlHRaEP-vViBeMDvvMtUnXMuIKqZVc7wggnjsHMfMw,12721
|
50
50
|
pertpy/tools/_scgen/__init__.py,sha256=uERFlFyF88TH0uLiwmsUGEfHfLVCiZMFuk8gO5f7164,45
|
51
51
|
pertpy/tools/_scgen/_base_components.py,sha256=Qq8myRUm43q9XBrZ9gBggfa2cSV2wbz_KYoLgH7iF1A,3009
|
52
52
|
pertpy/tools/_scgen/_scgen.py,sha256=AQNGsDe-9HEqli3oq7UBDg68ofLCoXm-R_jnLFQ-rlc,30856
|
53
53
|
pertpy/tools/_scgen/_scgenvae.py,sha256=bPk4v7EdJc7ROdLuDitHiX_Pvwa7Flw2qHRUwBvjLJY,3889
|
54
54
|
pertpy/tools/_scgen/_utils.py,sha256=qz5QUn_Bvk2NGyYVzp3jgjWTFOMt1YyHwUo6HWtoThY,2871
|
55
|
-
pertpy-0.
|
56
|
-
pertpy-0.
|
57
|
-
pertpy-0.
|
58
|
-
pertpy-0.
|
55
|
+
pertpy-1.0.0.dist-info/METADATA,sha256=PnK9O-MyIPzSy5DNOqMN7G6zcxZ2ZTJnMFB5cEr5XJQ,8920
|
56
|
+
pertpy-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
57
|
+
pertpy-1.0.0.dist-info/licenses/LICENSE,sha256=XuiT2hxeRInhquEIBKMZ5M21n5syhDQ4XbABoposIAg,1100
|
58
|
+
pertpy-1.0.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|