Skip to content

AxesGrid ticks missing on x-axis #11025

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
egpbos opened this issue Apr 11, 2018 · 32 comments
Closed

AxesGrid ticks missing on x-axis #11025

egpbos opened this issue Apr 11, 2018 · 32 comments

Comments

@egpbos
Copy link
Contributor

egpbos commented Apr 11, 2018

Bug report

Bug summary

There seem to be missing ticks on the x-axis for AxesGrids. Looking at the examples in the documentation, this bug seems to have been introduced since version 2.0.0. Compare v2.0.0 to v1.5.1 for instance.

Code for reproduction

The first example in the docs that shows this problem is:

import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
import numpy as np

im = np.arange(100)
im.shape = 10, 10

fig = plt.figure(1, (4., 4.))
grid = ImageGrid(fig, 111,  # similar to subplot(111)
                 nrows_ncols=(2, 2),  # creates 2x2 grid of axes
                 axes_pad=0.1,  # pad between axes in inch.
                 )

for i in range(4):
    grid[i].imshow(im)  # The AxesGrid object work as a list of axes.

plt.show()

Actual outcome

Outcome for v2.0.0 and higher: https://matplotlib.org/2.0.0/_images/simple_axesgrid1.png

Expected outcome

Outcome for v1.5.1 and before: https://matplotlib.org/1.5.1/_images/simple_axesgrid1.png

Matplotlib version

Seeing as this happens on the matplotlib doc pages themselves, I think we can be pretty sure it's platform / build-system / version (except v1 vs v2) independent.

@egpbos
Copy link
Contributor Author

egpbos commented Apr 11, 2018

Possibly related to #7378?

@jklymak
Copy link
Member

jklymak commented Apr 11, 2018

Thanks for the bug report.

axes_grid1 is somewhat spottily maintained (hence its in a toolkit). Hopefully someone will fix it. However, if you need a workaround, let us know.

@jklymak jklymak added this to the v3.0 milestone Apr 11, 2018
@egpbos
Copy link
Contributor Author

egpbos commented Apr 11, 2018

I would like a workaround, yes. I'm also not afraid of digging in and trying to solve it, but I just wanted to check first what the status is, whether others have already looked into it, whether a fix is at all appreciated, etc.

@jklymak
Copy link
Member

jklymak commented Apr 11, 2018

Fig.Subplots is the most straightforward workaround.

@fredrik-1
Copy link
Contributor

fredrik-1 commented Apr 11, 2018

Works for me in 2.2.2 and the documentation for that version also seems ok (the only difference from the old versions plot are the direction of the ticks)

@egpbos
Copy link
Contributor Author

egpbos commented Apr 11, 2018

@fredrik-1 compare the x-axis ticks in 1.5.1 to those in 2.2.2. The number of ticks is not right in 2.2.2, a lot are missing. This is how it should be (1.5.1, ignore the color scheme):
Image v1.5.1
This is how it now is (2.0.0, but 2.2.2 is exactly the same):
Image v2.0.0
As you can see, the x-axis ticks are different, not only are they spaced differently (and with way too much distance between them), different values have been selected (5 instead of 2,4,6,8). This must have been unintended.

@egpbos
Copy link
Contributor Author

egpbos commented Apr 11, 2018

@jklymak Ok, I thought you were thinking of a workaround using AxesGrid. Using regular subplots is not really an option for me, as that would require me to essentially write a new axisgrid toolkit from scratch :)

@fredrik-1
Copy link
Contributor

egbos, I understand the problem but I don't see it when I run the code myself using 2.2.2 and I don't see it in the 2.2.2 documentation. The same ticks in all xaxis

@egpbos
Copy link
Contributor Author

egpbos commented Apr 11, 2018

@fredrik-1 The problem is not a different number of ticks on different xaxis'es, it's the fact that it has only 0 and 5 on it instead of 0, 2, 4, 6 and 8 as it had before.

@fredrik-1
Copy link
Contributor

fredrik-1 commented Apr 11, 2018

I get 0, 2, 4, 6 and 8 when I make the figure larger.

I thought that the problem where that there where more x-tics in the upper plots than in the lower plots. That is probably a bug in some versions of matplotlib. The number of ticks depends on the figure size and I don't see any problem with the behavior I see now.

@egpbos
Copy link
Contributor Author

egpbos commented Apr 11, 2018

Making the figure larger is not a fix for the broken default. When running plots non-interactively (saving to file from a script), it is not an option to resize the figure interactively until it works. Also, interactivity is not reproducible. In any case, even though you don't see any problem, let's agree that it is a change from the v1 default behavior and that I do see this as a problem, since it changes plot scripts from 1.5.1, which is unintended and non-desired behavior.

One interesting point you raise is that the upper plot ticks indeed do seem correct... Perhaps this is a hint in the direction of a fix.

@fredrik-1
Copy link
Contributor

fredrik-1 commented Apr 11, 2018

Ok, so the problem is that you think that the x and y ticks should always be the same? I guess that is good in some applications but not in all.

edit: I deleted my example because I forgot that imshow show data points and not data values on the axis...

@egpbos
Copy link
Contributor Author

