Skip to content

Feature Idea: Make subplots return an AxisList object instead of a numpy array #9434

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
AKuederle opened this issue Oct 16, 2017 · 16 comments
Closed
Labels
Documentation New feature status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. status: inactive Marked by the “Stale” Github Action

Comments

@AKuederle
Copy link

Hey,

This is a short idea I had when creating complex graphs with multiple subplots.
Sometimes you want to apply the same actions to multiple subplots and a way that feels very natural would be something like this:

fig, axs = plt.subplots(2, 2)
axs[:, 0].set_ylabel('test')

This does of course not work, but could be easily be achieved by returning a custom object that wraps a list of axes instances and overwrites the __getitem__ method.
Of course it would need to reimplement all axes methods, but it might be possible to do that by just forwarding all call in a modified __getattribute__ or by automatically creating wrapped classmethods in a metaclass.

With such a custom object it might even be possible to support something like this:

axs[:, 0].set_ylabel(['test1', 'test2'])

If there is interest in such functionality, I would try to build a working prototype.

Other than that, thanks to everybody for all the awesome work you put into matplotlib!
Regards,
Arne

@tacaswell
Copy link
Member

That is a pretty good idea, however I have some back-compatibility concerns as the returned object would have to also be a ndarray subclass (or at least fully duck-typed).

I think the best course of action is to prototype it (maybe dropping the full numpy compatibility). Instead of it being the default return type of subplots, have it a stand-alone class

fig, ax_grid = plt.subplots()
nice_grid = NiceGrid(ax_grid)
nice_grid[:, 1].set_ylabel()

If the prototype goes well we can then sort out if this would be better as a small package on pypi or someplace as a helper in Matplotlib core.

@AKuederle
Copy link
Author

Thank you for your response!

Not making that a default sounds reasonable. I will try to prototype as a small independent package and update here as soon as I've made progress.

@jklymak
Copy link
Member

jklymak commented Oct 16, 2017

Plus one on this idea! I’m not even sure how big a deal it is for backwards compatibility so long as it supports most of the indexing and reshaping ndarray commands.

@anntzer
Copy link
Contributor

anntzer commented Oct 16, 2017

I think this can also help with avoiding the squeeze=False dance.

@AKuederle
Copy link
Author

AKuederle commented Oct 16, 2017 via email

@anntzer
Copy link
Contributor

anntzer commented Oct 16, 2017

Right now if you write fig, axs = plt.subplots(m, n) (as opposed to literal values for m and n) and then later try to use axs[i, j] then this will fail if m == 1 or n == 1 because subplots implicitly squeezes dimensions that are equal to 1; i.e., it returns a 1D array (or not an array at all if m == n == 1 instead of a 2D array). Of course, that behavior makes it possible to write e.g. fig, (ax1, ax2) = plt.subplots(2) or fig, ax = plt.subplots() which is not unreasonable. Right now the safe way around this (unless you are certain that m and n will never be equal to 1 -- I wouldn't count on it) is to use plt.subplots(..., squeeze=False) which always returns a 2D array.

Now, if the AxesList object (however we name it) returned also has whatever methods an Axes has, then you're fine even if you don't pass squeeze=False, because we can decide to always return an AxesList and drop the need for squeeze. Now you always just call whatever method you want on the AxesList (need to fully flesh out the idea as to how indexing would exactly work).

On the other hand, having thought about it again, I think another reasonable (unrelated) option would have been (probably too late to change it...) to have the signature plt.subplots(nrows=None, ncols=None, ...) with None meaning "squeeze this dimension" and 1 meaning "don't squeeze this dimension". In this way, plt.subplots(m, n) always give a 2D array, whereas plt.subplots(2) or plt.subplots(ncols=2) or plt.subplots(None, 2) can be unpacked to (ax1, ax2) (but plt.subplots(1, 2) would return an array of size (1, 2) as one would expect).

@jklymak
Copy link
Member

jklymak commented Oct 16, 2017

But I really like the basic idea here. There is no need for axes to be ndarrays, other than the fact we like them to be iterable as a 2-D array. Being able to do ax[:,1:].set_xticklabel('') would be a lot of fun!

@anntzer
Copy link
Contributor

anntzer commented Oct 16, 2017

On the other hand, note that the desired functionality is already available as (e.g.) plt.setp(axs[:, 0], xticklabels="") (modify accordingly; and yes that capability may not be easily discoverable -- that's why I didn't even think of it initially)... which is really not that much harder to type, and I would consider this an argument against adding what is likely to be non-trivial proxying code...

@jklymak
Copy link
Member

jklymak commented Oct 16, 2017

dc-cover-7hqlji2fu754ccr38i0mr9sal2-20170519113544 medi

@AKuederle
Copy link
Author

Wow I did not know that you could pass multiple objects to plt.setp.
This covers indeed the basic functionality I was missing.
So is there a need for a AxesList object beside the fancy syntax?

@anntzer
Copy link
Contributor

anntzer commented Oct 17, 2017

I would consider this at least an argument against it.

@AKuederle
Copy link
Author

Btw. Is there a good place in the docs were setting properties of multiple axis at once could be shown? I don't think many people are aware of that.

@anntzer
Copy link
Contributor

anntzer commented Oct 17, 2017

It's implicitly (but not very clearly) documented at http://matplotlib.org/gallery/misc/set_and_get.html#sphx-glr-gallery-misc-set-and-get-py (when setp is called on lines).

But yes the docs could use some improvement.

@AKuederle
Copy link
Author

I just realised that we would also need to support nested attributes, like ax.xaxis.grid(). I think this can not be done with plt.setp but would also not be trivial to implement in a AxList class

@github-actions
Copy link

This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

@github-actions github-actions bot added the status: inactive Marked by the “Stale” Github Action label Apr 21, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale May 21, 2023
@timhoffm
Copy link
Member

timhoffm commented May 21, 2023

Superseded by #25937, which in a first step decouples the code from the array implementation while keeping API compatibility. This is reasonable anyway to narrow he API surface. Preventing Tab-completion to list a lot of nonsensical and often not working operation is by itself an improvement. Expanding or modifying behavior will be a second step.

@rcomer rcomer added the status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. label May 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Documentation New feature status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. status: inactive Marked by the “Stale” Github Action
Projects
None yet
Development

No branches or pull requests

7 participants