Repository: scverse/scanpy

Issue:
tl.score_genes does not work with one gene given for scoring
Hi,

I have found that using `sc.api.tl.score_genes()` gives the following error if I input a single gene as gene list:
```
computing score 'score'
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-27-526dfa387800> in <module>()
----> 1 sc.tl.score_genes(adata=adata,gene_list= genes)

~/Documents/Python/scanpy/scanpy/tools/score_genes.py in score_genes(adata, gene_list, ctrl_size, gene_pool, n_bins, score_name, random_state, copy)
     96     gene_list = list(gene_list)
     97 
---> 98     score = np.mean(adata[:, gene_list].X, axis=1) - np.mean(adata[:, control_genes].X, axis=1)
     99     adata.obs[score_name] = pd.Series(np.array(score).ravel(), index=adata.obs_names)
    100 

~/miniconda3/lib/python3.6/site-packages/numpy/core/fromnumeric.py in mean(a, axis, dtype, out, keepdims)
   2904             pass
   2905         else:
-> 2906             return mean(axis=axis, dtype=dtype, out=out, **kwargs)
   2907 
   2908     return _methods._mean(a, axis=axis, dtype=dtype,

~/miniconda3/lib/python3.6/site-packages/numpy/core/_methods.py in _mean(a, axis, dtype, out, keepdims)
     55 
     56     is_float16_result = False
---> 57     rcount = _count_reduce_items(arr, axis)
     58     # Make this warning show up first
     59     if rcount == 0:

~/miniconda3/lib/python3.6/site-packages/numpy/core/_methods.py in _count_reduce_items(arr, axis)
     48     items = 1
     49     for ax in axis:
---> 50         items *= arr.shape[ax]
     51     return items
     52 

IndexError: tuple index out of range
```
I suggest that you include a check for the length of the input `gene_list`. 


Discussion:

Resolving patch:
diff --git a/scanpy/tools/score_genes.py b/scanpy/tools/score_genes.py
--- a/scanpy/tools/score_genes.py
+++ b/scanpy/tools/score_genes.py
@@ -81,6 +81,9 @@ def score_genes(
         obs_avg = pd.Series(
             np.nanmean(adata[:, gene_pool].X, axis=0), index=gene_pool)  # average expression of genes
 
+    obs_avg = obs_avg[np.isfinite(obs_avg)] # Sometimes (and I don't know how) missing data may be there, with nansfor
+    gene_pool = list(obs_avg.index)
+
     n_items = int(np.round(len(obs_avg) / (n_bins - 1)))
     obs_cut = obs_avg.rank(method='min') // n_items
     control_genes = set()
@@ -95,7 +98,16 @@ def score_genes(
     control_genes = list(control_genes - gene_list)
     gene_list = list(gene_list)
 
-    score = np.mean(adata[:, gene_list].X, axis=1) - np.mean(adata[:, control_genes].X, axis=1)
+    if len(gene_list) == 0:
+        # We shouldn't even get here, but just in case
+        logg.hint('could not add \n'
+              '    \'{}\', score of gene set (adata.obs)'.format(score_name))
+        return adata if copy else None
+    elif len(gene_list) == 1:
+        score = np.nanmean(adata[:, gene_list].X) - np.nanmean(adata[:, control_genes].X)
+    else:
+        score = np.nanmean(adata[:, gene_list].X, axis=1) - np.nanmean(adata[:, control_genes].X, axis=1)
+
     adata.obs[score_name] = pd.Series(np.array(score).ravel(), index=adata.obs_names)
 
     logg.info('    finished', time=True, end=' ' if settings.verbosity > 2 else '\n')


Test patch: