Repository: owkin/PyDESeq2
Pull request #379 (2025-04-07 19:01:10)

ENH DeseqStats n_cpus
#### What does your PR implement? Be specific.

Adds n_cpus argument to DeseqStats with the same logic as DeseqDataSet


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 60bba87..6dc89b7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
       args: # arguments to configure black
        - --line-length=89
 -   repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.11.2
+    rev: v0.11.4
     hooks:
     - id: ruff
       args: [ --fix, --exit-non-zero-on-fix]
diff --git a/pydeseq2/dds.py b/pydeseq2/dds.py
index ff91a2e..126e4f0 100644
--- a/pydeseq2/dds.py
+++ b/pydeseq2/dds.py
@@ -321,16 +321,17 @@ class DeseqDataSet(ad.AnnData):
         self.filtered_genes = None
 
         if inference:
-            if hasattr(inference, "n_cpus"):
-                if n_cpus:
+            if n_cpus:
+                if hasattr(inference, "n_cpus"):
                     inference.n_cpus = n_cpus
-            else:
-                warnings.warn(
-                    "The provided inference object does not have an n_cpus "
-                    "attribute, cannot override `n_cpus`.",
-                    UserWarning,
-                    stacklevel=2,
-                )
+                else:
+                    warnings.warn(
+                        "The provided inference object does not have an n_cpus "
+                        "attribute, cannot override `n_cpus`.",
+                        UserWarning,
+                        stacklevel=2,
+                    )
+
         # Initialize the inference object.
         self.inference = inference or DefaultInference(n_cpus=n_cpus)
 
diff --git a/pydeseq2/ds.py b/pydeseq2/ds.py
index ccb9945..f73960c 100644
--- a/pydeseq2/ds.py
+++ b/pydeseq2/ds.py
@@ -1,5 +1,6 @@
 import sys
 import time
+import warnings
 from typing import Literal
 
 # import anndata as ad
@@ -141,6 +142,7 @@ class DeseqStats:
         ) = None,
         inference: Inference | None = None,
         quiet: bool = False,
+        n_cpus: int | None = None,
     ) -> None:
         assert (
             "LFC" in dds.varm
@@ -188,8 +190,20 @@ class DeseqStats:
         self.shrunk_LFCs = False
         self.quiet = quiet
 
+        if inference:
+            if n_cpus:
+                if hasattr(inference, "n_cpus"):
+                    inference.n_cpus = n_cpus
+                else:
+                    warnings.warn(
+                        "The provided inference object does not have an n_cpus "
+                        "attribute, cannot override `n_cpus`.",
+                        UserWarning,
+                        stacklevel=2,
+                    )
+
         # Initialize the inference object.
-        self.inference = inference or DefaultInference()
+        self.inference = inference or DefaultInference(n_cpus=n_cpus)
 
         # If the `refit_cooks` attribute of the dds object is True, check that outliers
         # were actually refitted.


Test patch:
diff --git a/tests/test_pydeseq2.py b/tests/test_pydeseq2.py
index a61682f..d2eb965 100644
--- a/tests/test_pydeseq2.py
+++ b/tests/test_pydeseq2.py
@@ -194,9 +194,7 @@ def test_alt_hypothesis(alt_hypothesis, counts_df, metadata, tol=0.02):
     )
 
     dds = DeseqDataSet(
-        counts=counts_df,
-        metadata=metadata,
-        design="~condition",
+        counts=counts_df, metadata=metadata, design="~condition", n_cpus=2
     )
     dds.deseq2()
 
@@ -205,6 +203,7 @@ def test_alt_hypothesis(alt_hypothesis, counts_df, metadata, tol=0.02):
         contrast=["condition", "B", "A"],
         lfc_null=-0.5 if alt_hypothesis == "less" else 0.5,
         alt_hypothesis=alt_hypothesis,
+        n_cpus=2,
     )
     ds.summary()