Repository: scverse/scanpy

Issue:
seaborn.set_theme causing issues with color mapping
_Originally posted by @OnlyBelter in https://github.com/theislab/scanpy/issues/1850#issuecomment-863089065_

> But in the original jupyter notebook which I used to process this .h5ad file (also contains many other steps before this step), I cannot use some specific columns to set parameter color in this function (some columns can be used correctly).

> I think this problem may cause by `seaborn`!
> 
> The following code should reproduce the error:

```python
import scanpy as sc
import seaborn as sns
sns.set()  # <--- here

pbmc = sc.datasets.pbmc68k_reduced()
sc.pl.umap(pbmc, color = 'phase')
```

-------------------------------

On current release this errors:

<details>
<summary> </summary>

```python
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-1-4c43dbe94eaf> in <module>
      4 
      5 pbmc = sc.datasets.pbmc68k_reduced()
----> 6 sc.pl.umap(pbmc, color = 'phase')

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/scanpy/plotting/_tools/scatterplots.py in umap(adata, **kwargs)
    601     If `show==False` a :class:`~matplotlib.axes.Axes` or a list of it.
    602     """
--> 603     return embedding(adata, 'umap', **kwargs)
    604 
    605 

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/scanpy/plotting/_tools/scatterplots.py in embedding(adata, basis, color, gene_symbols, use_raw, sort_order, edges, edges_width, edges_color, neighbors_key, arrows, arrows_kwds, groups, components, layer, projection, scale_factor, color_map, cmap, palette, na_color, na_in_legend, size, frameon, legend_fontsize, legend_fontweight, legend_loc, legend_fontoutline, vmax, vmin, add_outline, outline_width, outline_color, ncols, hspace, wspace, title, show, save, ax, return_fig, **kwargs)
    244             groups=groups,
    245         )
--> 246         color_vector, categorical = _color_vector(
    247             adata,
    248             value_to_plot,

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/scanpy/plotting/_tools/scatterplots.py in _color_vector(adata, values_key, values, palette, na_color)
   1129     else:  # is_categorical_dtype(values)
   1130         color_map = _get_palette(adata, values_key, palette=palette)
-> 1131         color_vector = values.map(color_map).map(to_hex)
   1132 
   1133         # Set color to 'missing color' for all missing values

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/arrays/categorical.py in map(self, mapper)
   1196         new_categories = self.categories.map(mapper)
   1197         try:
-> 1198             return self.from_codes(
   1199                 self._codes.copy(), categories=new_categories, ordered=self.ordered
   1200             )

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/arrays/categorical.py in from_codes(cls, codes, categories, ordered, dtype)
    567         Categories (2, object): ['a' < 'b']
    568         """
--> 569         dtype = CategoricalDtype._from_values_or_dtype(
    570             categories=categories, ordered=ordered, dtype=dtype
    571         )

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/dtypes/dtypes.py in _from_values_or_dtype(cls, values, categories, ordered, dtype)
    271             # Note: This could potentially have categories=None and
    272             # ordered=None.
--> 273             dtype = CategoricalDtype(categories, ordered)
    274 
    275         return dtype

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/dtypes/dtypes.py in __init__(self, categories, ordered)
    158 
    159     def __init__(self, categories=None, ordered: Ordered = False):
--> 160         self._finalize(categories, ordered, fastpath=False)
    161 
    162     @classmethod

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/dtypes/dtypes.py in _finalize(self, categories, ordered, fastpath)
    312 
    313         if categories is not None:
--> 314             categories = self.validate_categories(categories, fastpath=fastpath)
    315 
    316         self._categories = categories

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/dtypes/dtypes.py in validate_categories(categories, fastpath)
    505         if not fastpath:
    506 
--> 507             if categories.hasnans:
    508                 raise ValueError("Categorical categories cannot be null")
    509 

pandas/_libs/properties.pyx in pandas._libs.properties.CachedProperty.__get__()

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/indexes/base.py in hasnans(self)
   2193         """
   2194         if self._can_hold_na:
-> 2195             return bool(self._isnan.any())
   2196         else:
   2197             return False

pandas/_libs/properties.pyx in pandas._libs.properties.CachedProperty.__get__()

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/indexes/base.py in _isnan(self)
   2172         """
   2173         if self._can_hold_na:
-> 2174             return isna(self)
   2175         else:
   2176             # shouldn't reach to this condition by checking hasnans beforehand

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/dtypes/missing.py in isna(obj)
    125     Name: 1, dtype: bool
    126     """
--> 127     return _isna(obj)
    128 
    129 

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/dtypes/missing.py in _isna(obj, inf_as_na)
    154     # hack (for now) because MI registers as ndarray
    155     elif isinstance(obj, ABCMultiIndex):
--> 156         raise NotImplementedError("isna is not defined for MultiIndex")
    157     elif isinstance(obj, type):
    158         return False

NotImplementedError: isna is not defined for MultiIndex
```

</details>

I don't get an error from this on master, but I do get these warnings

```
*c* argument looks like a single numeric RGB or RGBA sequence, which should be avoided as value-mapping will have precedence in case its length matches with *x* & *y*.  Please use the *color* keyword-argument or provide a 2D array with a single row if you intend to specify the same RGB or RGBA value for all points.
*c* argument looks like a single numeric RGB or RGBA sequence, which should be avoided as value-mapping will have precedence in case its length matches with *x* & *y*.  Please use the *color* keyword-argument or provide a 2D array with a single row if you intend to specify the same RGB or RGBA value for all points.
*c* argument looks like a single numeric RGB or RGBA sequence, which should be avoided as value-mapping will have precedence in case its length matches with *x* & *y*.  Please use the *color* keyword-argument or provide a 2D array with a single row if you intend to specify the same RGB or RGBA value for all points.
```

No differences between pandas, seaborn, or matplotlib versions between these environments. Seems like it's sorta fixed on master. I think this might be a cause:

```python
import scanpy as sc
import seaborn as sns
sns.set()  # <--- here

pbmc = sc.datasets.pbmc68k_reduced()
sc.pl.umap(pbmc, color = 'phase') # This errors

pbmc.uns["phase_colors"]
```

```
[(0.2980392156862745, 0.4470588235294118, 0.6901960784313725),
 (0.8666666666666667, 0.5176470588235295, 0.3215686274509804),
 (0.3333333333333333, 0.6588235294117647, 0.40784313725490196)]
```


seaborn.set_theme causing issues with color mapping
_Originally posted by @OnlyBelter in https://github.com/theislab/scanpy/issues/1850#issuecomment-863089065_

> But in the original jupyter notebook which I used to process this .h5ad file (also contains many other steps before this step), I cannot use some specific columns to set parameter color in this function (some columns can be used correctly).

> I think this problem may cause by `seaborn`!
> 
> The following code should reproduce the error:

```python
import scanpy as sc
import seaborn as sns
sns.set()  # <--- here

pbmc = sc.datasets.pbmc68k_reduced()
sc.pl.umap(pbmc, color = 'phase')
```

-------------------------------

On current release this errors:

<details>
<summary> </summary>

```python
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-1-4c43dbe94eaf> in <module>
      4 
      5 pbmc = sc.datasets.pbmc68k_reduced()
----> 6 sc.pl.umap(pbmc, color = 'phase')

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/scanpy/plotting/_tools/scatterplots.py in umap(adata, **kwargs)
    601     If `show==False` a :class:`~matplotlib.axes.Axes` or a list of it.
    602     """
--> 603     return embedding(adata, 'umap', **kwargs)
    604 
    605 

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/scanpy/plotting/_tools/scatterplots.py in embedding(adata, basis, color, gene_symbols, use_raw, sort_order, edges, edges_width, edges_color, neighbors_key, arrows, arrows_kwds, groups, components, layer, projection, scale_factor, color_map, cmap, palette, na_color, na_in_legend, size, frameon, legend_fontsize, legend_fontweight, legend_loc, legend_fontoutline, vmax, vmin, add_outline, outline_width, outline_color, ncols, hspace, wspace, title, show, save, ax, return_fig, **kwargs)
    244             groups=groups,
    245         )
--> 246         color_vector, categorical = _color_vector(
    247             adata,
    248             value_to_plot,

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/scanpy/plotting/_tools/scatterplots.py in _color_vector(adata, values_key, values, palette, na_color)
   1129     else:  # is_categorical_dtype(values)
   1130         color_map = _get_palette(adata, values_key, palette=palette)
-> 1131         color_vector = values.map(color_map).map(to_hex)
   1132 
   1133         # Set color to 'missing color' for all missing values

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/arrays/categorical.py in map(self, mapper)
   1196         new_categories = self.categories.map(mapper)
   1197         try:
-> 1198             return self.from_codes(
   1199                 self._codes.copy(), categories=new_categories, ordered=self.ordered
   1200             )

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/arrays/categorical.py in from_codes(cls, codes, categories, ordered, dtype)
    567         Categories (2, object): ['a' < 'b']
    568         """
--> 569         dtype = CategoricalDtype._from_values_or_dtype(
    570             categories=categories, ordered=ordered, dtype=dtype
    571         )

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/dtypes/dtypes.py in _from_values_or_dtype(cls, values, categories, ordered, dtype)
    271             # Note: This could potentially have categories=None and
    272             # ordered=None.
--> 273             dtype = CategoricalDtype(categories, ordered)
    274 
    275         return dtype

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/dtypes/dtypes.py in __init__(self, categories, ordered)
    158 
    159     def __init__(self, categories=None, ordered: Ordered = False):
--> 160         self._finalize(categories, ordered, fastpath=False)
    161 
    162     @classmethod

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/dtypes/dtypes.py in _finalize(self, categories, ordered, fastpath)
    312 
    313         if categories is not None:
--> 314             categories = self.validate_categories(categories, fastpath=fastpath)
    315 
    316         self._categories = categories

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/dtypes/dtypes.py in validate_categories(categories, fastpath)
    505         if not fastpath:
    506 
--> 507             if categories.hasnans:
    508                 raise ValueError("Categorical categories cannot be null")
    509 

pandas/_libs/properties.pyx in pandas._libs.properties.CachedProperty.__get__()

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/indexes/base.py in hasnans(self)
   2193         """
   2194         if self._can_hold_na:
-> 2195             return bool(self._isnan.any())
   2196         else:
   2197             return False

pandas/_libs/properties.pyx in pandas._libs.properties.CachedProperty.__get__()

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/indexes/base.py in _isnan(self)
   2172         """
   2173         if self._can_hold_na:
-> 2174             return isna(self)
   2175         else:
   2176             # shouldn't reach to this condition by checking hasnans beforehand

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/dtypes/missing.py in isna(obj)
    125     Name: 1, dtype: bool
    126     """
--> 127     return _isna(obj)
    128 
    129 

~/miniconda3/envs/scanpy-forge/lib/python3.8/site-packages/pandas/core/dtypes/missing.py in _isna(obj, inf_as_na)
    154     # hack (for now) because MI registers as ndarray
    155     elif isinstance(obj, ABCMultiIndex):
--> 156         raise NotImplementedError("isna is not defined for MultiIndex")
    157     elif isinstance(obj, type):
    158         return False

NotImplementedError: isna is not defined for MultiIndex
```

