Repository: owkin/PyDESeq2 (python)

Issue:
[BUG] Design matrix check prevents use of VST without design matrix
**Describe the bug**
When creating a DDS, the latest version now checks that the design matrix is full rank; however, I only wish to use it to run VST without a design matrix. 

**To Reproduce**
```
import pydeseq2.dds
import numpy as np
import pandas as pd
dds = pydeseq2.dds.DeseqDataSet(counts=np.random.poisson(0.2, (100, 200)), clinical=pd.DataFrame({'condition': np.repeat([0, 1], 50), 'condition2':np.repeat([1, 2], 50)}, index=np.arange(100)), design_factors=['condition', 'condition2'])
dds.vst(use_design=False)
```

**Expected behavior**
A clear and concise description of what you expected to happen.

**Actual behavior**
PyDeSeq2 v0.3.3: runs successfully.

PyDeSeq2 v0.3.4
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/scottgigante/envs/immunaISR/lib/python3.8/site-packages/pydeseq2/dds.py", line 221, in __init__
    self._check_full_rank_design()
  File "/Users/scottgigante/envs/immunaISR/lib/python3.8/site-packages/pydeseq2/dds.py", line 1007, in _check_full_rank_design
    raise ValueError(
ValueError: The design matrix is not full rank, so the model cannot be fitted. Please remove the design variables that are linear combinations of others.
```


Pull request description:
MAINT turn rank deficiency exception to warning
#### Reference Issue or PRs
Fixes #136 


#### What does your PR implement? Be specific.
Throw a `UserWarning` instead of a `ValueError` when a `DeseqDataSet` is initialized with a rank-deficient design matrix.
This allows users to run design-free VST without providing a full rank design (as described in #136).


Patch:
diff --git a/pydeseq2/dds.py b/pydeseq2/dds.py
index 9b8b733..7789e5e 100644
--- a/pydeseq2/dds.py
+++ b/pydeseq2/dds.py
@@ -1041,8 +1041,11 @@ class DeseqDataSet(ad.AnnData):
         num_vars = self.obsm["design_matrix"].shape[1]
 
         if rank < num_vars:
-            raise ValueError(
+            warnings.warn(
                 "The design matrix is not full rank, so the model cannot be "
-                "fitted. Please remove the design variables that are linear "
-                "combinations of others."
+                "fitted, but some operations like design-free VST remain possible. "
+                "To perform differential expression analysis, please remove the design "
+                "variables that are linear combinations of others.",
+                UserWarning,
+                stacklevel=2,
             )
diff --git a/pydeseq2/utils.py b/pydeseq2/utils.py
index 3c75a05..20e9953 100644
--- a/pydeseq2/utils.py
+++ b/pydeseq2/utils.py
@@ -1324,5 +1324,6 @@ def make_scatter(
     plt.xlabel("mean of normalized counts")
     plt.ylabel("dispersion")
     plt.tight_layout()
-    plt.savefig(save_path, bbox_inches="tight")
+    if save_path is not None:
+        plt.savefig(save_path, bbox_inches="tight")
     plt.show()


Test patch:
diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py
index 4fe663b..e3989b6 100644
--- a/tests/test_edge_cases.py
+++ b/tests/test_edge_cases.py
@@ -136,7 +136,7 @@ def test_rank_deficient_design():
         {"condition": [0, 1], "batch": ["A", "B"]}, index=["sample1", "sample2"]
     )
 
-    with pytest.raises(ValueError):
+    with pytest.warns(UserWarning):
         DeseqDataSet(
             counts=counts_df, clinical=clinical_df, design_factors=["condition", "batch"]
         )