I am using matploitlib boxplot to create a figure like the following:
Using this code:
# Create a figure instance
fig = plt.figure(1, figsize=(4, 4))
# Create an axes instance
ax = fig.add_subplot(111)
ax.set_ylim([0, 1.2])
# Create the boxplot
bp = ax.boxplot(data_to_plot,widths=(0.5, 0.5))
plt.setp(bp['boxes'], linewidth=1)
# Save the figure
plt.xticks([1, 2], ['A', 'B'],fontsize=16)
plt.yticks(fontsize=16)
#fig.savefig('fig1.png', bbox_inches='tight')
plt.show()
What I am trying to get at is to set background for each box so they have different colors.
Thanks
This is only partly doing what you want, but maybe it helps you out to find the answer to your problem. You can use
bp = ax.boxplot(data_to_plot,widths=(0.5, 0.5),notch=True, patch_artist=True)
to fill the plot. In order to change the color take a look at this Example.
I ended up adding the following argument to boxplot:
bp = ax.boxplot(data_to_plot,widths=(0.5, 0.5),patch_artist=True)
and assign each box a unique color using:
bp['boxes'][0].set( facecolor = '#1b9e77' )
Related
I want to make a plot with a grid of thumbnails on the left and a line plot on the right. Here is a minimal example
import numpy as np
from matplotlib import pyplot as plt
### This can change at runtime
n_grid = 4
### Grid of thumbnails
fig = plt.figure(figsize=(20,10.2))
for i in range(n_grid):
for j in range(n_grid):
ax = plt.subplot2grid(shape=(n_grid, 2*n_grid), loc=(i,j))
plt.imshow(np.random.random((16,16)))
ax.set_axis_off()
### Line plot
ax = plt.subplot2grid(shape=(n_grid, 2*n_grid), loc=(0,n_grid), rowspan=n_grid-1, colspan=n_grid)
plt.plot(np.cumsum(np.random.random(100)), label='Random Sum')
plt.xlim([0, 100])
plt.ylim(0,50)
plt.xlabel('Number', fontsize=12)
plt.ylabel('Sum', fontsize=12)
plt.figtext(0.5, 0.01, f'Unique identifier', ha='center', va='baseline')
#plt.tight_layout()
plt.subplots_adjust(left=0.01, bottom=0.03, right=0.99, top=0.99, wspace = 0.06, hspace=0.06)
plt.savefig('plot_1.png', dpi=96)
The problem is that the yticklabels and ylabel stick over the center into the area of the thumbnails. The lineplot on the right is too wide.
One common solution found on the internet is using automatic resizing with tight_layout(), so I change the last three lines to
plt.tight_layout()
#plt.subplots_adjust(left=0.01, bottom=0.03, right=0.99, top=0.99, wspace = 0.06, hspace=0.06)
plt.savefig('plot_2.png', dpi=96)
This does not rescale the lineplot, but instead makes the wspace and hspace attributes so big I get way too much whitespace between the thumbnails.
I am looking for a solution to either
Set wspace and hspace of only the right subplot, not all of them together, or
resize the lineplot to fit into the designated area, without the labels sticking out
It would seem that this is an easy problem, but despite searching for about 2 hours and digging around in the object properties with iPython I found nothing suitable. All solutions seem to change the size and padding of the subplots, not fitting a plot into the area defined with subplot2grid. The only other solution I can think of is a hack that calculates a modified aspect from the value ranges to make the lineplot always a given percentage thinner.
You can play around with subfigures. For example, if you do:
import numpy as np
from matplotlib import pyplot as plt
### This can change at runtime
n_grid = 4
### Grid of thumbnails
fig = plt.figure(figsize=(20,10.2))
# add 2 subfigures
subfigs = fig.subfigures(1, 2, wspace=0)
# add thumbnail grid into left subfig
gsLeft = subfigs[0].add_gridspec(n_grid, n_grid)
axLeft = []
for i in range(n_grid):
for j in range(n_grid):
axLeft.append(subfigs[0].add_subplot(gsLeft[i, j]))
axLeft[-1].imshow(np.random.random((16,16)))
axLeft[-1].set_axis_off()
### Line plot
gsRight = subfigs[1].add_gridspec(3, 1)
axRight = subfigs[1].add_subplot(gsRight[:2, 0])
axRight.plot(np.cumsum(np.random.random(100)), label='Random Sum')
axRight.set_xlim([0, 100])
axRight.set_ylim(0,50)
axRight.set_xlabel('Number', fontsize=12)
axRight.set_ylabel('Sum', fontsize=12)
# adjust subfigures here (play around with these to get the desired effect)
subfigs[0].subplots_adjust(wspace=0.03, hspace=0.03, bottom=0.05, top=0.95, left=0.05, right=0.95)
subfigs[1].subplots_adjust(left=0.01)
# add title (here I've had to add it to the left figure, so it's not centred,
# in my test adding it to the figure itself meant it was not visible, although
# the example in the Matplotlib docs suggests it should work!)
# fig.suptitle(f'Unique identifier', x=0.5, y=0.025, ha='center', va='baseline')
subfigs[0].suptitle(f'Unique identifier', x=0.5, y=0.025, ha='center', va='baseline')
fig.savefig("plot_1.png", dpi=150)
This gives:
but you can play around with the values to adjust it as you like.
I have a plot of a map containing areas which are color coded. I need to remove the automatic color bar from the plot and replace with a legend. However, I cannot find a way to remove the color bar. Xarray was used to create the data array.
map_crs = ccrs.OSGB()
fig = plt.figure(figsize=(10, 15))
cmap = mpl.colors.ListedColormap(colours)
norm = mpl.colors.BoundaryNorm(boundaries=bins, ncolors=len(cmap.colors)-1 )
stamen_terrain = cigmt.Stamen('terrain')
ax = plt.axes(projection=stamen_terrain.crs)
data_array.plot(transform=map_crs, vmin=0, vmax=np.max(data_array), cmap=cmap)
plt.gca().coastlines()
plt.tight_layout()
plt.savefig(plotname, bbox_inches="tight", pad_inches=0.1)
plt.clf()
I have tried adding commands such as, colorbar=False, cbar=False, Colorbar=False. However, just continually receive the corresponding error;
AttributeError: 'QuadMesh' object has no property 'Colorbar'
Any ideas on how to get rid of the colorbar?
Done!
Just needed to put add_colorbar=False into the plotting command.
Now to add the legend!
I am trying to change location of plot legend. Below what I've got for now.
var_list=powiaty_cols[powiaty_cols.str.contains("apart_bel_40")]
for var in var_list:
fig = plt.figure(figsize=(25, 25))
ax = plt.gca()
powiaty.plot(column=var,cmap='Reds', categorical=True,
legend=True, ax=ax,edgecolor='black')
ax.legend(loc='best')
This code is plotting figure but without legend. I've received errors as follows:
No handles with labels found to put in legend.
No handles with labels found to put in legend.
No handles with labels found to put in legend.
No handles with labels found to put in legend.
But without part 'ax.legend(loc='best')' I can get my plot but legend is in upper left corner. Plotted column is filled with integer from 1 to 5. Similar issue is when I'm trying to change size of legend.
Could maybe somebody help in this?
I don't know what powiaty is, but my guess is that you need to get the Axes object back so you can continue modifying it. Try:
ax = powiaty.plot(column=var, cmap='Reds', categorical=True,
legend=True, ax=ax, edgecolor='black'
)
For me worked:
leg = ax.get_legend()
leg.set_bbox_to_anchor((0., 0.1, 0.2, 0.2))
If i run this code in python:
titles = ctf01[0,1:]
fig = plt.figure(figsize=(11.69,8.27), dpi=100)
for num in range(len(titles)):
ax = fig.add_subplot(3,4,num+1)
ax.plot(ctf03[1:,num+1], ctf0102[:,num], 'ro')
ax.set_title(titles[num])
plt.tight_layout()
fig.text(0.5, 0.04, 'CTF12', ha='center')
fig.text(0.04, 0.5, 'CTF3', va='center', rotation='vertical')
fig.savefig("example.pdf")
i get this in the pdf file:
I would like to fix the problem with the "figure title" shown in the red circles.
If i set the 0.04 value as an negative value the title runs out of paper.
I also would like to save some space with moving the title of the subplots (green circles) into the diagram. Any idea how i can realize this?
Thanks for help.
try to add before fig.savefig("example.pdf") following line.
plt.tight_layout()
you have it in your script but it should come after text
It looks like you're trying to set the x and y labels for the whole figure, which isn't possible as these can only be set on an Axes object. Fortunately we can work around it by creating an 'invisible' subplot that fills the whole area and set the labels on this.
After plotting your subplots you would create the invisible one with:
label_ax = fig.add_subplot(111, frameon=False)
The frameon argument prevents it from drawing the box that is added by the default style. Then you tell it not to draw tick marks and make the tick labels invisible (we can't just remove them as it will mess up the spacing).
label_ax.tick_params(bottom=False, left=False, labelcolor="none")
Finally, set your labels:
label_ax.set_xlabel("CTF12")
label_ax.set_ylabel("CTF3")
You can adjust the vertical positioning of the plot titles by providing a pad argument to the set_title function. Giving a negative value will push the title into the plot, you'll need trial and error to find the value that works.
Putting it all together (with made-up data):
fig = plt.figure(figsize=(11.69, 8.27), dpi=100)
for i in range(10):
ax = fig.add_subplot(3, 4, i + 1)
ax.plot([1, 2, 3, 4, 5], "ro")
ax.set_title("Plot {}".format(i), pad=-15)
label_ax = fig.add_subplot(111, frameon=False)
label_ax.tick_params(bottom=False, left=False, labelcolor="none")
label_ax.grid(False) # In case the current style displays a grid.
label_ax.set_xlabel("CTF12")
label_ax.set_ylabel("CTF3")
fig.tight_layout()
fig.savefig("example.pdf")
Which gives:
I'm familiar with the following questions:
Matplotlib savefig with a legend outside the plot
How to put the legend out of the plot
It seems that the answers in these questions have the luxury of being able to fiddle with the exact shrinking of the axis so that the legend fits.
Shrinking the axes, however, is not an ideal solution because it makes the data smaller making it actually more difficult to interpret; particularly when its complex and there are lots of things going on ... hence needing a large legend
The example of a complex legend in the documentation demonstrates the need for this because the legend in their plot actually completely obscures multiple data points.
http://matplotlib.sourceforge.net/users/legend_guide.html#legend-of-complex-plots
What I would like to be able to do is dynamically expand the size of the figure box to accommodate the expanding figure legend.
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax.plot(x, np.arctan(x), label='Inverse tan')
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0))
ax.grid('on')
Notice how the final label 'Inverse tan' is actually outside the figure box (and looks badly cutoff - not publication quality!)
Finally, I've been told that this is normal behaviour in R and LaTeX, so I'm a little confused why this is so difficult in python... Is there a historical reason? Is Matlab equally poor on this matter?
I have the (only slightly) longer version of this code on pastebin http://pastebin.com/grVjc007
Sorry EMS, but I actually just got another response from the matplotlib mailling list (Thanks goes out to Benjamin Root).
The code I am looking for is adjusting the savefig call to:
fig.savefig('samplefigure', bbox_extra_artists=(lgd,), bbox_inches='tight')
#Note that the bbox_extra_artists must be an iterable
This is apparently similar to calling tight_layout, but instead you allow savefig to consider extra artists in the calculation. This did in fact resize the figure box as desired.
import matplotlib.pyplot as plt
import numpy as np
plt.gcf().clear()
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax.plot(x, np.arctan(x), label='Inverse tan')
handles, labels = ax.get_legend_handles_labels()
lgd = ax.legend(handles, labels, loc='upper center', bbox_to_anchor=(0.5,-0.1))
text = ax.text(-0.2,1.05, "Aribitrary text", transform=ax.transAxes)
ax.set_title("Trigonometry")
ax.grid('on')
fig.savefig('samplefigure', bbox_extra_artists=(lgd,text), bbox_inches='tight')
This produces:
[edit] The intent of this question was to completely avoid the use of arbitrary coordinate placements of arbitrary text as was the traditional solution to these problems. Despite this, numerous edits recently have insisted on putting these in, often in ways that led to the code raising an error. I have now fixed the issues and tidied the arbitrary text to show how these are also considered within the bbox_extra_artists algorithm.
Added: I found something that should do the trick right away, but the rest of the code below also offers an alternative.
Use the subplots_adjust() function to move the bottom of the subplot up:
fig.subplots_adjust(bottom=0.2) # <-- Change the 0.02 to work for your plot.
Then play with the offset in the legend bbox_to_anchor part of the legend command, to get the legend box where you want it. Some combination of setting the figsize and using the subplots_adjust(bottom=...) should produce a quality plot for you.
Alternative:
I simply changed the line:
fig = plt.figure(1)
to:
fig = plt.figure(num=1, figsize=(13, 13), dpi=80, facecolor='w', edgecolor='k')
and changed
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0))
to
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,-0.02))
and it shows up fine on my screen (a 24-inch CRT monitor).
Here figsize=(M,N) sets the figure window to be M inches by N inches. Just play with this until it looks right for you. Convert it to a more scalable image format and use GIMP to edit if necessary, or just crop with the LaTeX viewport option when including graphics.
Here is another, very manual solution. You can define the size of the axis and paddings are considered accordingly (including legend and tickmarks). Hope it is of use to somebody.
Example (axes size are the same!):
Code:
#==================================================
# Plot table
colmap = [(0,0,1) #blue
,(1,0,0) #red
,(0,1,0) #green
,(1,1,0) #yellow
,(1,0,1) #magenta
,(1,0.5,0.5) #pink
,(0.5,0.5,0.5) #gray
,(0.5,0,0) #brown
,(1,0.5,0) #orange
]
import matplotlib.pyplot as plt
import numpy as np
import collections
df = collections.OrderedDict()
df['labels'] = ['GWP100a\n[kgCO2eq]\n\nasedf\nasdf\nadfs','human\n[pts]','ressource\n[pts]']
df['all-petroleum long name'] = [3,5,2]
df['all-electric'] = [5.5, 1, 3]
df['HEV'] = [3.5, 2, 1]
df['PHEV'] = [3.5, 2, 1]
numLabels = len(df.values()[0])
numItems = len(df)-1
posX = np.arange(numLabels)+1
width = 1.0/(numItems+1)
fig = plt.figure(figsize=(2,2))
ax = fig.add_subplot(111)
for iiItem in range(1,numItems+1):
ax.bar(posX+(iiItem-1)*width, df.values()[iiItem], width, color=colmap[iiItem-1], label=df.keys()[iiItem])
ax.set(xticks=posX+width*(0.5*numItems), xticklabels=df['labels'])
#--------------------------------------------------
# Change padding and margins, insert legend
fig.tight_layout() #tight margins
leg = ax.legend(loc='upper left', bbox_to_anchor=(1.02, 1), borderaxespad=0)
plt.draw() #to know size of legend
padLeft = ax.get_position().x0 * fig.get_size_inches()[0]
padBottom = ax.get_position().y0 * fig.get_size_inches()[1]
padTop = ( 1 - ax.get_position().y0 - ax.get_position().height ) * fig.get_size_inches()[1]
padRight = ( 1 - ax.get_position().x0 - ax.get_position().width ) * fig.get_size_inches()[0]
dpi = fig.get_dpi()
padLegend = ax.get_legend().get_frame().get_width() / dpi
widthAx = 3 #inches
heightAx = 3 #inches
widthTot = widthAx+padLeft+padRight+padLegend
heightTot = heightAx+padTop+padBottom
# resize ipython window (optional)
posScreenX = 1366/2-10 #pixel
posScreenY = 0 #pixel
canvasPadding = 6 #pixel
canvasBottom = 40 #pixel
ipythonWindowSize = '{0}x{1}+{2}+{3}'.format(int(round(widthTot*dpi))+2*canvasPadding
,int(round(heightTot*dpi))+2*canvasPadding+canvasBottom
,posScreenX,posScreenY)
fig.canvas._tkcanvas.master.geometry(ipythonWindowSize)
plt.draw() #to resize ipython window. Has to be done BEFORE figure resizing!
# set figure size and ax position
fig.set_size_inches(widthTot,heightTot)
ax.set_position([padLeft/widthTot, padBottom/heightTot, widthAx/widthTot, heightAx/heightTot])
plt.draw()
plt.show()
#--------------------------------------------------
#==================================================
I tried a very simple way, just make the figure a bit wider:
fig, ax = plt.subplots(1, 1, figsize=(a, b))
adjust a and b to a proper value such that the legend is included in the figure