scverse / scverse/scanpy

Legends not accessible via provided axis and misplaced (scatterplot, subplots)

Open
#1,258 10 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Area - Plotting 🌺
Dominant language
Python
Stars
2.6k
Forks
779
Avg merge
1d 4h
Merged PRs (30d)
27

Description

When using sc.pl.scatter() and providing an existing axis object, the legend doesn't always appear correctly and cannot be accessed.
This doesn't seem to happen with a categorial coloring however, only with a continous colormap.

This code works as expected:

sc.pp.calculate_qc_metrics(adata_raw, qc_vars=['mt'], percent_top=None, log1p=False, inplace=True)
sc_fig, (sc_ax1, sc_ax2) = plt.subplots(1,2, figsize=(12,5))
sc.pl.scatter(adata_raw, 'total_counts','n_genes_by_counts', color='batch', size = 10, ax=sc_ax1, show=False, title="all counts")
sc_ax1.get_legend().remove()
sc.pl.scatter(adata_raw[adata_raw.obs['total_counts']<1000],'total_counts','n_genes_by_counts', color='batch', size = 10, ax=sc_ax2, show=False, title="< 1000 counts")
plt.show()

It creates some metrics and stores them in adata_raw.obs, then plots these metrics for all counts and for counts < 1000 on the two axes created by plt.subplots(). The legend from the first axis is then removed.
This is an example of this output:
image

Now the code that doesn't work:

sc_fig, (sc_ax1, sc_ax2) = plt.subplots(1,2, figsize=(12,5))
sc.pl.scatter(adata_raw, 'total_counts','n_genes_by_counts', color='pct_counts_mt', size = 10, ax=sc_ax1, show=False, title="all counts")
#sc_ax1.get_legend().remove()
sc.pl.scatter(adata_raw[adata_raw.obs['total_counts']<1000],'total_counts','n_genes_by_counts', color='pct_counts_mt', size = 10, ax=sc_ax2, show=False, title="< 1000 counts")
plt.show()

Essentially the same thing but colored by the percentage of mitochondrial counts.
Only one legend seems to be drawn and this one is not looking as expected. Plus, I cannot remove the legend from the first plot.
This is how it looks:
image

Why doesn't it behave in the same way like in the example above?
Is there a way I can share the same legend with a scale from 0 to 1 (0%-100%) for both plots in this case?
As you can see, the line removing the legend from sc_ax1 is commented out because get_legend() returns None in this case, which would lead to the error below:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-154-702da93b63cb> in <module>
      2     sc_fig, (sc_ax1, sc_ax2) = plt.subplots(1,2, figsize=(12,5))
      3     sc.pl.scatter(adata_raw, 'total_counts','n_genes_by_counts', color='pct_counts_mt', size = 10, ax=sc_ax1, show=False, title="all counts")
----> 4     sc_ax1.get_legend().remove()
      5     sc.pl.scatter(adata_raw[adata_raw.obs['total_counts']<1000],'total_counts','n_genes_by_counts', color='pct_counts_mt', size = 10, ax=sc_ax2, show=False, title="< 1000 counts")
      6     plt.show()

AttributeError: 'NoneType' object has no attribute 'remove'

Shouldn't the legends be attached to the individual axes objects?
I cannot access them and I wonder where they are stored in this case.

Versions:

scanpy==1.5.2.dev5+ge5d246aa anndata==0.7.1 umap==0.4.3 numpy==1.18.4 scipy==1.4.1 pandas==0.25.3 scikit-learn==0.23.0 statsmodels==0.11.1 python-igraph==0.8.2 louvain==0.7.0
matplotlib==3.1.2

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the sc.pl.scatter() entry point and reproduce the two-subplot example using the versions listed, especially matplotlib 3.1.2. Trace how continuous-colormap legends are created when an existing ax is supplied; done means each legend is accessible from the intended axis and the reported subplot behavior is covered by a regression test.

Written by the indexing model from the issue text.

Assessment

Tech stack
matplotlib, python
Domain
data-visualization
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.