I'm not sure my wording is correct, but what I am trying to do is create a figure of two subplots, where the two plots have different limits, but their size is such that the physical scale (as in, y-distance per centimeter of figure height) is the same. To clarify, lets say subplot 1 shows data from -3 to 3 and subplot 2 shows data from -1 to 1. I want to have them below one another in such a way that the height of subplot2 (excluding ticks, just everything inside the frame) is exactly one third of subplot 1.
My attempt was as follows:
from matplotlib import gridspec
from matplotlib import pyplot as plt
import numpy as np
x = np.linspace(0,2, 101)
y1 = 3*np.cos(x*np.pi)
y2 = np.cos(x*np.pi)
fig = plt.figure(figsize=(4, 6))
gs = gridspec.GridSpec(8, 1)
ax1 = plt.subplot(gs[0:6,0])
ax1.plot(x, y1, c='orange')
ax1.set_ylim(-3, 3)
ax1.set_xticks([], [])
ax2 = plt.subplot(gs[6:,0])
ax2.plot(x, y2, c='green')
ax2.set_ylim(-1,1)
ax2.set_xticks([0, 1, 2])
ax2.set_xticklabels([r'0', r'0.5', r'1'])
ax2.set_xlabel(r'$n_g$ (2e)')
plt.tight_layout()
fig.text(-0.025, 0.5, 'Frequency (GHz)', ha='center', va='center', rotation='vertical', size=18)
which produces the figure below, but as you can see (although you have to look closely) the range -1 to 1 in the second subplot is compressed (takes up less height) than the range -1 to 1 in subplot 1. I'm guessing this is because of the space between the two subplots.
Note that I'm using gridspec because I plan on adding another column of subplots with interesting aspect ratio's and its own labels and limits. I didn't know how to add a global ylabel in a more elegant way, if someone was wondering.
You can set the height_ratios of the gridspec to match the range of the limits.
from matplotlib import gridspec
from matplotlib import pyplot as plt
import numpy as np
x = np.linspace(0,2, 101)
y1 = 3*np.cos(x*np.pi)
y2 = np.cos(x*np.pi)
ylim1 = -3,3
ylim2 = -1,1
fig = plt.figure(figsize=(4, 6), constrained_layout=True)
gs = gridspec.GridSpec(2, 1, height_ratios=[np.diff(ylim1)[0],
np.diff(ylim2)[0]], figure=fig)
ax1 = plt.subplot(gs[0,0])
ax1.plot(x, y1, c='orange')
ax1.set_ylim(ylim1)
ax1.set_xticks([], [])
ax2 = plt.subplot(gs[1,0])
ax2.plot(x, y2, c='green')
ax2.set_ylim(ylim2)
ax2.set_xticks([0, 1, 2])
ax2.set_xticklabels([r'0', r'0.5', r'1'])
ax2.set_xlabel(r'$n_g$ (2e)')
plt.show()
Related
I have the issue that I am trying to make multiple plots that are supposed to have the same bbox size. As some of my plots have an additional colorbar or wider yticklabels the bbox size varies within multiple plots.
As I would like to use these plots in a LaTex document underneath each other, I would like to set the bbox for all plots to the same value instead of defining the figure size.
If it is not clear yet what I mean, here's an example:
As you can see the bbox sizes vary, as the width of the ylabel + ylabelticks and additionally the cbar is added. I thought the easisest way to approach this would be to find the image of the smallest drawn bbox and use that as a standard for all figures and keep the figsize constant, or to just set the bbox size constant and just add the rest and have varying figsizes.. the later would need me to do additional positioning in latex/illustrator/power point or whatever, but just about any solution that works would be great (even though I belive that the later is likely not possible with matplotlib). I tried changing the bbox size but unfortunately did not succeed. So I do not have some code to start from. But any help or pointers where to look at or start would help a lot.
Here a short code snippet to reproduce.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
np.random.seed(1)
mpl.rcParams['figure.figsize'] = (16.0, 12.0)
x = np.linspace(0, 100, 100)
y = np.random.randint(100, size=100)
z = np.random.randint(0, 1e6, size=100)/1e6
fig, ax = plt.subplots()
m = mpl.cm.ScalarMappable(cmap=mpl.cm.jet)
norm = plt.Normalize(min(z), max(z))
m.set_array(list(set(z)))
cbar = plt.colorbar(m, orientation="vertical", fraction=0.07, pad=0.02)
color = lambda c: m.cmap(norm(c))
ax.scatter(x, y, color=color(z))
fig, ax = plt.subplots()
ax.scatter(x, y)
pls see following code. I recommend you using ax1 and ax2, which have more flexibility.
Key points:
using get_position() to get bounds of axes.
using set_position() to set bounds of axes.
I highly recommend using ax1, ax2 ... instead of plt.stuff for multiple subplots.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
np.random.seed(1)
x = np.linspace(0, 100, 100)
y = np.random.randint(100, size=100)
z = np.random.randint(0, 1e6, size=100)/1e6
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 4))
m = mpl.cm.ScalarMappable(cmap=mpl.cm.jet)
norm = plt.Normalize(min(z), max(z))
m.set_array(list(set(z)))
cbar = fig.colorbar(m, orientation="vertical", fraction=0.07, pad=0.02)
color = lambda c: m.cmap(norm(c))
ax2.scatter(x, y, color=color(z))
ax1.scatter(x, y)
# get the bounds of ax1 and ax2
x1, y1, w1, h1 = ax1.get_position().bounds
x2, y2, w2, h2 = ax2.get_position().bounds
# set ax1 width to width of ax2
ax1.set_position([x1, y1, w2, h1])
I have 2 sets of rectangular patches in a plot. I want to name them separately. "Layer-1" for the bottom part and similarly "Layer-2" for the upper part. I wanted to set coordinates for the Y-axis but it did not work. Moreover i was not able to add the "Layer-2" text into the label. Please help.
I tried with the below mentioned code but it did not work.
plt.ylabel("LAYER-1", loc='bottom')
yaxis.labellocation(bottom)
One solution is to create a second axis, so called twin axis that shares the same x axis. Then it is possbile to label them separately. Furthermore, you can adjust the location of the label via
axis.yaxis.set_label_coords(-0.1, 0.75)
Here is an example that you can adjust to your desires. The result can be found here: https://i.stack.imgur.com/1o2xl.png
%matplotlib notebook
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
plt.rcParams['figure.dpi'] = 100
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = 0.05 * x**2
y2 = -1 *y1
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.plot(x, y1, 'g-')
ax2.plot(x, y2, 'b-')
# common x axis
ax1.set_xlabel('X data')
# First y axis label
ax1.set_ylabel('LAYER-1', color='g')
# Second y [enter image description here][1]axis label
ax2.set_ylabel('LAYER-2', color='b')
# Adjust the label location
ax1.yaxis.set_label_coords(-0.075, 0.25)
ax2.yaxis.set_label_coords(-0.1, 0.75)
plt.show()
I wonder how to set the size of the subplot when figure contains multiple subplots (5 × 2 in my case). No matter how big I allow the whole figure to be, the subplots always seem to be small. I would like to have direct control of the size of the subplot in this figure. The simplified version of the code is pasted below.
import numpy as np
import matplotlib.pyplot as plt
x = np.random.randn(20)
y = np.random.randn(20)
fig = plt.figure(figsize=(20, 8))
for i in range(0,10):
ax = fig.add_subplot(5, 2, i+1)
plt.plot(x, y, 'o')
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
# x and y axis should be equal length
x0,x1 = ax.get_xlim()
y0,y1 = ax.get_ylim()
ax.set_aspect(abs(x1-x0)/abs(y1-y0))
plt.show()
fig.savefig('plot.pdf', bbox_inches='tight')
Just switch figure size width and height from:
fig = plt.figure(figsize=(20, 8))
to:
fig = plt.figure(figsize=(8, 20))
to use the whole page for your plots.
This will change your plot from:
to:
I am using GridSpec to plot two plots one below the other without a gap in between with
gs = gridspec.GridSpec(3, 1)
gs.update(hspace=0., wspace=0.)
ax1 = plt.subplot(gs[0:2, 0])
ax2 = plt.subplot(gs[2, 0], sharex=ax1)
which works fine. However, I want to get rid of each subplot's top and bottom tick label.
For that I use
nbins = len(ax1.get_yticklabels())
ax1.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='both'))
nbins = len(ax2.get_yticklabels())
ax2.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='both'))
which in many cases works fine. In some plots, however, one or more of the 4 labels to prune are still there. I looked at e.g. ax1.get_ylim() and noticed that instead of for example the upper limit being 10 (as it is shown in the plot itself), it is actually 10.000000000000002, which I suspect is the reason why it is not pruned. How does that happen and how can I get rid of that?
Here is an example: Note that in the figure the y axis is inverted and no label is pruned, altough it should be. Also note that for some reason the lowest y-label is set to a negative position, which I don't see. The y-tick positions are shown in in axis coordinates in the text within the plots. In the image below, the label at 10.6 should not be there!
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.ticker import MaxNLocator
import numpy as np
x1 = 1
y1 = 10.53839
err1 = 0.00865
x2 = 2
y2 = 9.43045
err2 = 0.00658
plt.clf()
fig = plt.figure(figsize=(6, 6))
gs = gridspec.GridSpec(3, 1)
gs.update(hspace=0., wspace=0.)
ax1 = plt.subplot(gs[0:2, 0])
ax1.errorbar(x1, y1, yerr=err1)
ax1.errorbar(x2, y2, yerr=err2)
ax1.invert_yaxis()
plt.setp(ax1.get_xticklabels(), visible=False) # Remove x-labels between the plots
plt.xlim(0, 3)
ax2 = plt.subplot(gs[2, 0], sharex=ax1)
nbins = len(ax1.get_yticklabels())
ax1.yaxis.set_major_locator(MaxNLocator(nbins=8, prune='both'))
nbins = len(ax2.get_yticklabels())
ax2.yaxis.set_major_locator(MaxNLocator(nbins=6, prune='both'))
plt.savefig('prune.png')
plt.close()
Could it be, that you are looking at the left most label on the x axis of the upper plot? If so, this should do the trick:
ax1.set_xticklabels([])
EDIT: If you use sharex, you have to use this, otherwise the tick labels are removed on both axes.
plt.setp(ax1.get_xticklabels(), visible=False)
You can try to use this:
import matplotlib.ticker as mticker
ax2.yaxis.set_major_locator(mticker.MaxNLocator(nbins=7, prune='upper'))
I found the above command only works for the y-axis.
Does someone know how to set up the maximum limits of x-axis tickers' number?
I have a simple scatter plot where each point has a color given by a value between 0 and 1 set to a chosen colormap. Here's a MWE of my code:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.gridspec as gridspec
x = np.random.randn(60)
y = np.random.randn(60)
z = [np.random.random() for _ in range(60)]
fig = plt.figure()
gs = gridspec.GridSpec(1, 2)
ax0 = plt.subplot(gs[0, 0])
plt.scatter(x, y, s=20)
ax1 = plt.subplot(gs[0, 1])
cm = plt.cm.get_cmap('RdYlBu_r')
plt.scatter(x, y, s=20 ,c=z, cmap=cm)
cbaxes = fig.add_axes([0.6, 0.12, 0.1, 0.02])
plt.colorbar(cax=cbaxes, ticks=[0.,1], orientation='horizontal')
fig.tight_layout()
plt.show()
which looks like this:
The problem here is that I want the small horizontal colorbar position to the lower left of the plot but using the cax argument not only feels a bit hacky, it apparently conflicts with tight_layout which results in the warning:
/usr/local/lib/python2.7/dist-packages/matplotlib/figure.py:1533: UserWarning: This figure includes Axes that are not compatible with tight_layout, so its results might be incorrect.
warnings.warn("This figure includes Axes that are not "
Isn't there a better way to position the colorbar, ie without getting a nasty warning thrown at you whenever you run the code?
Edit
I wanted the colorbar to show only the max and min values, ie: 0 and 1 and Joe helped me do that by adding vmin=0, vmax=1 to scatter like so:
plt.scatter(x, y, s=20, vmin=0, vmax=1)
so I'm removing this part of the question.
One may use a mpl_toolkits.axes_grid1.inset_locator.inset_axes to place an axes inside another axes. This axes can be used to host the colorbar. Its position is relative the the parent axes, similar to how legends are placed, using a loc argument (e.g. loc=3 means lower left). Its width and height can be specified in absolute numbers (inches) or relative to the parent axes (percentage).
cbaxes = inset_axes(ax1, width="30%", height="3%", loc=3)
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.gridspec as gridspec
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
x = np.random.randn(60)
y = np.random.randn(60)
z = [np.random.random() for _ in range(60)]
fig = plt.figure()
gs = gridspec.GridSpec(1, 2)
ax0 = plt.subplot(gs[0, 0])
plt.scatter(x, y, s=20)
ax1 = plt.subplot(gs[0, 1])
cm = plt.cm.get_cmap('RdYlBu_r')
plt.scatter(x, y, s=20 ,c=z, cmap=cm)
fig.tight_layout()
cbaxes = inset_axes(ax1, width="30%", height="3%", loc=3)
plt.colorbar(cax=cbaxes, ticks=[0.,1], orientation='horizontal')
plt.show()
Note that in order to suppress the warning, one might simply call tight_layout prior to adding the inset axes.