egpbos commented Apr 11, 2018

That's probably because the extent kwarg is missing from the imshow call. Indeed, though, when you add this (change e.g. to grid[i].imshow(im, extent=(0, N*N, 0, N*N))), it seems to show that the x and y axes use different tick formatters. For me, it gives 1 decimal on the y-axis and 2 on the x-axis.

@egpbos
Copy link
Contributor Author

egpbos commented Apr 11, 2018

But to be clear, I don't need the x and y ticks to always be the same. In my particular application this is actually what I want, but I can imagine other cases where I'd like it differently. What I want is for the default behavior of mpl v1's axesgrid to return, i.e. that the same formatters/locators are used for the ticks (or at least that's what I assume has changed, I may be mistaken).

@egpbos
Copy link
Contributor Author

egpbos commented Apr 11, 2018

Perhaps what changed is the maximum width of a label in order to prevent overlapping labels? This would explain why the ticks return when you make the figure larger. How to change this maximum width though?

@ImportanceOfBeingErnest
Copy link
Member

ImportanceOfBeingErnest commented Apr 11, 2018

In general it seems there are several things mixed up in this issue.

Number of ticks for single axis

The number of ticks shown is determined by the matplotlib.ticker.AutoLocator. There was a change between v1.5 and v2.0. This change was deliberate. Read about it in the changes to the default style:

The default Locator used for the x and y axis is AutoLocator which tries to find, up to some maximum number, ‘nicely’ spaced ticks. The locator now includes an algorithm to estimate the maximum number of ticks that will leave room for the tick labels. By default it also ensures that there are at least two ticks visible.

There is no way, other than using mpl.style.use('classic'), to restore the previous behavior as the default. On an axis-by-axis basis you may either control the existing locator via:

     ax.xaxis.get_major_locator().set_params(nbins=9, steps=[1, 2, 5, 10])

or create a new MaxNLocator:

    import matplotlib.ticker as mticker
    ax.set_major_locator(mticker.MaxNLocator(nbins=9, steps=[1, 2, 5, 10])

The algorithm used by MaxNLocator has been improved, and this may change the choice of tick locations in some cases. This also affects AutoLocator, which uses MaxNLocator internally.

So in the case of the example this would look like

for i in range(4):
    grid[i].imshow(im)  # The AxesGrid object work as a list of axes.
    grid[i].xaxis.get_major_locator().set_params(nbins=9, steps=[1, 2, 5, 10])

resulting in

image

Number of ticks for different subplots

The other issue is the differing number of ticks between the upper and lower subplot which you observe. I cannot reproduce this. When running the above code or other codes I always see the same number of ticks on all subplots with matplotlib 2.2.2.
The example from the current documentation looks like this:
image
where subplots have the same number of ticks.
Any kind of further information you can provide on how you create that plot would be helpful here (Note that for issues there is usually a template which should be filled in that queries those things.)

@egpbos
Copy link
Contributor Author

egpbos commented Apr 11, 2018

Thanks for that info @ImportanceOfBeingErnest. I will use set_params as a work around in my code.

On the same page, next paragraph, it says:

As in the case of the AutoLocator, the heuristic algorithm reduces the incidence of overlapping tick labels but does not prevent it.

So, I guess the heuristic could be improved. Indeed, for the example shown on that page (https://matplotlib.org/_images/dflt_style_changes-18.png), the new behavior is clearly much better, but in the case presented here, with such small labels, I would say it is not. I guess this would be a different issue. @ImportanceOfBeingErnest what are your thoughts on this? Is it worth pursuing an improved spacing algorithm or has everything been tried and is this the best we can do?

@jklymak
Copy link
Member

jklymak commented Apr 11, 2018

Using regular subplots is not really an option for me, as that would require me to essentially write a new axisgrid toolkit from scratch :)

Just out of curiosity whats does axes_grid1 do that core can't?

@jklymak
Copy link
Member

jklymak commented Apr 11, 2018

So, I guess the heuristic could be improved. Indeed, for the example shown on that page

The heuristic is 3*fontsize. Not very sophisticated.

@egpbos
Copy link
Contributor Author

egpbos commented Apr 11, 2018

Just out of curiosity whats does axes_grid1 do that core can't?

axes_grid1 automates things like synchronizing axis ticks between different subplots, hiding them on the inner axes, giving nice defaults for subplot layout, etc. I assume core can do anything, but last time I checked (2015-16) it took considerably more manual effort to do these things with plain subplots. I don't know whether that is still the case.

heuristic

I was making a small PR just now to maybe spark some discussion :)

@jklymak
Copy link
Member

jklymak commented Apr 11, 2018

Ok but just be aware that axes_grid1 is poorly maintained and documented. It seems to me you could write a three line wrapper to subplots to do all the above. You also miss out on automated layout like constrained_layout.

@egpbos
Copy link
Contributor Author

egpbos commented Apr 11, 2018

Oh, I didn't know about constrained_layout, thanks for the tip!

@ImportanceOfBeingErnest
Copy link
Member

The advantage of the axes_grid is that it positions figures in a gridded way (hence the name I suppose).

image

You will need a lot more thinking and a lot more lines than 3 to get the same with subplots.

@jklymak
Copy link
Member

jklymak commented Apr 11, 2018

@ImportanceOfBeingErnest yes, fair enough, making different sized axes all have aspect equal is a bit if a chore. But, very curious how often people actually need to make plots like this, and in what fields.

For the more usual cases of four same-sized axes, or two different height rows, thats pretty straight forward w/ usual tools.

@jklymak
Copy link
Member

jklymak commented Apr 11, 2018

https://matplotlib.org/gallery/axes_grid1/simple_axesgrid.html#sphx-glr-gallery-axes-grid1-simple-axesgrid-py doesn't have the reported problem, and playing w/ it manually I cannot reproduce. Can we close this?

@egpbos
Copy link
Contributor Author

egpbos commented Apr 12, 2018

But, very curious how often people actually need to make plots like this, and in what fields.

I personally make heavy use of these kinds of plots to compare images of density fields, i.e. slices out of 3D grids. These kinds of gridded field image plots are very typical in astrophysical simulations and I expect in related fields like hydrodynamics, climate science, plasma physics, etc. as well. When comparing such slices, having clean, nicely aligned plots is critical. The noise in the image needs to be minimal and scales need to be comparable. To have this all automated in one package I think must have been the original reason to build it and imho is a reason something like this should continue to exist (whether in axesgrid1 or anywhere else in mpl). Constrained layout looks great, but it still lacks the super convenient gridding options that axesgrid1 offers. Perhaps soon it can be replaced, but not yet, I think :)

https://matplotlib.org/gallery/axes_grid1/simple_axesgrid.html#sphx-glr-gallery-axes-grid1-simple-axesgrid-py doesn't have the reported problem

It does have the problem, it displays only 0 and 5, whereas imo it should display more numbers. The workaround above (specifying the locator parameters manually) works, but I still think the default behavior caused by the XAxis tick_space heuristic (3*tick-label-fontsize) is suboptimal. This makes perfect sense, because the heuristic is based on the assumption that the aspect ratio of a label is about 1:3 (h:w), whereas when your labels are only single digits (as in the example you show), this estimate is relatively far off, it's more like 1:1. This means that 2*tick-label-fontsize is a lot better (which happens to be the heuristic used for the YAxis).

This line of thought led me to #11027.

@jklymak
Copy link
Member

jklymak commented Apr 12, 2018

@EGPOS I work with gridded data sets all the time. I’m still at a loss as to what you are trying to do that requires the grid aspect ratios be one-to-one, have different limits, but ones that lineup Can you point me to a published real-world example so I can see what you mean? Because the plot made above by @ImportanceOfBeingEarnest seems hard to make sense of the application.

@egpbos
Copy link
Contributor Author

egpbos commented Apr 12, 2018

Aspect ratio doesn't have to be 1:1, that's one of the parameters of axesgrid1. I'm currently working on a paper where I use this (not yet published, sorry ;) ), which is based on my PhD thesis. There I use it a lot.

@jklymak
Copy link
Member

jklymak commented Apr 12, 2018

I have to download your whole thesis? 😥. How about a couple of examples here?

@egpbos
Copy link
Contributor Author

egpbos commented Apr 12, 2018

No problem :) The most recent figure I was working on (which prompted me to post this issue and do PR #11027) is this one:
schermafbeelding 2018-04-12 om 08 48 56
I fixed the x-axis ticks here manually using the workaround suggested by @ImportanceOfBeingErnest (ax.xaxis.get_major_locator().set_params(nbins=9, steps=[1, 2, 5, 10])). That has to be done 3 times, for each subplot, whereas normally axesgrid takes care of such synchronization for me, but ok, still doable.

Another example:
schermafbeelding 2018-04-12 om 08 51 53
This one didn't have any issues though, but just to show you the kinds of plots I use axisgrid1 for.

@egpbos
Copy link
Contributor Author

egpbos commented Apr 12, 2018

Notice that these simulations have periodic boundary conditions. Having the figures next to each other as close as possible really helps in comparing the structures in different realizations of the same constrained simulation.

@tacaswell tacaswell modified the milestones: v3.0, v3.1 Aug 11, 2018
@jklymak
Copy link
Member

jklymak commented Feb 11, 2019

Closing as this is requesting a change to the default parameters, and we aren't too likely to change those.

Note that

import matplotlib.pyplot as plt
import numpy as np

fig, axs = plt.subplots(2, 2, sharex=True, sharey=True,
                        constrained_layout=True);
fig.set_constrained_layout_pads(w_pad=0./72., h_pad=0./72.,
        hspace=0., wspace=0.)
for i in range(2):
    ax = axs[0, i]
    pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='RdBu_r')
fig.colorbar(pcm, ax=axs[0,:])
for i in range(2):
    ax = axs[1, i]
    pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis_r')
fig.colorbar(pcm, ax=axs[1,:])
plt.show()

Gives essentially what you want above. Where main matplotlib fails, and requires axes_grid1 is when you want the data aspect ratio to be one in the plots.

@jklymak jklymak closed this as completed Feb 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants