Repository: scverse/scanpy

Issue:
"IndexError: boolean index did not match indexed array along dimension" when working with subsetted anndata object
When I run this

```
import scanpy as sc

sc.logging.print_versions()

pbmc = sc.datasets.pbmc68k_reduced()
pbmc = pbmc[pbmc.obs['louvain'] == '0', :]
sc.pp.scale(pbmc)
```

I get this

```
scanpy==1.4.3 anndata==0.6.21 umap==0.3.9 numpy==1.16.2 scipy==1.3.0 pandas==0.24.2 scikit-learn==0.21.2 statsmodels==0.9.0
/stor/home/ericb123/miniconda3/lib/python3.7/site-packages/scanpy/preprocessing/_simple.py:1120: RuntimeWarning: invalid value encountered in sqrt
  scale = np.sqrt(var)
Trying to set attribute `.X` of view, making a copy.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/stor/home/ericb123/miniconda3/lib/python3.7/site-packages/scanpy/preprocessing/_simple.py", line 860, in scale
    scale(adata.X, zero_center=zero_center, max_value=max_value, copy=False)
File "/stor/home/ericb123/miniconda3/lib/python3.7/site-packages/scanpy/preprocessing/_simple.py", line 876, in scale
    _scale(X, zero_center)
File "/stor/home/ericb123/miniconda3/lib/python3.7/site-packages/scanpy/preprocessing/_simple.py", line 1126, in _scale
    scale[scale == 0] = 1e-12
File "/stor/home/ericb123/miniconda3/lib/python3.7/site-packages/anndata/base.py", line 392, in __setitem__
    getattr(adata_view, attr_name)[idx] = value
IndexError: boolean index did not match indexed array along dimension 0; dimension is 130 but corresponding boolean dimension is 765
```

Any idea what's going on?

Thanks,

-Eric



Discussion:

Resolving patch:
diff --git a/scanpy/preprocessing/_simple.py b/scanpy/preprocessing/_simple.py
--- a/scanpy/preprocessing/_simple.py
+++ b/scanpy/preprocessing/_simple.py
@@ -15,7 +15,7 @@
 
 from .._settings import settings as sett
 from .. import logging as logg
-from ..utils import sanitize_anndata, deprecated_arg_names
+from ..utils import sanitize_anndata, deprecated_arg_names, view_to_actual
 from ._distributed import materialize_as_ndarray
 from ._utils import _get_mean_var
 
@@ -281,6 +281,9 @@ def log1p(
     elif not isinstance(data, AnnData) and np.issubdtype(data.dtype, np.integer):
         raise TypeError("Cannot perform inplace log1p on integer array")
 
+    if isinstance(data, AnnData) and data.isview:
+        view_to_actual(data)
+
     def _log1p(X):
         if issparse(X):
             np.log1p(X.data, out=X.data)
@@ -736,6 +739,9 @@ def regress_out(adata, keys, n_jobs=None, copy=False) -> Optional[AnnData]:
             'lead to high memory use'
         )
     adata = adata.copy() if copy else adata
+
+    sanitize_anndata(adata)
+
     if isinstance(keys, str):
         keys = [keys]
 
@@ -745,7 +751,6 @@ def regress_out(adata, keys, n_jobs=None, copy=False) -> Optional[AnnData]:
     n_jobs = sett.n_jobs if n_jobs is None else n_jobs
 
     # regress on a single categorical variable
-    sanitize_anndata(adata)
     variable_is_categorical = False
     if keys[0] in adata.obs_keys() and is_categorical_dtype(adata.obs[keys[0]]):
         if len(keys) > 1:
@@ -861,6 +866,7 @@ def scale(data, zero_center=True, max_value=None, copy=False) -> Optional[AnnDat
     """
     if isinstance(data, AnnData):
         adata = data.copy() if copy else data
+        view_to_actual(adata)
         # need to add the following here to make inplace logic work
         if zero_center and issparse(adata.X):
             logg.debug(
diff --git a/scanpy/utils.py b/scanpy/utils.py
--- a/scanpy/utils.py
+++ b/scanpy/utils.py
@@ -614,6 +614,15 @@ def sanitize_anndata(adata):
     adata._sanitize()
 
 
+def view_to_actual(adata):
+    if adata.isview:
+        warnings.warn(
+            "Revieved a view of an AnnData. Making a copy.",
+            stacklevel=2
+        )
+        adata._init_as_actual(adata.copy())
+
+
 def moving_average(a, n):
     """Moving average over one-dimensional array.
 


Test patch:
diff --git a/scanpy/tests/test_preprocessing.py b/scanpy/tests/test_preprocessing.py
--- a/scanpy/tests/test_preprocessing.py
+++ b/scanpy/tests/test_preprocessing.py
@@ -3,6 +3,7 @@
 import numpy as np
 from scipy import sparse as sp
 import scanpy as sc
+from sklearn.utils.testing import assert_allclose
 import pytest
 from anndata import AnnData
 
@@ -56,6 +57,19 @@ def test_subsample():
     assert adata.n_obs == 4
 
 
+def test_scale():
+    adata = sc.datasets.pbmc68k_reduced()
+    adata.X = adata.raw.X
+    v = adata[:, 0:adata.shape[1] // 2]
+    # Should turn view to copy https://github.com/theislab/anndata/issues/171#issuecomment-508689965
+    assert v.isview
+    with pytest.warns(Warning, match="view"):
+        sc.pp.scale(v)
+    assert not v.isview
+    assert_allclose(v.X.var(axis=0), np.ones(v.shape[1]), atol=0.01)
+    assert_allclose(v.X.mean(axis=0), np.zeros(v.shape[1]), atol=0.00001)
+
+
 def test_recipe_plotting():
     sc.settings.autoshow = False
     adata = AnnData(np.random.randint(0, 1000, (1000, 1000)))