</details>

I don't get an error from this on master, but I do get these warnings

```
*c* argument looks like a single numeric RGB or RGBA sequence, which should be avoided as value-mapping will have precedence in case its length matches with *x* & *y*.  Please use the *color* keyword-argument or provide a 2D array with a single row if you intend to specify the same RGB or RGBA value for all points.
*c* argument looks like a single numeric RGB or RGBA sequence, which should be avoided as value-mapping will have precedence in case its length matches with *x* & *y*.  Please use the *color* keyword-argument or provide a 2D array with a single row if you intend to specify the same RGB or RGBA value for all points.
*c* argument looks like a single numeric RGB or RGBA sequence, which should be avoided as value-mapping will have precedence in case its length matches with *x* & *y*.  Please use the *color* keyword-argument or provide a 2D array with a single row if you intend to specify the same RGB or RGBA value for all points.
```

No differences between pandas, seaborn, or matplotlib versions between these environments. Seems like it's sorta fixed on master. I think this might be a cause:

```python
import scanpy as sc
import seaborn as sns
sns.set()  # <--- here

pbmc = sc.datasets.pbmc68k_reduced()
sc.pl.umap(pbmc, color = 'phase') # This errors

pbmc.uns["phase_colors"]
```

```
[(0.2980392156862745, 0.4470588235294118, 0.6901960784313725),
 (0.8666666666666667, 0.5176470588235295, 0.3215686274509804),
 (0.3333333333333333, 0.6588235294117647, 0.40784313725490196)]
```




Discussion:

Resolving patch:
diff --git a/scanpy/plotting/_utils.py b/scanpy/plotting/_utils.py
--- a/scanpy/plotting/_utils.py
+++ b/scanpy/plotting/_utils.py
@@ -447,7 +447,6 @@ def _set_default_colors_for_categorical_obs(adata, value_to_plot):
     -------
     None
     """
-
     categories = adata.obs[value_to_plot].cat.categories
     length = len(categories)
 
@@ -470,7 +469,7 @@ def _set_default_colors_for_categorical_obs(adata, value_to_plot):
                 "'grey' color will be used for all categories."
             )
 
-    adata.uns[value_to_plot + '_colors'] = palette[:length]
+    _set_colors_for_categorical_obs(adata, value_to_plot, palette[:length])
 
 
 def add_colors_for_categorical_sample_annotation(


Test patch:
diff --git a/scanpy/tests/test_plotting.py b/scanpy/tests/test_plotting.py
--- a/scanpy/tests/test_plotting.py
+++ b/scanpy/tests/test_plotting.py
@@ -14,6 +14,7 @@
 import matplotlib as mpl
 import matplotlib.pyplot as plt
 import matplotlib.cm as cm
+import seaborn as sns
 import numpy as np
 import pandas as pd
 from matplotlib.testing.compare import compare_images
@@ -1249,3 +1250,20 @@ def test_groupby_list(image_comparer):
             adata, ['Gata1', 'Gata2'], groupby=['rand_cat', 'cell_type'], swap_axes=True
         )
         save_and_compare_images('master_dotplot_groupby_list_catorder')
+
+
+def test_color_cycler(caplog):
+    # https://github.com/theislab/scanpy/issues/1885
+    import logging
+
+    pbmc = sc.datasets.pbmc68k_reduced()
+    colors = sns.color_palette("deep")
+    cyl = sns.rcmod.cycler('color', sns.color_palette("deep"))
+
+    with caplog.at_level(logging.WARNING):
+        with plt.rc_context({'axes.prop_cycle': cyl, "patch.facecolor": colors[0]}):
+            sc.pl.umap(pbmc, color="phase")
+            plt.show()
+            plt.close()
+
+    assert caplog.text == ""