matplotlib imshow centering with disabled axes - python

How can I center a matlotlib imshow figure after disabling the axes? Example:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(5,5))
plt.axis('off')
plt.imshow(np.random.randint(10, size=(100, 100)))
plt.show()
Now the image is not really centered, especially if I apply a tight_layout since it does take the axes into account although they are disabled?!
plt.tight_layout()
Same problem occurs if I e.g. add a colorbar. Of course one could adjust the borders manually by command or in the UI, however, I would prefer a more robust solution that works automatically with different image shapes and sizes. Moreover, the figure size should not be changed during centering. Any hints?

The subplot parameters set by the default rc file are
figure.subplot.left : 0.125 ## the left side of the subplots of the figure
figure.subplot.right : 0.9 ## the right side of the subplots of the figure
figure.subplot.bottom : 0.11 ## the bottom of the subplots of the figure
figure.subplot.top : 0.88 ## the top of the subplots of the figure
As you can see, they are asymmetric. You can set them to something symmetric, if you want
rc = {"figure.subplot.left" : 0.1,
"figure.subplot.right" : 0.9,
"figure.subplot.bottom" : 0.1,
"figure.subplot.top" : 0.9 }
plt.rcParams.update(rc)
or
fig.subplots_adjust(left=0.1, bottom=0.1, right=0.9, top=0.9)
The fact that fig.tight_layout() does not produce a centered plot when the axes are off, is considered as bug. This has been fixed and will be working correctly from matplotlib 3.1 on (which will be released in a couple of days or weeks).

Related

Matplotlib colorbar shows only up to half the tick values

When creating colorbars using the "ColorbarBase" object and adding ticks with the "set_yticklabels" method matplotlib only displays up to half the values specified in the "ticks" list. This only started happening after I upgraded to the latest matplotlib version.
Here is a short code to test it:
import matplotlib.pyplot as plt
import matplotlib as mpl
fig = plt.figure(1)
cax = fig.add_axes([0.8, 0.15, 0.03, 0.72])
ticks = range(11)
cbar = mpl.colorbar.ColorbarBase(cax, cmap='rainbow', orientation='vertical')
cbar.ax.set_yticklabels(ticks)
plt.show()
Using matplotlib version 2.1.0 I get a colorbar with ticks that go from 1 to 5, meanwhile with matplotlib version 1.4.3 I get the correct figure with ticks that go from 1 to 10.
I can do a dirty fix by increasing the tick range by two, but I would prefer this not to happen. Any tips on what could be going on?
I would think that the above code works in matplotlib 1.4 is only coincidence. The point is that you do not tell the colorbar what range it should cover. It would hence cover the range 0 - 1. In matplotlib versions below 2, an axes with range 0 - 1 would have 11 tickmarks (0,0.1,0.2,...); then setting the labels as the range(11) would fit well (0 stays 0, 0.1 is labeled 1, etc.). In newer matplotlib versions the labeling density is by default lower (0,0.2,0.4,...). Hence you only get the label up to 5 (0 stays 0, 0.2 is labeled 1, 0.4 is labeled 2, etc. up to 5). At the end this approach is anyways very fragile, since the tick density is also subject to the size of the axes and might change.
So you would want to tell the colorbar which range it should cover. One way to do that would be to supply a norm.
ticks = range(11)
cbar = mpl.colorbar.ColorbarBase(cax, cmap='rainbow', orientation='vertical',
norm=plt.Normalize(ticks[0],ticks[-1]))
You can then set the ticks and ticklabels to the numbers within ticks.
cbar.set_ticks(ticks)
cbar.ax.set_yticklabels(ticks)
Complete example:
import matplotlib.pyplot as plt
import matplotlib as mpl
fig = plt.figure(1)
cax = fig.add_axes([0.8, 0.15, 0.03, 0.72])
ticks = range(11)
cbar = mpl.colorbar.ColorbarBase(cax, cmap='rainbow', orientation='vertical',
norm=plt.Normalize(ticks[0],ticks[-1]))
cbar.set_ticks(ticks)
cbar.ax.set_yticklabels(ticks)
plt.show()

In matplotlib 2.0, how do I revert colorbar behaviour to that of matplotlib 1.5?

I just upgraded to matplotlib 2.0 and in general, I'm very happy with the new defaults. One thing which I'd like to revert to the 1.5 behaviour is that of plt.colorbar, specifically the yticks. In the old matplotlib, only major ticks were plotted on my colorbars; in the new matplotlib, minor and major ticks are drawn, which I do not want.
Below is shown a comparison of the 1.5 behaviour (left) and the 2.0 behaviour (right) using the same colormap and logarithmic ticks.
What defaults do I need to set in matplotlibrc in order to revert to the 1.5 behaviour shown on the left? If there is no way to do this using matplotlibrc, what other avenues are available for altering this globally beyond downgrading to matplotlib 1.5?
I have tried simply setting cbar.ax.minorticks_off() after every instance of cbar = plt.colorbar(mesh), but that doesn't solve the issue.
It should be sufficient to just set the colorbar locator to a LogLocator from the matplotlib.ticker module, and then call update_ticks() on the colorbar instance.
For example, consider this minimal example which produces the colorbar you are seeing with minor ticks:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import matplotlib.colors as colors
import numpy as np
fig, ax = plt.subplots(1)
# Some random data in your range 1e-26 to 1e-19
data = 10**(-26. + 7. * np.random.rand(10, 10))
p = ax.imshow(data, norm=colors.LogNorm(vmin=1e-26, vmax=1e-19))
cb = fig.colorbar(p, ax=ax)
plt.show()
If we now add the following two lines before calling plt.show(), we remove the minor ticks:
cb.locator = ticker.LogLocator()
cb.update_ticks()
Alternatively, to achieve the same thing, you can use the ticks kwarg when creating the colorbar, and set that to the LogLocator()
cb = fig.colorbar(p, ax=ax, ticks=ticker.LogLocator())

Gridlines that overlap with axes spines have different width from other gridlines

I'm using Seaborn to make some plots using the whitegrid style. After calling despine(), I'm seeing that the gridlines that would overlap with the axes spines have smaller linewidth than the other gridlines. But it seems like this only happens when I save the plots as pdf. I'm sharing
three different figures with different despine configurations that show the effect.
Does anyone know why this occurs? And is there a simple fix?
PDF plot with all spines
PDF plot that despines all axes
PDF plot that despines left, top, and right axes
Code:
splot = sns.boxplot(data=df, palette=color, whis=np.inf, width=0.5, linewidth = 0.5)
splot.set_ylabel('Normalized WS')
plt.xticks(rotation=90)
plt.tight_layout()
sns.despine(left=True, bottom=True)
plt.savefig('test.pdf', bbox_inches='tight')
Essentially what's happening here is that the grid lines are centered on the tick position, so the outer half of the extreme grid lines are not drawn because they extend past the limits of the axes.
One approach is to disable clipping for the grid lines:
import numpy as np
import seaborn as sns
sns.set(style="whitegrid", rc={"grid.linewidth": 5})
x = np.random.randn(100, 6)
ax = sns.boxplot(data=x)
ax.yaxis.grid(True, clip_on=False)
sns.despine(left=True)
My hacking solution now is to not despine the top and bottom axes and make them the same width as the gridlines. This is not ideal. If someone can point out a way to fix the root cause, I will really appreciate that.

Specify the exact size of matplotlib figure

I'm having a hard time to make some of my figures in my paper aligned appropriately.
Here is the problem. I have two figures that I want to use the same x-axis. I plot two figures separately and include them as two subfigures in Latex. The problem with this: the yticklabels of the second graph take more room, which makes it look smaller than the first graph.
I specified the figsize as the same using the following code
fig, ax = plt.subplots(figsize=(6,4))
But obviously when other things like titles, labels, tick labels take more space, the "main plot" appears smaller, making two subfigures misaligned. Is there any way to specify the size of the "main plot" ignoring other labels, titles, etc.?
P.S.: I didn't use matplotlib's subplots function because I noticed a bug in pandas or matplotlib: whenever I use an inset axis, the xlabel and xticklabels will not show. So I have to get around this by plotting two figures respectively and include them as two subfigures in latex.
When I am faced with this situation, I simply hard code the axes placement on the figure, then things will line up...
import matplotlib.pyplot as plt
ax = plt.axes([0.15, 0.65, 0.8, 0.3])
ax.plot(range(100), range(100))
ax2 = plt.axes([0.15, 0.15, 0.8, 0.3])
ax2.plot(range(100), range(10000, 110000, 1000))
If you set the left value of fig.subplots_adjust to a constant for both plots, the left edges should be in the same place, e.g.:
fig.subplots_adjust(left = 0.12) # you might need to adjust 0.12 to your needs
Put that in your scripts for both your figures and that should align them both nicely.

Subplots: tight_layout changes figure size

