Is it possible to set the size/position of a matplotlib subplot after the axes are created? I know that I can do:
import matplotlib.pyplot as plt
ax = plt.subplot(111)
ax.change_geometry(3,1,1)
to put the axes on the top row of three. But I want the axes to span the first two rows. I have tried this:
import matplotlib.gridspec as gridspec
ax = plt.subplot(111)
gs = gridspec.GridSpec(3,1)
ax.set_subplotspec(gs[0:2])
but the axes still fill the whole window.
Update for clarity
I want to change the position of an existing axes instance rather than set it when it is created. This is because the extent of the axes will be modified each time I add data (plotting data on a map using cartopy). The map may turn out tall and narrow, or short and wide (or something in between). So the decision on the grid layout will happen after the plotting function.
Thanks to Molly pointing me in the right direction, I have a solution:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
ax = fig.add_subplot(111)
gs = gridspec.GridSpec(3,1)
ax.set_position(gs[0:2].get_position(fig))
ax.set_subplotspec(gs[0:2]) # only necessary if using tight_layout()
fig.add_subplot(gs[2])
fig.tight_layout() # not strictly part of the question
plt.show()
You can create a figure with one subplot that spans two rows and one subplot that spans one row using the rowspan argument to subplot2grid:
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = plt.subplot2grid((3,1), (0,0), rowspan=2)
ax2 = plt.subplot2grid((3,1), (2,0))
plt.show()
If you want to change the subplot size and position after it's been created you can use the set_position method.
ax1.set_position([0.1,0.1, 0.5, 0.5])
Bu you don't need this to create the figure you described.
You can avoid ax.set_position() by using fig.tight_layout() instead which recalculates the new gridspec:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# create the first axes without knowing of further subplot creation
fig, ax = plt.subplots()
ax.plot(range(5), 'o-')
# now update the existing gridspec ...
gs = gridspec.GridSpec(3, 1)
ax.set_subplotspec(gs[0:2])
# ... and recalculate the positions
fig.tight_layout()
# add a new subplot
fig.add_subplot(gs[2])
fig.tight_layout()
plt.show()
Related
I want to give all the figures in my code a single title #Variable_cycles. Figures are not subplots but plotted separately. I am using %matplotlib to show plots in separate window. As far as i know plt.rcParams has no such key
import matplotlib.pyplot as plt
%matplotlib
plt.figure(1), plt.scatter(x,y,marker='o'),
plt.title("Variable_cycles"),
plt.show
plt.figure(2),
plt.scatter(x,y,marker='*'),
plt.title("Variable_cycles"),
plt.show
I don't believe there is such a setting in rcParams or similar, but if there are options you are setting for all figures, you could create a simple helper function to create the figure, apply those settings (e.g. the title, axes labels, etc), and return the figure object, then you just need to call that function once for each new figure. A simple example would be:
import matplotlib.pyplot as plt
%matplotlib
def makefigure():
# Create figure and axes
fig, ax = plt.subplots()
# Set title
fig.suptitle('Variable cycles')
# Set axes labels
ax.set_xlabel('My xlabel')
ax.set_ylabel('My ylabel')
# Put any other common settings here...
return fig, ax
fig1, ax1 = makefigure()
ax1.scatter(x, y, marker='o')
fig2, ax2 = makefigure()
ax2.scatter(x, y, marker='*')
I have a matplotlib bar chart, which bars are colored according to some rules through a colormap. I need a colorbar on the right of the main axes, so I added a new axes with
fig, (ax, ax_cbar) = plt.subplots(1,2)
and managed to draw my color bar in the ax_bar axes, while I have my data displayed in the ax axes. Now I need to reduce the width of the ax_bar, because it looks like this:
How can I do?
Using subplots will always divide your figure equally. You can manually divide up your figure in a number of ways. My preferred method is using subplot2grid.
In this example, we are setting the figure to have 1 row and 10 columns. We then set ax to be the start at row,column = (0,0) and have a width of 9 columns. Then set ax_cbar to start at (0,9) and has by default a width of 1 column.
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8,6))
num_columns = 10
ax = plt.subplot2grid((1,num_columns), (0,0), colspan=num_columns-1)
ax_cbar = plt.subplot2grid((1,num_columns), (0,num_columns-1))
The ususal way to add a colorbar is by simply putting it next to the axes:
fig.colorbar(sm)
where fig is the figure and sm is the scalar mappable to which the colormap refers. In the case of the bars, you need to create this ScalarMappable yourself. Apart from that there is no need for complex creation of multiple axes.
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
fig , ax = plt.subplots()
x = [0,1,2,3]
y = np.array([34,40,38,50])*1e3
norm = matplotlib.colors.Normalize(30e3, 60e3)
ax.bar(x,y, color=plt.cm.plasma_r(norm(y)) )
ax.axhline(4.2e4, color="gray")
ax.text(0.02, 4.2e4, "42000", va='center', ha="left", bbox=dict(facecolor="w",alpha=1),
transform=ax.get_yaxis_transform())
sm = plt.cm.ScalarMappable(cmap=plt.cm.plasma_r, norm=norm)
sm.set_array([])
fig.colorbar(sm)
plt.show()
If you do want to create a special axes for the colorbar yourself, the easiest method would be to set the width already inside the call to subplots:
fig , (ax, cax) = plt.subplots(ncols=2, gridspec_kw={"width_ratios" : [10,1]})
and later put the colorbar to the cax axes,
fig.colorbar(sm, cax=cax)
Note that the following questions have been asked for this homework assignment already:
Point picker event_handler drawing line and displaying coordinates in matplotlib
Matplotlib's widget to select y-axis value and change barplot
Display y axis value horizontal line drawn In bar chart
How to change colors automatically once a parameter is changed
Interactively Re-color Bars in Matplotlib Bar Chart using Confidence Intervals
I currently have 2 subplots using seaborn:
import matplotlib.pyplot as plt
import seaborn.apionly as sns
f, (ax1, ax2) = plt.subplots(2, sharex=True)
sns.distplot(df['Difference'].values, ax=ax1) #array, top subplot
sns.boxplot(df['Difference'].values, ax=ax2, width=.4) #bottom subplot
sns.stripplot([cimin, cimax], color='r', marker='d') #overlay confidence intervals over boxplot
ax1.set_ylabel('Relative Frequency') #label only the top subplot
plt.xlabel('Difference')
plt.show()
Here is the output:
I am rather stumped on how to make ax2 (the bottom figure) to become shorter relative to ax1 (the top figure). I was looking over the GridSpec (http://matplotlib.org/users/gridspec.html) documentation but I can't figure out how to apply it to seaborn objects.
Question:
How do I make the bottom subplot shorter compared to the top
subplot?
Incidentally, how do I move the plot's title "Distrubition of Difference" to go above the top
subplot?
Thank you for your time.
As #dnalow mentioned, seaborn has no impact on GridSpec, as you pass a reference to the Axes object to the function. Like so:
import matplotlib.pyplot as plt
import seaborn.apionly as sns
import matplotlib.gridspec as gridspec
tips = sns.load_dataset("tips")
gridkw = dict(height_ratios=[5, 1])
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw=gridkw)
sns.distplot(tips.loc[:,'total_bill'], ax=ax1) #array, top subplot
sns.boxplot(tips.loc[:,'total_bill'], ax=ax2, width=.4) #bottom subplot
plt.show()
If you're using a FacetGrid (either directly or through something like catplot, which uses it indirectly), then you can pass gridspec_kws.
Here is an example using a catplot, where "var3" has two values, i.e. there are two subplots, which I am displaying at a ratio of 3:8, with un-shared x-axes.
g = sns.catplot(data=data, x="bin", y="y", col="var3", hue="var4", kind="bar",
sharex=False,
facet_kws={
'gridspec_kws': {'width_ratios': [3, 8]}
})
# Make the first subplot have a custom `xlim`:
g.axes[0][0].set_xlim(right=2.5)
Result, with labels hidden because I just copied my actual data's output, so the labels wouldn't make sense.
I'm trying to construct a simple function that takes a subplot instance (matplotlib.axes._subplots.AxesSubplot) and transforms its projection to another projection, for example, to one of the cartopy.crs.CRS projections.
The idea looks something like this
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
def make_ax_map(ax, projection=ccrs.PlateCarree()):
# set ax projection to the specified projection
...
# other fancy formatting
ax2.coastlines()
...
# Create a grid of plots
fig, (ax1, ax2) = plt.subplots(ncols=2)
# the first subplot remains unchanged
ax1.plot(np.random.rand(10))
# the second one gets another projection
make_ax_map(ax2)
Of course, I can just use fig.add_subplot() function:
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121)
ax1.plot(np.random.rand(10))
ax2 = fig.add_subplot(122,projection=ccrs.PlateCarree())
ax2.coastlines()
but I was wondering if there is a proper matplotlib method to change a subplot axis projection after it was defined. Reading matplotlib API didn't help unfortunately.
You can't change the projection of an existing axes, the reason is given below. However the solution to your underlying problem is simply to use the subplot_kw argument to plt.subplots() described in the matplotlib documentation here. For example, if you wanted all your subplots to have the cartopy.crs.PlateCarree projection you could do
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
# Create a grid of plots
fig, (ax1, ax2) = plt.subplots(ncols=2, subplot_kw={'projection': ccrs.PlateCarree()})
Regarding the actual question, specifying a projection when you create an axes set determines the axes class you get, which is different for each projection type. For example
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
ax1 = plt.subplot(311)
ax2 = plt.subplot(312, projection='polar')
ax3 = plt.subplot(313, projection=ccrs.PlateCarree())
print(type(ax1))
print(type(ax2))
print(type(ax3))
This code will print the following
<class 'matplotlib.axes._subplots.AxesSubplot'>
<class 'matplotlib.axes._subplots.PolarAxesSubplot'>
<class 'cartopy.mpl.geoaxes.GeoAxesSubplot'>
Notice how each axes is actually an instance of a different class.
Assuming there are multiple axes being used for 2D plotting, like...
fig = matplotlib.pyplot.Figure()
axs = fig.subplots(3, 4) # prepare for multiple subplots
# (some plotting here)
axs[0,0].plot([1,2,3])
... one can simply destroy one of them and replace it with a new one having the 3D projection:
axs[2,3].remove()
ax = fig.add_subplot(3, 4, 12, projection='3d')
ax.plot_surface(...)
Just note that unlike rest of Python, the add_subplot uses row-column indexing starting from 1 (not from 0).
EDIT: Changed my typo about indexing.
following the answer to this question :
In python, how can I inherit and override a method on a class instance, assigning this new version to the same name as the old one?
I found a hack to change the projection of an axe after creating it which seems to work at least in the simple example below, but I have no idea if this solution is the best way
from matplotlib.axes import Axes
from matplotlib.projections import register_projection
class CustomAxe(Axes):
name = 'customaxe'
def plotko(self, x):
self.plot(x, 'ko')
self.set_title('CustomAxe')
register_projection(CustomAxe)
if __name__ == '__main__':
import matplotlib.pyplot as plt
fig = plt.figure()
## use this syntax to create a customaxe directly
# ax = fig.add_subplot(111, projection="customaxe")
## change the projection after creation
ax = plt.gca()
ax.__class__ = CustomAxe
ax.plotko(range(10))
plt.show()
You can use the following function, which removes the axis and generates the axis in the specified projection, similar to dominecf answer, with the advantage that the specific subplot parameters (row, col, and index) are retrieved automatically.
import matplotlib.pyplot as plt
def update_projection(ax, axi, projection='3d', fig=None):
if fig is None:
fig = plt.gcf()
rows, cols, start, stop = axi.get_subplotspec().get_geometry()
ax.flat[start].remove()
ax.flat[start] = fig.add_subplot(rows, cols, start+1, projection=projection)
and generate a plot with all available projections
import matplotlib.projections
import numpy as np
# test data
x = np.linspace(-np.pi, np.pi, 10)
# plot all projections available
projections = matplotlib.projections.get_projection_names()
fig, ax = plt.subplots(nrows=1, ncols=len(projections), figsize=[3.5*len(projections), 4], squeeze=False)
for i, pro_i in enumerate(projections):
update_projection(ax, ax.flat[i], pro_i)
ax.flat[i].set_title(pro_i)
try:
ax.flat[i].grid(True)
ax.flat[i].plot(x, x)
except Exception as a:
print(pro_i, a)
plt.tight_layout(pad=.5)
The code below produces gaps between the subplots. How do I remove the gaps between the subplots and make the image a tight grid?
import matplotlib.pyplot as plt
for i in range(16):
i = i + 1
ax1 = plt.subplot(4, 4, i)
plt.axis('on')
ax1.set_xticklabels([])
ax1.set_yticklabels([])
ax1.set_aspect('equal')
plt.subplots_adjust(wspace=None, hspace=None)
plt.show()
The problem is the use of aspect='equal', which prevents the subplots from stretching to an arbitrary aspect ratio and filling up all the empty space.
Normally, this would work:
import matplotlib.pyplot as plt
ax = [plt.subplot(2,2,i+1) for i in range(4)]
for a in ax:
a.set_xticklabels([])
a.set_yticklabels([])
plt.subplots_adjust(wspace=0, hspace=0)
The result is this:
However, with aspect='equal', as in the following code:
import matplotlib.pyplot as plt
ax = [plt.subplot(2,2,i+1) for i in range(4)]
for a in ax:
a.set_xticklabels([])
a.set_yticklabels([])
a.set_aspect('equal')
plt.subplots_adjust(wspace=0, hspace=0)
This is what we get:
The difference in this second case is that you've forced the x- and y-axes to have the same number of units/pixel. Since the axes go from 0 to 1 by default (i.e., before you plot anything), using aspect='equal' forces each axis to be a square. Since the figure is not a square, pyplot adds in extra spacing between the axes horizontally.
To get around this problem, you can set your figure to have the correct aspect ratio. We're going to use the object-oriented pyplot interface here, which I consider to be superior in general:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8,8)) # Notice the equal aspect ratio
ax = [fig.add_subplot(2,2,i+1) for i in range(4)]
for a in ax:
a.set_xticklabels([])
a.set_yticklabels([])
a.set_aspect('equal')
fig.subplots_adjust(wspace=0, hspace=0)
Here's the result:
You can use gridspec to control the spacing between axes. There's more information here.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
plt.figure(figsize = (4,4))
gs1 = gridspec.GridSpec(4, 4)
gs1.update(wspace=0.025, hspace=0.05) # set the spacing between axes.
for i in range(16):
# i = i + 1 # grid spec indexes from 0
ax1 = plt.subplot(gs1[i])
plt.axis('on')
ax1.set_xticklabels([])
ax1.set_yticklabels([])
ax1.set_aspect('equal')
plt.show()
Without resorting gridspec entirely, the following might also be used to remove the gaps by setting wspace and hspace to zero:
import matplotlib.pyplot as plt
plt.clf()
f, axarr = plt.subplots(4, 4, gridspec_kw = {'wspace':0, 'hspace':0})
for i, ax in enumerate(f.axes):
ax.grid('on', linestyle='--')
ax.set_xticklabels([])
ax.set_yticklabels([])
plt.show()
plt.close()
Resulting in:
With recent matplotlib versions you might want to try Constrained Layout. This does (or at least did) not work with plt.subplot() however, so you need to use plt.subplots() instead:
fig, axs = plt.subplots(4, 4, constrained_layout=True)
Have you tried plt.tight_layout()?
with plt.tight_layout()
without it:
Or: something like this (use add_axes)
left=[0.1,0.3,0.5,0.7]
width=[0.2,0.2, 0.2, 0.2]
rectLS=[]
for x in left:
for y in left:
rectLS.append([x, y, 0.2, 0.2])
axLS=[]
fig=plt.figure()
axLS.append(fig.add_axes(rectLS[0]))
for i in [1,2,3]:
axLS.append(fig.add_axes(rectLS[i],sharey=axLS[-1]))
axLS.append(fig.add_axes(rectLS[4]))
for i in [1,2,3]:
axLS.append(fig.add_axes(rectLS[i+4],sharex=axLS[i],sharey=axLS[-1]))
axLS.append(fig.add_axes(rectLS[8]))
for i in [5,6,7]:
axLS.append(fig.add_axes(rectLS[i+4],sharex=axLS[i],sharey=axLS[-1]))
axLS.append(fig.add_axes(rectLS[12]))
for i in [9,10,11]:
axLS.append(fig.add_axes(rectLS[i+4],sharex=axLS[i],sharey=axLS[-1]))
If you don't need to share axes, then simply axLS=map(fig.add_axes, rectLS)
Another method is to use the pad keyword from plt.subplots_adjust(), which also accepts negative values:
import matplotlib.pyplot as plt
ax = [plt.subplot(2,2,i+1) for i in range(4)]
for a in ax:
a.set_xticklabels([])
a.set_yticklabels([])
plt.subplots_adjust(pad=-5.0)
Additionally, to remove the white at the outer fringe of all subplots (i.e. the canvas), always save with plt.savefig(fname, bbox_inches="tight").