Repository: rsagroup/rsatoolbox

Issue:
rdms.rank_transform treats nans as data
When an `RDMs.dissimilarities` contains `nan` values, calling `rdms.rank_transform` on it treats the `nan`s as data, and (by default when using `rank_transform(method='average')`) assigns them sequential integer ranks.

A better option for me (and perhaps the least surprising) would be if `nan` values were ignored (i.e. kept as `nan`) when running `rank_transform`.

I often pass an RDM through `rank_transform` when visualising it, like `show_rdms(rank_transform(rdms))`, and this causes weird colours when an RDM contains a lot of `nans` (especially when entire rows/columns are missing).

Discussion:
@caiw: The ability to change how `nan`s are handled by `scipy.stats.rankdata` was [changed in scipy v1.1.0](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.rankdata.html), with the new `nan_policy` argument, which to achieve the desired solution above should be set to `omit`.@JasperVanDenBosch: Agreed, let's fix this@caiw: I've submitted a PR #403, it's a 1-line fix!

Solution patch:
diff --git a/requirements.txt b/requirements.txt
index 11c8dced..d2199b6a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
 numpy>=1.21.2
-scipy
+scipy>=1.10.1
 scikit-learn
 scikit-image
 pandas
diff --git a/src/rsatoolbox/rdm/transform.py b/src/rsatoolbox/rdm/transform.py
index f477dd90..8ec1e926 100644
--- a/src/rsatoolbox/rdm/transform.py
+++ b/src/rsatoolbox/rdm/transform.py
@@ -11,7 +11,7 @@ from scipy.spatial.distance import squareform
 from .rdms import RDMs
 
 
-def rank_transform(rdms: RDMs, method='average'):
+def rank_transform(rdms: RDMs, method='average') -> RDMs:
     """ applies a rank_transform and generates a new RDMs object
     This assigns a rank to each dissimilarity estimate in the RDM,
     deals with rank ties and saves ranks as new dissimilarity estimates.
@@ -30,17 +30,20 @@ def rank_transform(rdms: RDMs, method='average'):
 
     """
     dissimilarities = rdms.get_vectors()
-    dissimilarities = np.array([rankdata(dissimilarities[i], method=method)
-                                for i in range(rdms.n_rdm)])
+    cfg = dict(method=method, nan_policy='omit')
+    dissimilarities = np.array(
+        [rankdata(dissimilarities[i], **cfg) for i in range(rdms.n_rdm)]
+    )
     measure = rdms.dissimilarity_measure or ''
     if '(ranks)' not in measure:
         measure = (measure + ' (ranks)').strip()
-    rdms_new = RDMs(dissimilarities,
-                    dissimilarity_measure=measure,
-                    descriptors=deepcopy(rdms.descriptors),
-                    rdm_descriptors=deepcopy(rdms.rdm_descriptors),
-                    pattern_descriptors=deepcopy(rdms.pattern_descriptors))
-    return rdms_new
+    return RDMs(
+        dissimilarities,
+        dissimilarity_measure=measure,
+        descriptors=deepcopy(rdms.descriptors),
+        rdm_descriptors=deepcopy(rdms.rdm_descriptors),
+        pattern_descriptors=deepcopy(rdms.pattern_descriptors)
+    )
 
 
 def sqrt_transform(rdms):


Test patch:
diff --git a/tests/test_rdm.py b/tests/test_rdm.py
index 28e30582..07b79a0e 100644
--- a/tests/test_rdm.py
+++ b/tests/test_rdm.py
@@ -234,21 +234,15 @@ class TestRDM(unittest.TestCase):
         self.assertEqual(transformed_rdm.n_cond, rdms.n_cond)
 
     def test_rank_transform(self):
-        from rsatoolbox.rdm import rank_transform
-        dis = np.zeros((8, 10))
-        mes = "Euclidean"
-        des = {'subj': 0}
-        pattern_des = {'type': np.array([0, 1, 2, 2, 4])}
-        rdm_des = {'session': np.array([0, 1, 2, 2, 4, 5, 6, 7])}
-        rdms = rsr.RDMs(dissimilarities=dis,
-                        rdm_descriptors=rdm_des,
-                        pattern_descriptors=pattern_des,
-                        dissimilarity_measure=mes,
-                        descriptors=des)
+        from rsatoolbox.rdm.transform import rank_transform
+        from rsatoolbox.rdm.rdms import RDMs
+        rdms = RDMs(
+            dissimilarities=np.array([[8, 6, 10, np.nan]]),
+            dissimilarity_measure="Euclidean",
+        )
         rank_rdm = rank_transform(rdms)
-        self.assertEqual(rank_rdm.n_rdm, rdms.n_rdm)
-        self.assertEqual(rank_rdm.n_cond, rdms.n_cond)
         self.assertEqual(rank_rdm.dissimilarity_measure, 'Euclidean (ranks)')
+        assert_array_equal(rank_rdm.dissimilarities, [[2, 1, 3, np.nan]])
 
     def test_rank_transform_unknown_measure(self):
         from rsatoolbox.rdm import rank_transform