Changing the vertical distance between two subplot using tight_layout(h_pad=-1) changes the total figuresize. How can I define the figuresize using tight_layout?
Here is the code:
#define figure
pl.figure(figsize=(10, 6.25))
ax1=subplot(211)
img=pl.imshow(np.random.random((10,50)), interpolation='none')
ax1.set_xticklabels(()) #hides the tickslabels of the first plot
subplot(212)
x=linspace(0,50)
pl.plot(x,x,'k-')
xlim( ax1.get_xlim() ) #same x-axis for both plots
And here is the results:
If I write
pl.tight_layout(h_pad=-2)
in the last line, then I get this:
As you can see, the figure is bigger...
You can use a GridSpec object to control precisely width and height ratios, as answered on this thread and documented here.
Experimenting with your code, I could produce something like what you want, by using a height_ratio that assigns twice the space to the upper subplot, and increasing the h_pad parameter to the tight_layout call. This does not sound completely right, but maybe you can adjust this further ...
import numpy as np
from matplotlib.pyplot import *
import matplotlib.pyplot as pl
import matplotlib.gridspec as gridspec
#define figure
fig = pl.figure(figsize=(10, 6.25))
gs = gridspec.GridSpec(2, 1, height_ratios=[2,1])
ax1=subplot(gs[0])
img=pl.imshow(np.random.random((10,50)), interpolation='none')
ax1.set_xticklabels(()) #hides the tickslabels of the first plot
ax2=subplot(gs[1])
x=np.linspace(0,50)
ax2.plot(x,x,'k-')
xlim( ax1.get_xlim() ) #same x-axis for both plots
fig.tight_layout(h_pad=-5)
show()
There were other issues, like correcting the imports, adding numpy, and plotting to ax2 instead of directly with pl. The output I see is this:
This case is peculiar because of the fact that the default aspect ratios of images and plots are not the same. So it is worth noting for people looking to remove the spaces in a grid of subplots consisting of images only or of plots only that you may find an appropriate solution among the answers to this question (and those linked to it): How to remove the space between subplots in matplotlib.pyplot?.
The aspect ratios of the subplots in this particular example are as follows:
# Default aspect ratio of images:
ax1.get_aspect()
# 1.0
# Which is as it is expected based on the default settings in rcParams file:
matplotlib.rcParams['image.aspect']
# 'equal'
# Default aspect ratio of plots:
ax2.get_aspect()
# 'auto'
The size of ax1 and the space beneath it are adjusted automatically based on the number of pixels along the x-axis (i.e. width) so as to preserve the 'equal' aspect ratio while fitting both subplots within the figure. As you mentioned, using fig.tight_layout(h_pad=xxx) or the similar fig.set_constrained_layout_pads(hspace=xxx) is not a good option as this makes the figure larger.
To remove the gap while preserving the original figure size, you can use fig.subplots_adjust(hspace=xxx) or the equivalent plt.subplots(gridspec_kw=dict(hspace=xxx)), as shown in the following example:
import numpy as np # v 1.19.2
import matplotlib.pyplot as plt # v 3.3.2
np.random.seed(1)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6.25),
gridspec_kw=dict(hspace=-0.206))
# For those not using plt.subplots, you can use this instead:
# fig.subplots_adjust(hspace=-0.206)
size = 50
ax1.imshow(np.random.random((10, size)))
ax1.xaxis.set_visible(False)
# Create plot of a line that is aligned with the image above
x = np.arange(0, size)
ax2.plot(x, x, 'k-')
ax2.set_xlim(ax1.get_xlim())
plt.show()
I am not aware of any way to define the appropriate hspace automatically so that the gap can be removed for any image width. As stated in the docstring for fig.subplots_adjust(), it corresponds to the height of the padding between subplots, as a fraction of the average axes height. So I attempted to compute hspace by dividing the gap between the subplots by the average height of both subplots like this:
# Extract axes positions in figure coordinates
ax1_x0, ax1_y0, ax1_x1, ax1_y1 = np.ravel(ax1.get_position())
ax2_x0, ax2_y0, ax2_x1, ax2_y1 = np.ravel(ax2.get_position())
# Compute negative hspace to close the vertical gap between subplots
ax1_h = ax1_y1-ax1_y0
ax2_h = ax2_y1-ax2_y0
avg_h = (ax1_h+ax2_h)/2
gap = ax1_y0-ax2_y1
hspace=-(gap/avg_h) # this divided by 2 also does not work
fig.subplots_adjust(hspace=hspace)
Unfortunately, this does not work. Maybe someone else has a solution for this.
It is also worth mentioning that I tried removing the gap between subplots by editing the y positions like in this example:
# Extract axes positions in figure coordinates
ax1_x0, ax1_y0, ax1_x1, ax1_y1 = np.ravel(ax1.get_position())
ax2_x0, ax2_y0, ax2_x1, ax2_y1 = np.ravel(ax2.get_position())
# Set new y positions: shift ax1 down over gap
gap = ax1_y0-ax2_y1
ax1.set_position([ax1_x0, ax1_y0-gap, ax1_x1, ax1_y1-gap])
ax2.set_position([ax2_x0, ax2_y0, ax2_x1, ax2_y1])
Unfortunately, this (and variations of this) produces seemingly unpredictable results, including a figure resizing similar to when using fig.tight_layout(). Maybe someone else has an explanation for what is happening here behind the scenes.

Categories