Is there an automatic way to add pure labels to the subplots? To be specific, I used
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
and I would like to add 'A' and 'B' to the upper right in the subplots to distinguish them, and right now I am using a dummy way something like
ax1.annotate('A', xy=(2, 1), xytext=(1, 22))
ax2.annotate('B', xy=(2, 1), xytext=(1, 22))
I tried using
ax1.legend()
and that also gives me "small images" of lines or dots before the letter and I do not need that image.
You can skip writing a helper function and just call:
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
ax1.annotate("A", xy=(0.9, 0.9), xycoords="axes fraction")
ax2.annotate("B", xy=(0.9, 0.9), xycoords="axes fraction")
You can use annotate, but you'll need to set the correct limits so they are in the "upper right corner". If you call the annotate commands after you've made all the plots, this should work since it pulls the limits from the axis itself.
import pylab as plt
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
def get_axis_limits(ax, scale=.9):
return ax.get_xlim()[1]*scale, ax.get_ylim()[1]*scale
ax1.annotate('A', xy=get_axis_limits(ax1))
ax2.annotate('B', xy=get_axis_limits(ax2))
plt.show()
It's also worth looking at the other ways to put text on the figure.
Answer by hooked works, but keep in mind that you need to scale the position properly.
def text_coords(ax=None,scalex=0.9,scaley=0.9):
xlims = ax.get_xlim()
ylims = ax.get_ylim()
return {'x':scalex*np.diff(xlims)+xlims[0],
'y':scaley*np.diff(ylims)+ylims[0]}
scalex = [0.02,0.02,0.75,0.75]
scaley = [0.02,0.75,0.02,0.75]
labels = ['(a)','(b)','(c)','(d)']
f,ax = plt.subplots(2,2)
for sx,sy,a,l in zip(scalex,scaley,np.ravel(ax),labels):
a.text(s=l,**text_coords(ax=a,scalex=sx,scaley=sy))
plt.tight_layout()
plt.show()
labels demo
Matplotlib (version 3.4.2) has a function to help with this: pyplot.subplot_mosaic.
See the example here which demonstrates how to produce the following:
Related
I have been trying to find some answers, but most of them don't include a table, or they solve the problem generally and I get in trouble trying to find a workaround with the table I created as I managed to put the table through an empty axis. But now decreasing the right-axis size (as the table gets accommodated to the axis size) and increasing the left two-axis size is becoming a daunting task.
I have this code:
fig = plt.figure(figsize=(18,5))
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(223)
ax3 = fig.add_subplot(122)
ax3.axis('off')
data = pd.DataFrame({'metrics': ['MSLE train', 'msle_test', 'asdsad'],
'values': [0.43, 0.52, 0.54]})
ax3.table(cellText=data.values, colLabels=data.columns, loc='center')
fig.suptitle(f'Train MSLE: {msle_train}, Test MSLE: {msle_test}')
ax1 = y_data.plot(label='Original data', ax=ax1, c='blue')
ax1 = y_pred_train.plot(ax=ax1, c='orange')
ax1 = y_pred_test.plot(ax=ax1, c='orange', linestyle='--')
ax1.legend()
ax2 = error_train.plot(label='Train error', ax=ax2)
ax2 = error_test.plot(label='Test error', ax=ax2, linestyle='--')
ax2.legend()
plt.show()
That returns this plot:
I'm looking to increase the horizontal size of the two left plots, something near the red mark:
Any suggestions?
You can use gridspec.
It even works with a vertical centered right hand side and a table:
import matplotlib.pyplot as plt
from matplotlib import gridspec
import pandas as pd
data = pd.DataFrame({'metrics': ['MSLE train', 'msle_test', 'asdsad'],
'values': [0.43, 0.52, 0.54]})
fig = plt.figure(figsize=(18,5))
gs = gridspec.GridSpec(4, 2, width_ratios=[3,1])
ax1 = fig.add_subplot(gs[0:2,:-1])
ax1.set_title('ax1')
ax2 = fig.add_subplot(gs[2:4,:-1])
ax2.set_title('ax2')
ax3 = fig.add_subplot(gs[1:3,1])
ax3.set_axis_off()
ax3.table(cellText=data.values, colLabels=data.columns, loc='center')
fig.tight_layout()
plt.show()
Notes:
Horizontal alignment is set with the ratio of width_ratios=[3,1]
fig.tight_layout() is helpfull to automatically align the spacing between the plots.
Vertical centering is achieved with a little workaround by having initially a larger vertical grid than required (no. of vertical plots) and distributing the plots and table accordingly (see e.g. gs[2:4).
The titles were just added for visual orientation.
ax3.set_axis_off() is required to suppress the plot frame at the table position - without it you'll get:
I'm trying to remove the white space from the plot that I created:
As it is possible to see, there a big white spot on the right and also on the bottom, how to fix it? Here is my script:
fig = plt.figure(figsize=(7,7))
ax1 = plt.subplot2grid((4,3), (0,0),)
ax2 = plt.subplot2grid((4,3), (1,0),)
ax3 = plt.subplot2grid((4,3), (0,1),)
ax4 = plt.subplot2grid((4,3), (1,1),)
data = self.dframe[i]
tes = print_data(data, self.issues, self.color, self.type_user)
tes.print_top(data=data, top=10, ax=ax1, typegraph="hbar", problem=self.issues[i], tone=self.color[i])
tes.print_top(data=data, top=10, ax=ax2, typegraph="prod_bar", problem=self.issues[i], tone=self.color[i])
tes.print_top(data=data, top=10, ax=ax3, typegraph="reg_hbar", problem=self.issues[i], tone=self.color[i])
tes.print_top(data=data, top=10, ax=ax4, typegraph=self.type_user, problem=self.issues[i], tone=self.color[i])
problem = self.issues[i]
plt.tight_layout()
name = problem + str('.PNG')
plt.close(fig)
fig.savefig(name)
You are creating too many subplots!
If we look at this line:
ax1 = plt.subplot2grid((4,3), (0,0),)
We can see the first argument given to subplot2grid are the dimensions of the subplot grid to be made, in this case 4 rows, and 3 columns. You are then plotting in the subplots in the top left of your figure (the second argument given) which leaves a lot of space that's not used.
So to solve this, reduce the number of subplots by using:
ax1 = plt.subplot2grid((2,2), (0,0),)
Full example:
import numpy as np
import matplotlib.pyplot as plt
data = np.random.randn(25)
fig = plt.figure(figsize=(7,7))
ax1 = plt.subplot2grid((2,2), (0,0),)
ax2 = plt.subplot2grid((2,2), (1,0),)
ax3 = plt.subplot2grid((2,2), (0,1),)
ax4 = plt.subplot2grid((2,2), (1,1),)
ax1.plot(data)
ax2.plot(data)
ax3.plot(data)
ax4.plot(data)
plt.show()
Giving:
you can use
plt.subplots_adjust(left=0.09, bottom=0.07, right=0.98, top=0.97, wspace=0.2 , hspace=0.17 ) to adjust the window.
But the issue is that a lot of the space in your plot is empty
maybe you should change
plt.subplot2grid((4,3)... to plt.subplot2grid((2,2)
This question already has answers here:
Save a subplot in matplotlib
(2 answers)
Closed 5 years ago.
Suppose I have the following code (modified version of matplotlib gridspec tutorial)
import matplotlib.pyplot as plt
def make_ticklabels_invisible(fig):
for i, ax in enumerate(fig.axes):
ax.text(0.5, 0.5, "ax%d" % (i+1), va="center", ha="center")
for tl in ax.get_xticklabels() + ax.get_yticklabels():
tl.set_visible(False)
plt.figure(0)
ax1 = plt.subplot2grid((3,3), (0,0), colspan=3)
ax2 = plt.subplot2grid((3,3), (1,0), colspan=2)
ax3 = plt.subplot2grid((3,3), (1, 2), rowspan=2)
ax4 = plt.subplot2grid((3,3), (2, 0))
plt.subplot2grid((3,3), (2, 1)) # OOPS! Forgot to store axes object
plt.suptitle("subplot2grid")
make_ticklabels_invisible(plt.gcf())
plt.show()
which results in
How can I 'extract' ax5 and plot it 'full screen' in a separate figure without having to recreate the plot?
I can't find anything in official documentation to back up what I'm saying, but my understanding is that it is impossible to "clone" an existing axes onto a new figure. In fact, no artist (line, text, legend) defined in one axes may be added to another axes. This discussion on Github may explain it to some degree.
For example, attempting to add a line from an axes defined on fig1 to an axes on a different figure fig2 raises an error:
import matplotlib.pyplot as plt
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
line, = ax1.plot([0,1])
fig2 = plt.figure()
ax2 = fig2.add_subplot(111)
ax2.add_line(line)
>>>RuntimeError: Can not put single artist in more than one figure`
And attempting to add a line that was drawn in ax1 to a second axes ax2 on the same figure raises an error:
fig1 = plt.figure()
ax1 = fig1.add_subplot(121)
line, = ax1.plot([0,1])
ax12 = fig1.add_subplot(122)
ax12.add_line(line)
>>>ValueError: Can not reset the axes. You are probably trying to re-use an artist in more than one Axes which is not supported
The best recommendation I can make is extract the data from the axes you want to copy, and manually plot that into a new axes object that is sized to your liking. Something like below demonstrates this. Note that this works for Line2D objects plotted via ax.plot. If the data was plotted using ax.scatter, then you need to change things just a little bit and I refer you here for instructions on how to extract data from a scatter.
import matplotlib.pyplot as plt
import numpy as np
def rd(n=5):
# Make random data
return np.sort(np.random.rand(n))
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
# Plot three lines on one axes
ax1.plot(rd(), rd(), rd(), rd(), rd(), rd())
xdata = []
ydata = []
# Iterate thru lines and extract x and y data
for line in ax1.get_lines():
xdata.append( line.get_xdata() )
ydata.append( line.get_ydata() )
# New figure and plot the extracted data
fig2 = plt.figure()
ax2 = fig2.add_subplot(111)
for X,Y in zip(xdata,ydata):
ax2.plot(X,Y)
Hope it helps.
I am new to python and having some difficulties with plotting using pyplot. My goal is to plot a grid of plots in-line (%pylab inline) in Juypter Notebook.
I programmed a function plot_CV which plots cross-validation erorr over the degree of polynomial of some x where across plots the degree of penalization (lambda) is supposed to vary. Ultimately there are 10 elements in lambda and they are controlled by the first argument in plot_CV. So
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
ax1 = plot_CV(1,CV_ve=CV_ve)
Gives
Now I think I have to use add_subplot to create a grid of plots as in
fig = plt.figure()
ax1 = fig.add_subplot(2,2,1)
ax1 = plot_CV(1,CV_ve=CV_ve)
ax2 = fig.add_subplot(2,2,2)
ax2 = plot_CV(2,CV_ve=CV_ve)
ax3 = fig.add_subplot(2,2,3)
ax3 = plot_CV(3,CV_ve=CV_ve)
ax4 = fig.add_subplot(2,2,4)
ax4 = plot_CV(4,CV_ve=CV_ve)
plt.show()
If I continue this, however, then the plots get smaller and smaller and start to overlap on the x and y labels. Here a picture with a 3 by 3 plot.
Is there a way to space the plots evenly, so that they do not overlap and make better use of the horizontal and vertical in-line space in Jupyter Notebook? To illustrate this point here a screenshot from jupyter:
Final note: I still need to add a title or annotation with the current level of lambda used in plot_CV.
Edit: Using the tight layout as suggested, gives:
Edit 2: Using the fig.set_figheight and fig.set_figwidth I could finally use the full length and heigth available.
The first suggestion to your problem would be taking a look at the "Tight Layout guide" for matplotlib.
They have an example that looks visually very similar to your situation. As well they have examples and suggestions for taking into consideration axis labels and plot titles.
Furthermore you can control the overall figure size by using Figure from the matplotlib.figure class.
Figure(figsize = (x,y))
figsize: x,y (inches)
EDIT:
Here is an example that I pulled from the matplotlib website and added in the:
fig.set_figheight(15)
fig.set_figwidth(15)
example:
import matplotlib.pyplot as plt
plt.rcParams['savefig.facecolor'] = "0.8"
def example_plot(ax, fontsize=12):
ax.plot([1, 2])
ax.locator_params(nbins=3)
ax.set_xlabel('x-label', fontsize=fontsize)
ax.set_ylabel('y-label', fontsize=fontsize)
ax.set_title('Title', fontsize=fontsize)
plt.close('all')
fig = plt.figure()
fig.set_figheight(15)
fig.set_figwidth(15)
ax1 = plt.subplot2grid((3, 3), (0, 0))
ax2 = plt.subplot2grid((3, 3), (0, 1), colspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 0), colspan=2, rowspan=2)
ax4 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
plt.tight_layout()
You can achieve padding of your subplots by using tight_layout this way:
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0)
That way you can keep your subplots from crowding each other even further.
Have a good one!
In matplotlib, I know how to set the height and width and DPI of a figure:
fig = plt.figure(figsize=(4, 5), dpi=100)
However, it seems that if I want to create small multiple plots, I can't create a figure like this, I have to use this:
fig, subplots = plt.subplots(nrows=4, ncols=4)
How can I set the height and width and DPI of a figure created with subplots like this?
You can actually specify height and widthplt.savefig('Desktop/test.png',dpi=500)
, even though it's not listed as keyword in the help (I think it is passed on to the figure call(?)):
fig,axs=plt.subplots(nrows,ncols,figsize=(width,height))
For some reason, dpi is ignored though. However, you can use it when saving the figure, when it is important:
plt.savefig('test.png',dpi=1000)
A working example of the gridspec module:
import matplotlib.pyplot as plt
from matplotlib import gridspec
fig = plt.figure(figsize=(18,18))
gs = gridspec.GridSpec(3, 3)
ax1 = fig.add_subplot(gs[0,:])
ax1.plot([1,2,3,4,5], [10,5,10,5,10], 'r-')
ax2 = fig.add_subplot(gs[1,:-1])
ax2.plot([1,2,3,4], [1,4,9,16], 'k-')
ax3 = fig.add_subplot(gs[1:, 2])
ax3.plot([1,2,3,4], [1,10,100,1000], 'b-')
ax4 = fig.add_subplot(gs[2,0])
ax4.plot([1,2,3,4], [0,0,1,1], 'g-')
ax5 = fig.add_subplot(gs[2,1])
ax5.plot([1,2,3,4], [1,0,0,1], 'c-')
gs.update(wspace=0.5, hspace=0.5)
plt.show()
But I prefer wrapping it in a function and using it like this:
def mySubplotFunction(fig,gs,x,y,c,ax=None):
if not ax:
ax = fig.add_subplot(gs)
ax.plot(x, y, c)
return fig, ax
Usage:
fig2 = plt.figure(figsize=(9,9))
fig2, ax1 = mySubplotFunction(fig2,gs[0,:],[1,2,3,4,5],[10,5,10,5,10],'r-');
fig2, ax2 = mySubplotFunction(fig2,gs[1,:-1],[1,2,3,4],[1,4,9,16],'k-');