Repository: scverse/scanpy

Issue:
`clustermap` with sparse matrix throwing value error
Hey,

Was just trying to plot a cluster map when I ran into an error:

```python
import scanpy.api as sc
from scipy import sparse
A = sparse.rand(100, 100, 0.1, "csr")

# This works
sc.pl.clustermap(sc.AnnData(A.toarray()))

# This throws an error
sc.pl.clustermap(sc.AnnData(sparse.rand(100, 100, 0.1, "csr")))
```

<details>
<summary> Traceback: </summary>

```python
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-13-f65606c38b22> in <module>
----> 1 sc.pl.clustermap(sc.AnnData(A))

~/github/scanpy/scanpy/plotting/anndata.py in clustermap(adata, obs_keys, use_raw, show, save, **kwds)
    750         g = sns.clustermap(df, row_colors=row_colors, **kwds)
    751     else:
--> 752         g = sns.clustermap(df, **kwds)
    753     show = settings.autoshow if show is None else show
    754     if show: pl.show()

/usr/local/lib/python3.6/site-packages/seaborn/matrix.py in clustermap(data, pivot_kws, method, metric, z_score, standard_scale, figsize, cbar_kws, row_cluster, col_cluster, row_linkage, col_linkage, row_colors, col_colors, mask, **kwargs)
   1299                         row_cluster=row_cluster, col_cluster=col_cluster,
   1300                         row_linkage=row_linkage, col_linkage=col_linkage,
-> 1301                         **kwargs)

/usr/local/lib/python3.6/site-packages/seaborn/matrix.py in plot(self, metric, method, colorbar_kws, row_cluster, col_cluster, row_linkage, col_linkage, **kws)
   1126         colorbar_kws = {} if colorbar_kws is None else colorbar_kws
   1127         self.plot_dendrograms(row_cluster, col_cluster, metric, method,
-> 1128                               row_linkage=row_linkage, col_linkage=col_linkage)
   1129         try:
   1130             xind = self.dendrogram_col.reordered_ind

/usr/local/lib/python3.6/site-packages/seaborn/matrix.py in plot_dendrograms(self, row_cluster, col_cluster, metric, method, row_linkage, col_linkage)
   1019             self.dendrogram_row = dendrogram(
   1020                 self.data2d, metric=metric, method=method, label=False, axis=0,
-> 1021                 ax=self.ax_row_dendrogram, rotate=True, linkage=row_linkage)
   1022         else:
   1023             self.ax_row_dendrogram.set_xticks([])

/usr/local/lib/python3.6/site-packages/seaborn/matrix.py in dendrogram(data, linkage, axis, label, metric, method, rotate, ax)
    745     plotter = _DendrogramPlotter(data, linkage=linkage, axis=axis,
    746                                  metric=metric, method=method,
--> 747                                  label=label, rotate=rotate)
    748     if ax is None:
    749         ax = plt.gca()

/usr/local/lib/python3.6/site-packages/seaborn/matrix.py in __init__(self, data, linkage, metric, method, axis, label, rotate)
    562 
    563         if linkage is None:
--> 564             self.linkage = self.calculated_linkage
    565         else:
    566             self.linkage = linkage

/usr/local/lib/python3.6/site-packages/seaborn/matrix.py in calculated_linkage(self)
    624     def calculated_linkage(self):
    625         try:
--> 626             return self._calculate_linkage_fastcluster()
    627         except ImportError:
    628             return self._calculate_linkage_scipy()

/usr/local/lib/python3.6/site-packages/seaborn/matrix.py in _calculate_linkage_fastcluster(self)
    618         else:
    619             linkage = fastcluster.linkage(self.array, method=self.method,
--> 620                                           metric=self.metric)
    621             return linkage
    622 

/usr/local/lib/python3.6/site-packages/fastcluster.py in linkage(X, method, metric, preserve_input)
    241         assert X.ndim==2
    242         N = len(X)
--> 243         X = pdist(X, metric)
    244         X = array(X, dtype=double, copy=False, order='C', subok=True)
    245     Z = empty((N-1,4))

/usr/local/lib/python3.6/site-packages/scipy/spatial/distance.py in pdist(X, metric, *args, **kwargs)
   1932         if metric_name is not None:
   1933             X, typ, kwargs = _validate_pdist_input(X, m, n,
-> 1934                                                    metric_name, **kwargs)
   1935 
   1936             # get pdist wrapper

/usr/local/lib/python3.6/site-packages/scipy/spatial/distance.py in _validate_pdist_input(X, m, n, metric_name, **kwargs)
    287         typ = types[types.index(X.dtype)] if X.dtype in types else types[0]
    288         # validate data
--> 289         X = _convert_to_type(X, out_type=typ)
    290 
    291         # validate kwargs

/usr/local/lib/python3.6/site-packages/scipy/spatial/distance.py in _convert_to_type(X, out_type)
    182 
    183 def _convert_to_type(X, out_type):
--> 184     return np.ascontiguousarray(X, dtype=out_type)
    185 
    186 

/usr/local/lib/python3.6/site-packages/numpy/core/numeric.py in ascontiguousarray(a, dtype)
    588 
    589     """
--> 590     return array(a, dtype, copy=False, order='C', ndmin=1)
    591 
    592 

ValueError: setting an array element with a sequence.
```

</details>


-------------------

I think this is happening because `pandas` doesn't like being passed a sparse array ([which can happen in `sc.pl.clustermap`](https://github.com/theislab/scanpy/blob/7de1f5159c91d6d2243bb7866d9495ee6747c750/scanpy/plotting/anndata.py#L741)):

```python
import pandas as pd
adata = sc.AnnData(A) # from above
pd.DataFrame(A) # Throws error
pd.DataFrame(adata.X, index=adata.obs_name, columns=adata.var_name).head() #  returns malformed dataframe
                                                   0                        ...                                                                         99
0    (0, 9)\t0.76754415\n  (0, 28)\t0.16313511\n ...                        ...                            (0, 9)\t0.76754415\n  (0, 28)\t0.16313511\n ...
1    (0, 9)\t0.76754415\n  (0, 28)\t0.16313511\n ...                        ...                            (0, 9)\t0.76754415\n  (0, 28)\t0.16313511\n ...
2    (0, 9)\t0.76754415\n  (0, 28)\t0.16313511\n ...                        ...                            (0, 9)\t0.76754415\n  (0, 28)\t0.16313511\n ...
3    (0, 9)\t0.76754415\n  (0, 28)\t0.16313511\n ...                        ...                            (0, 9)\t0.76754415\n  (0, 28)\t0.16313511\n ...
4    (0, 9)\t0.76754415\n  (0, 28)\t0.16313511\n ...                        ...                            (0, 9)\t0.76754415\n  (0, 28)\t0.16313511\n ...

[5 rows x 100 columns]

```


Discussion:

Resolving patch:
diff --git a/scanpy/plotting/anndata.py b/scanpy/plotting/anndata.py
--- a/scanpy/plotting/anndata.py
+++ b/scanpy/plotting/anndata.py
@@ -5,6 +5,7 @@
 import numpy as np
 import pandas as pd
 from pandas.api.types import is_categorical_dtype
+from scipy.sparse import issparse
 from matplotlib import pyplot as pl
 from matplotlib import rcParams
 from matplotlib.colors import is_color_like
@@ -738,6 +739,8 @@ def clustermap(
     sanitize_anndata(adata)
     if use_raw is None and adata.raw is not None: use_raw = True
     X = adata.raw.X if use_raw else adata.X
+    if issparse(X):
+        X = X.toarray()
     df = pd.DataFrame(X, index=adata.obs_names, columns=adata.var_names)
     if obs_keys is not None:
         row_colors = adata.obs[obs_keys]


Test patch: