Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Some subplot axes not displaying when using both gridspec_kw/width_ratios and subplot2grid #11434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
tiffanygwilson opened this issue Jun 13, 2018 · 13 comments

Comments

@tiffanygwilson
Copy link

Bug report

Bug summary

When using a combination of plt.subplots with gridspec_kw/width_ratios along with plt.subplot2grid, some of the axes disappear when one of the width ratios is (in this case) below 0.73.

Code for reproduction

import matplotlib.pyplot as plt
fig, ax = plt.subplots(2,3,sharex=True, sharey=True, gridspec_kw={'width_ratios':[1,1,0.7]})
axi = plt.subplot2grid((2,3),(0,2),rowspan=2)
plt.show()

Actual outcome

image

Note that the axes only disappear with the call to subplot2grid. If I remove that line, all axes appear.

Expected outcome

If width_ratios is instead [1,1,0.8], all of the axes appear.
image

I can achieve the desired width ratios if I use GridSpec directly instead of using the wrappers, but it would be nice if the wrappers didn't fail based on the width_ratios parameter.

Matplotlib version

  • Operating system: Windows 10.0.15063
  • Matplotlib version: 2.1.0
  • Matplotlib backend: module://ipykernel.pylab.backend_inline
  • Python version: 3.6.3
  • IPython version: 6.1.0
  • Jupyter notebook server version: 5.4.1

mabplotlib and python installed via conda.

@tiffanygwilson
Copy link
Author

The problem is that the subplot2grid call is based on the entire figure and doesn't know about the width ratios prescribed in gridspec_kw, so when subplot2grid creates a new virtual grid, the new axes end up overlapping some of the original axes, causing them to disappear. After posting on stack overflow, @ImportanceOfBeingErnest posted this issue that covers this problem. Perhaps there should be a gridspec_kw option for subplot2grid or another way to force it to follow already specified widths. I could then avoid having to calculate the subplot2grid parameters so make everything line up.

@ImportanceOfBeingErnest
Copy link
Member

One could of course add the gridspec in use to the returned values from subplots. I.e. add an argument
subplots(...., return_gs=None) and add something like

if return_gs:
    return fig, axarr, gs
else
    return fig, axarr

@ImportanceOfBeingErnest
Copy link
Member

Just to summarize here for others: The wish here is to not needing to use a GridSpec and add all subplots like add_subplot(gs[n,m], sharex=ax1) and repeat such lines 20 times for a 5 by 4 gridspec, but at the same time still be able to add a column or row spanning plot somewhere in the grid and using unequal width or height ratios.

This reminds me of @jklymak saying

OK, due to popular demand, I added figure._gridspecs as a private list of gridspecs added using add_gridspec. We can add convenience functions for getting the list if and when the need arises.

I guess the time has come, the first request for such function is there. ;-)

@jklymak
Copy link
Member

jklymak commented Jun 13, 2018

Right, best not to mix the subplot and subplot2grid paradigm with the subplots and gridspec paradigm. Annoying, but they are pretty different.

You can do gs = ax.get_subplotspec().get_gridspec() to get the gridspec for any axes object.

import matplotlib.pyplot as plt
fig, axs = plt.subplots(2,3,sharex=True, sharey=True, gridspec_kw={'width_ratios':[1,1,0.7]})
gs = axs[0, 0].get_subplotspec().get_gridspec()
ax = fig.add_subplot(gs[:, -1])
axs[0, -1].remove()
axs[1, -1].remove()
plt.show()

A bit clunky, but works...

@tiffanygwilson
Copy link
Author

I think it would be nice to have a clean way to do this instead of a workaround. Building off the first comment from @ImportanceOfBeingErnest, it could be something like:

fig, axes, gs = plt.subplots(2,3,sharex=True, sharey=True, gridspec_kw={'width_ratios':[1,1,0.7]}, return_gs=True)
axi = plt.subplot2grid(geometry=gs, loc=(0,2), rowspan=2)

where geometry=gs would supply the shape parameter (and supersede if one is given anyway) and make the axes the right size.

@tacaswell tacaswell added this to the v3.0 milestone Jun 13, 2018
@jklymak
Copy link
Member

jklymak commented Jun 13, 2018

https://matplotlib.org/tutorials/intermediate/gridspec.html#sphx-glr-tutorials-intermediate-gridspec-py is the clean way to do this right now. If someone wants to try and add a 'gridspec' argument to subplot2grid that might work - its been a while since I looked at that code. I think right now it makes a new gridspec for each call, which is a mess organizationally.

@tiffanygwilson
Copy link
Author

In that tutorial, fig4 comes the closest, but several things come to mind.

  • Call me lazy, but I still don't like the idea of having to use loops to create all of the axes when subplots does it in one line.

  • If you want to also span rows/columns (as is my goal here) you have to do another call to add_subplot outside the loop used to create the non-spanning axes (e.g. axi = fig4.add_subplot(spec4[:,2]).

  • As far as I can tell, without subplots you can only get sharex and sharey functionality by manually adding it to each add_subplot call, complicating matters even further.

I would offer to look into the addition of gridspec-type argument to subplot2grid myself, but I am fairly new to python/matplotlib and not yet at the level of tinkering with source code.

@jklymak
Copy link
Member

jklymak commented Jun 14, 2018

I'm trying to think of the best API for this.

  1. I think it would be possible to pass a gridspec to subplot2grid instead of the first tuple i.e.
    plt.subplot2grid(gs, (0,2),rowspan=2) instead of plt.subplot2grid((2, 3), (0,2),rowspan=2).
    • doing it that way would allow subplot2grid to work with constrained_layout properly as well, at least when called with a gridspec.
    • ideally, subplot2grid and subplot would always check if an existing gridspec with the right geometry exists and use that gridspec instead of creating a new gridspec each time they are called.
    • there is still the question of clobbering the underlying axes. do the covered axes get removed in fig, axs = plt.subplots(3, 2); plt.subplot2grid(gs, (0,2),rowspan=2) or do they stay?
  2. we could make a fig.axes_combine(list) which would make a big axes that explicitly clobbers the list of supplied axes and makes a big axes that covers the outer geometry of the supplied axes. i.e. fig, axs = plt.subplots(3, 2); fig.axes_combine(axs[:, -1])
    • I prefer this to monkeying with subplot2grid just because it doesn't involve any back-compatibility issues and I strongly prefer the indexing API to the clunky subplot2grid colspan/rowspan API.
    • the only disadvantage/ambiguity is that if the list doesn't include every covered axes. Do we still clobber those covered axes or do we assume the user knows what they are doing. (I strongly lean towards the latter).

@tiffanygwilson
Copy link
Author

tiffanygwilson commented Jun 14, 2018

I love the idea of implementing fig.axes_combine() and then not having to deal with subplot2grid() or add_subplot at all!

Say we have fig, axs = plt.subplots(2,3) which essentially gives us axs = [[ax00, ax01, ax02], [ax10, ax11, ax12]].

Does fig.axes_combine(axs[:,-1]) remove ax01 and ax02 so axs = [[ax00, ax01, NaN], [ax10, ax11, NaN]], does it replace them with a duplicate reference to the larger axes, yielding axs = [[ax00, ax01, ax2], [ax10, ax11, ax2]], or does it leave them untouched?

I bring this up thinking about multiple calls to fig.axes_combine() and references to the new axes. With the first and third options, the new method would need to return a new axis object (i.e. ax2 = fig.axes_combine(axs[:,-1])) so one can then call something like ax2.plot(), right? It would also prohibit another fig.axes_combine() call that involves the last column, going with assuming the user knows what they are doing. The second option would allow one to access the new spanning axis by either axs[0,2] or axs[1,2]. It may be ambiguous to have two references to the same axes in the array, but it also allows all axis objects to exist within the original array.

Regarding the disadvantage/ambiguity, I think a covered axis that is not in the list should not be clobbered. When the plot shows up, it will likely be easier for the user should to figure out why axes are overlapping than to figure out why axes suddenly disappeared. As a side benefit, leaving non-listed axes as-is gives one the opportunity to make neat (if not confusing) axis-within-axis plots using the fig.axes_combine() method.

@ImportanceOfBeingErnest
Copy link
Member

To me it looks a lot like fig.axes_combine() is pretty hard to implement correctly and unambiguously. The number of edge cases to consider are enormous. Like what would fig.axes_combine([axs[0,0],axs[1,1]]) or fig.axes_combine([axs[0,0],axs[3,0]]) give? What happens to the axes below the new axes? Would the new axes inherit anything from the old? What about combining shared axes?

In general my point of view is that axes should never get removed. Given that there apperently is a good way of obtaining the gridspec of an axes via gs = ax.get_subplotspec().get_gridspec() I guess the main point is to document that clearly, probably inside the GridSpec tutorial as well.

@jklymak
Copy link
Member

jklymak commented Jun 14, 2018

Right, well if axes are never removed (unless the user removes them), then there is little point in combine_axes; rather, it'd make sense for me for us to supply a ax.get_gridspec() as a short cut to ax.get_subplotspec().get_gridspec() since the gridspec is unique anyhow.

OTOH, the use cases aren't that hard. Any included axes are removed; the extent of the new axes is the same as the extent of the listed axes; the new axes would be a new axes with no sharing etc except maybe by kwarg. i.e it'd just be add_subplot; which hmmm, maybe means this could just be add_subplot with a different argument list.

@jklymak
Copy link
Member

jklymak commented Jun 14, 2018

See #11438

@jklymak
Copy link
Member

jklymak commented Jul 7, 2018

Closing because #11438 was merged. OTOH we can reopen if folks think the conveneoence method isn’t enough.

@jklymak jklymak closed this as completed Jul 7, 2018
@QuLogic QuLogic removed this from the v3.0 milestone Jul 8, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants