Related
I'm struggling to deal with my plot margins in matplotlib. I've used the code below to produce my chart:
plt.imshow(g)
c = plt.colorbar()
c.set_label("Number of Slabs")
plt.savefig("OutputToUse.png")
However, I get an output figure with lots of white space on either side of the plot. I've searched google and read the matplotlib documentation, but I can't seem to find how to reduce this.
One way to automatically do this is the bbox_inches='tight' kwarg to plt.savefig.
E.g.
import matplotlib.pyplot as plt
import numpy as np
data = np.arange(3000).reshape((100,30))
plt.imshow(data)
plt.savefig('test.png', bbox_inches='tight')
Another way is to use fig.tight_layout()
import matplotlib.pyplot as plt
import numpy as np
xs = np.linspace(0, 1, 20); ys = np.sin(xs)
fig = plt.figure()
axes = fig.add_subplot(1,1,1)
axes.plot(xs, ys)
# This should be called after all axes have been added
fig.tight_layout()
fig.savefig('test.png')
You can adjust the spacing around matplotlib figures using the subplots_adjust() function:
import matplotlib.pyplot as plt
plt.plot(whatever)
plt.subplots_adjust(left=0.1, right=0.9, top=0.9, bottom=0.1)
This will work for both the figure on screen and saved to a file, and it is the right function to call even if you don't have multiple plots on the one figure.
The numbers are fractions of the figure dimensions, and will need to be adjusted to allow for the figure labels.
All you need is
plt.tight_layout()
before your output.
In addition to cutting down the margins, this also tightly groups the space between any subplots:
x = [1,2,3]
y = [1,4,9]
import matplotlib.pyplot as plt
fig = plt.figure()
subplot1 = fig.add_subplot(121)
subplot1.plot(x,y)
subplot2 = fig.add_subplot(122)
subplot2.plot(y,x)
fig.tight_layout()
plt.show()
Sometimes, the plt.tight_layout() doesn't give me the best view or the view I want. Then why don't plot with arbitrary margin first and do fixing the margin after plot?
Since we got nice WYSIWYG from there.
import matplotlib.pyplot as plt
fig,ax = plt.subplots(figsize=(8,8))
plt.plot([2,5,7,8,5,3,5,7,])
plt.show()
Then paste settings into margin function to make it permanent:
fig,ax = plt.subplots(figsize=(8,8))
plt.plot([2,5,7,8,5,3,5,7,])
fig.subplots_adjust(
top=0.981,
bottom=0.049,
left=0.042,
right=0.981,
hspace=0.2,
wspace=0.2
)
plt.show()
In case anybody wonders how how to get rid of the rest of the white margin after applying plt.tight_layout() or fig.tight_layout(): With the parameter pad (which is 1.08 by default), you're able to make it even tighter:
"Padding between the figure edge and the edges of subplots, as a fraction of the font size."
So for example
plt.tight_layout(pad=0.05)
will reduce it to a very small margin. Putting 0 doesn't work for me, as it makes the box of the subplot be cut off a little, too.
Just use ax = fig.add_axes([left, bottom, width, height])
if you want exact control of the figure layout. eg.
left = 0.05
bottom = 0.05
width = 0.9
height = 0.9
ax = fig.add_axes([left, bottom, width, height])
plt.savefig("circle.png", bbox_inches='tight',pad_inches=-1)
inspired by Sammys answer above:
margins = { # vvv margin in inches
"left" : 1.5 / figsize[0],
"bottom" : 0.8 / figsize[1],
"right" : 1 - 0.3 / figsize[0],
"top" : 1 - 1 / figsize[1]
}
fig.subplots_adjust(**margins)
Where figsize is the tuple that you used in fig = pyplot.figure(figsize=...)
With recent matplotlib versions you might want to try Constrained Layout:
constrained_layout automatically adjusts subplots and decorations like
legends and colorbars so that they fit in the figure window while
still preserving, as best they can, the logical layout requested by
the user.
constrained_layout is similar to tight_layout, but uses a constraint
solver to determine the size of axes that allows them to fit.
constrained_layout needs to be activated before any axes are added to
a figure.
Too bad pandas does not handle it well...
The problem with matplotlibs subplots_adjust is that the values you enter are relative to the x and y figsize of the figure. This example is for correct figuresizing for printing of a pdf:
For that, I recalculate the relative spacing to absolute values like this:
pyplot.subplots_adjust(left = (5/25.4)/figure.xsize, bottom = (4/25.4)/figure.ysize, right = 1 - (1/25.4)/figure.xsize, top = 1 - (3/25.4)/figure.ysize)
for a figure of 'figure.xsize' inches in x-dimension and 'figure.ysize' inches in y-dimension. So the whole figure has a left margin of 5 mm, bottom margin of 4 mm, right of 1 mm and top of 3 mm within the labels are placed. The conversion of (x/25.4) is done because I needed to convert mm to inches.
Note that the pure chart size of x will be "figure.xsize - left margin - right margin" and the pure chart size of y will be "figure.ysize - bottom margin - top margin" in inches
Other sniplets (not sure about these ones, I just wanted to provide the other parameters)
pyplot.figure(figsize = figureSize, dpi = None)
and
pyplot.savefig("outputname.eps", dpi = 100)
For me, the answers above did not work with matplotlib.__version__ = 1.4.3 on Win7. So, if we are only interested in the image itself (i.e., if we don't need annotations, axis, ticks, title, ylabel etc), then it's better to simply save the numpy array as image instead of savefig.
from pylab import *
ax = subplot(111)
ax.imshow(some_image_numpyarray)
imsave('test.tif', some_image_numpyarray)
# or, if the image came from tiff or png etc
RGBbuffer = ax.get_images()[0].get_array()
imsave('test.tif', RGBbuffer)
Also, using opencv drawing functions (cv2.line, cv2.polylines), we can do some drawings directly on the numpy array. http://docs.opencv.org/2.4/modules/core/doc/drawing_functions.html
# import pyplot
import matplotlib.pyplot as plt
# your code to plot the figure
# set tight margins
plt.margins(0.015, tight=True)
I am generating plots like this one:
When using less ticks, the plot fits nicely and the bars are wide enough to see them correctly. Nevertheless, when there are lots of ticks, instead of making the plot larger, it just compress the y axe, resulting in thin bars and overlapping tick text.
This is happening both for plt.show() and plt.save_fig().
Is there any solution so it plots the figure in a scale which guarantees that bars have the specified width, not more (if too few ticks) and not less (too many, overlapping)?
EDIT:
Yes, I'm using barh, and yes, I'm setting height to a fixed value (8):
height = 8
ax.barh(yvalues-width/2, xvalues, height=height, color='blue', align='center')
ax.barh(yvalues+width/2, xvalues, height=height, color='red', align='center')
I don't quite understand your code, it seems you do two plots with the same (only shifted) yvalues, but the image doesn't look so. And are you sure you want to shift by width/2 if you have align=center? Anyways, to changing the image size:
No, I am not sure there is no other way, but I don't see anything in the manual at a glance. To set image size by hand:
fig = plt.figure(figsize=(5, 80))
ax = fig.add_subplot(111)
...your_code
the size is in cm. You can compute it beforehand, try for example
import numpy as np
fig_height = (max(yvalues) - min(yvalues)) / np.diff(yvalue)
this would (approximately) set the minimum distance between ticks to a centimeter, which is too much, but try to adjust it.
I think of two solutions for your case:
If you are trying to plot a histogram, use hist function [1]. This will automatically bin your data. You can even plot multiple overlapping histograms as long as you set alpha value lower than 1. See this post
import matplotlib.pyplot as plt
import numpy as np
x = mu + sigma*np.random.randn(10000)
plt.hist(x, 50, normed=1, facecolor='green',
alpha=0.75, orientation='horizontal')
You can also identify interval of your axis ticks. This will place a tick every 10 items. But I doubt this will solve your problem.
import matplotlib.ticker as ticker
...
ax.yaxis.set_major_locator(ticker.MultipleLocator(10))
In a loop I have created some subplots using Matplotlib.
I noticed that in some instances the Y axis (particularly when displaying 0's) shows some negative numbers on the Y axis.
Is there a parameter to force the Y axis to display Positive numbers (i.e. from 0 upwards.
And a parameter to make the Y axis values either one or no decimal places. (display 2 instead of 2.0 or 2.00 (which it seems to do automatically depending on the data).
code:
for a in account_list:
f = plt.figure()
f.set_figheight(20)
f.set_figwidth(20)
f.sharex = True
f.sharey=True
left = 0.125 # the left side of the subplots of the figure
right = 0.9 # the right side of the subplots of the figure
bottom = 0.1 # the bottom of the subplots of the figure
top = 0.9 # the top of the subplots of the figure
wspace = 0.2 # the amount of width reserved for blank space between subplots
hspace = .8 # the amount of height reserved for white space between subplots
subplots_adjust(left=left, right=right, bottom=bottom, top=top, wspace=wspace, hspace=hspace)
count = 1
for h in headings:
sorted_data[sorted_data.account == a].ix[0:,['month_date',h]].plot(ax=f.add_subplot(7,3,count),legend=True,subplots=True,x='month_date',y=h)
import matplotlib.patches as mpatches
legend_name = mpatches.Patch(color='none', label=h)
plt.xlabel("")
ppl.legend(handles=[legend_name],bbox_to_anchor=(0.,1.2,1.0,.10), loc="center",ncol=2, mode="expand", borderaxespad=0.)
count = count + 1
EDIT
When I set the minimum and maximum for the Y axis using the below: I end up with the numbers repeating. For instance where the plot used to have on the Y axis: 1, 1.5, 2, 2.5 it now just has 1, 1, 2, 2, 3, 3, (I want each number to only appear once.
Maybe it has to do with the fact that my if statement is not working. I want all plots with a maximum Y axis value lower than ten to have their maximum Y value set to ten.
#set bottom Y axis limit to 0 and change number format to 1 dec place.
axis_data = f.gca()
from matplotlib.ticker import FormatStrFormatter
axis_data.yaxis.set_major_formatter(FormatStrFormatter('%.0f'))
#set only integers on Y axis >>>
import matplotlib.ticker as ticker
axis_data.xaxis.set_major_locator(ticker.MaxNLocator(integer=True))
#if y max < 10 make it ten
ymin,ymax = axis_data.get_ylim()
print(type(ymax))
if ymax <10:
axis_data.set_ylim(bottom=0.,top=ymax)
else:
axis_data.set_ylim(bottom=0.)
ax.set_ylim is the method which changes the limits on the y-axis of the subplot. In order to use this, you must have at your disposal an Axes instance (the one that is returned by plt.subplot); then you can set the lower limit to 0. Something along these lines should work:
ax = plt.subplot(1, 1, 1)
ax.plot(x, y)
ax.set_ylim(bottom=0.)
plt.show()
To change the tick label format, one convenient way is to use FormatStrFormatter:
from matplotlib.ticker import FormatStrFormatter
ax.yaxis.set_major_formatter(FormatStrFormatter('%.1f'))
EDIT:
I've looked at your code; you can get an Axes instance with fig.gca() method, then setting ylim and major_formatter as described above.
EDIT2:
The problem addressed it the comment can be solved by specifying the ticks locator. The locator used by default is AutoLocator, which is essentially the MaxNLocator with default values. One of the keywords taken by MaxNLocator is integer, which specifies only integer values for ticks and is False by default. Thus, to make ticks take only integer values, you need to define for y-axis MaxNLocator with integer=True. This should work:
from matplotlib.ticker import MaxNLocator
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
EDIT3:
And if you want to set your top y limit to 10, you should explicitly set it to 10:
if ymax < 10:
axis_data.set_ylim(bottom=0., top=10.)
else:
axis_data.set_ylim(bottom=0.)
The axis range can be limited to a particular range by xlim() or ylim(), e.g.
ylim([0,2])
and the axis ticks can be set manually to any string using xticks() or yticks(), e.g.
yticks([0,1,2],["0","1","2"])
Unfortunately there are many ways to adjust the ticks. So it can sometimes become a mess.
Try:
tickpos = [1,2,5]
py.axis(ymin = 0)
py.yticks(tickpos,tickpos)
If you don't use tickpos in both arguments and use two different arrays, the first array says where the ticks are. The second says what the tick labels are. This is useful if maybe you want the label to be a string (say it's a bar chart and you want the label to be category for the given bar). It's very dangerous if you have them as numbers because you may find that you move the tick location, but not the label. So you're labeling location y=4 as, say, 6. The following would be BAD
py.yticks([1,2,3], [1, 4, -1]) #DO NOT DO THIS
You can also set
py.axis(ymin = 0, ymax = 4, xmin = 3)
or other variants depending on what limits you want py to be free to set. As a note, sometimes I set ymax = 4.000001 if I want to be sure that the tick at y=4 appears.
I'm trying to make a grouped bar plot in matplotlib, following the example in the gallery. I use the following:
import matplotlib.pyplot as plt
plt.figure(figsize=(7,7), dpi=300)
xticks = [0.1, 1.1]
groups = [[1.04, 0.96],
[1.69, 4.02]]
group_labels = ["G1", "G2"]
num_items = len(group_labels)
ind = arange(num_items)
width = 0.1
s = plt.subplot(1,1,1)
for num, vals in enumerate(groups):
print "plotting: ", vals
group_len = len(vals)
gene_rects = plt.bar(ind, vals, width,
align="center")
ind = ind + width
num_groups = len(group_labels)
# Make label centered with respect to group of bars
# Is there a less complicated way?
offset = (num_groups / 2.) * width
xticks = arange(num_groups) + offset
s.set_xticks(xticks)
print "xticks: ", xticks
plt.xlim([0 - width, max(xticks) + (num_groups * width)])
s.set_xticklabels(group_labels)
My questions are:
How can I control the space between the groups of bars? Right now the spacing is huge and it looks silly. Note that I do not want to make the bars wider - I want them to have the same width, but be closer together.
How can I get the labels to be centered below the groups of bars? I tried to come up with some arithmetic calculations to position the xlabels in the right place (see code above) but it's still slightly off... it feels a bit like writing a plotting library rather than using one. How can this be fixed? (Is there a wrapper or built in utility for matplotlib where this is default behavior?)
EDIT: Reply to #mlgill: thank you for your answer. Your code is certainly much more elegant but still has the same issue, namely that the width of the bars and the spacing between the groups are not controlled separately. Your graph looks correct but the bars are far too wide -- it looks like an Excel graph -- and I wanted to make the bar thinner.
Width and margin are now linked, so if I try:
margin = 0.60
width = (1.-2.*margin)/num_items
It makes the bar skinnier, but brings the group far apart, so the plot again does not look right.
How can I make a grouped bar plot function that takes two parameters: the width of each bar, and the spacing between the bar groups, and plots it correctly like your code did, i.e. with the x-axis labels centered below the groups?
I think that since the user has to compute specific low-level layout quantities like margin and width, we are still basically writing a plotting library :)
Actually I think this problem is best solved by adjusting figsize and width; here is my output with figsize=(2,7) and width=0.3:
By the way, this type of thing becomes a lot simpler if you use pandas wrappers (i've also imported seaborn, not necessary for the solution, but makes the plot a lot prettier and more modern looking in my opinion):
import pandas as pd
import seaborn
seaborn.set()
df = pd.DataFrame(groups, index=group_labels)
df.plot(kind='bar', legend=False, width=0.8, figsize=(2,5))
plt.show()
The trick to both of your questions is understanding that bar graphs in Matplotlib expect each series (G1, G2) to have a total width of "1.0", counting margins on either side. Thus, it's probably easiest to set margins up and then calculate the width of each bar depending on how many of them there are per series. In your case, there are two bars per series.
Assuming you left align each bar, instead of center aligning them as you had done, this setup will result in series which span from 0.0 to 1.0, 1.0 to 2.0, and so forth on the x-axis. Thus, the exact center of each series, which is where you want your labels to appear, will be at 0.5, 1.5, etc.
I've cleaned up your code as there were a lot of extraneous variables. See comments within.
import matplotlib.pyplot as plt
import numpy as np
plt.figure(figsize=(7,7), dpi=300)
groups = [[1.04, 0.96],
[1.69, 4.02]]
group_labels = ["G1", "G2"]
num_items = len(group_labels)
# This needs to be a numpy range for xdata calculations
# to work.
ind = np.arange(num_items)
# Bar graphs expect a total width of "1.0" per group
# Thus, you should make the sum of the two margins
# plus the sum of the width for each entry equal 1.0.
# One way of doing that is shown below. You can make
# The margins smaller if they're still too big.
margin = 0.05
width = (1.-2.*margin)/num_items
s = plt.subplot(1,1,1)
for num, vals in enumerate(groups):
print "plotting: ", vals
# The position of the xdata must be calculated for each of the two data series
xdata = ind+margin+(num*width)
# Removing the "align=center" feature will left align graphs, which is what
# this method of calculating positions assumes
gene_rects = plt.bar(xdata, vals, width)
# You should no longer need to manually set the plot limit since everything
# is scaled to one.
# Also the ticks should be much simpler now that each group of bars extends from
# 0.0 to 1.0, 1.0 to 2.0, and so forth and, thus, are centered at 0.5, 1.5, etc.
s.set_xticks(ind+0.5)
s.set_xticklabels(group_labels)
I read an answer that Paul Ivanov posted on Nabble that might solve this problem with less complexity. Just set the index as below. This will increase the spacing between grouped columns.
ind = np.arange(0,12,2)
I'm struggling to deal with my plot margins in matplotlib. I've used the code below to produce my chart:
plt.imshow(g)
c = plt.colorbar()
c.set_label("Number of Slabs")
plt.savefig("OutputToUse.png")
However, I get an output figure with lots of white space on either side of the plot. I've searched google and read the matplotlib documentation, but I can't seem to find how to reduce this.
One way to automatically do this is the bbox_inches='tight' kwarg to plt.savefig.
E.g.
import matplotlib.pyplot as plt
import numpy as np
data = np.arange(3000).reshape((100,30))
plt.imshow(data)
plt.savefig('test.png', bbox_inches='tight')
Another way is to use fig.tight_layout()
import matplotlib.pyplot as plt
import numpy as np
xs = np.linspace(0, 1, 20); ys = np.sin(xs)
fig = plt.figure()
axes = fig.add_subplot(1,1,1)
axes.plot(xs, ys)
# This should be called after all axes have been added
fig.tight_layout()
fig.savefig('test.png')
You can adjust the spacing around matplotlib figures using the subplots_adjust() function:
import matplotlib.pyplot as plt
plt.plot(whatever)
plt.subplots_adjust(left=0.1, right=0.9, top=0.9, bottom=0.1)
This will work for both the figure on screen and saved to a file, and it is the right function to call even if you don't have multiple plots on the one figure.
The numbers are fractions of the figure dimensions, and will need to be adjusted to allow for the figure labels.
All you need is
plt.tight_layout()
before your output.
In addition to cutting down the margins, this also tightly groups the space between any subplots:
x = [1,2,3]
y = [1,4,9]
import matplotlib.pyplot as plt
fig = plt.figure()
subplot1 = fig.add_subplot(121)
subplot1.plot(x,y)
subplot2 = fig.add_subplot(122)
subplot2.plot(y,x)
fig.tight_layout()
plt.show()
Sometimes, the plt.tight_layout() doesn't give me the best view or the view I want. Then why don't plot with arbitrary margin first and do fixing the margin after plot?
Since we got nice WYSIWYG from there.
import matplotlib.pyplot as plt
fig,ax = plt.subplots(figsize=(8,8))
plt.plot([2,5,7,8,5,3,5,7,])
plt.show()
Then paste settings into margin function to make it permanent:
fig,ax = plt.subplots(figsize=(8,8))
plt.plot([2,5,7,8,5,3,5,7,])
fig.subplots_adjust(
top=0.981,
bottom=0.049,
left=0.042,
right=0.981,
hspace=0.2,
wspace=0.2
)
plt.show()
In case anybody wonders how how to get rid of the rest of the white margin after applying plt.tight_layout() or fig.tight_layout(): With the parameter pad (which is 1.08 by default), you're able to make it even tighter:
"Padding between the figure edge and the edges of subplots, as a fraction of the font size."
So for example
plt.tight_layout(pad=0.05)
will reduce it to a very small margin. Putting 0 doesn't work for me, as it makes the box of the subplot be cut off a little, too.
Just use ax = fig.add_axes([left, bottom, width, height])
if you want exact control of the figure layout. eg.
left = 0.05
bottom = 0.05
width = 0.9
height = 0.9
ax = fig.add_axes([left, bottom, width, height])
plt.savefig("circle.png", bbox_inches='tight',pad_inches=-1)
inspired by Sammys answer above:
margins = { # vvv margin in inches
"left" : 1.5 / figsize[0],
"bottom" : 0.8 / figsize[1],
"right" : 1 - 0.3 / figsize[0],
"top" : 1 - 1 / figsize[1]
}
fig.subplots_adjust(**margins)
Where figsize is the tuple that you used in fig = pyplot.figure(figsize=...)
With recent matplotlib versions you might want to try Constrained Layout:
constrained_layout automatically adjusts subplots and decorations like
legends and colorbars so that they fit in the figure window while
still preserving, as best they can, the logical layout requested by
the user.
constrained_layout is similar to tight_layout, but uses a constraint
solver to determine the size of axes that allows them to fit.
constrained_layout needs to be activated before any axes are added to
a figure.
Too bad pandas does not handle it well...
The problem with matplotlibs subplots_adjust is that the values you enter are relative to the x and y figsize of the figure. This example is for correct figuresizing for printing of a pdf:
For that, I recalculate the relative spacing to absolute values like this:
pyplot.subplots_adjust(left = (5/25.4)/figure.xsize, bottom = (4/25.4)/figure.ysize, right = 1 - (1/25.4)/figure.xsize, top = 1 - (3/25.4)/figure.ysize)
for a figure of 'figure.xsize' inches in x-dimension and 'figure.ysize' inches in y-dimension. So the whole figure has a left margin of 5 mm, bottom margin of 4 mm, right of 1 mm and top of 3 mm within the labels are placed. The conversion of (x/25.4) is done because I needed to convert mm to inches.
Note that the pure chart size of x will be "figure.xsize - left margin - right margin" and the pure chart size of y will be "figure.ysize - bottom margin - top margin" in inches
Other sniplets (not sure about these ones, I just wanted to provide the other parameters)
pyplot.figure(figsize = figureSize, dpi = None)
and
pyplot.savefig("outputname.eps", dpi = 100)
For me, the answers above did not work with matplotlib.__version__ = 1.4.3 on Win7. So, if we are only interested in the image itself (i.e., if we don't need annotations, axis, ticks, title, ylabel etc), then it's better to simply save the numpy array as image instead of savefig.
from pylab import *
ax = subplot(111)
ax.imshow(some_image_numpyarray)
imsave('test.tif', some_image_numpyarray)
# or, if the image came from tiff or png etc
RGBbuffer = ax.get_images()[0].get_array()
imsave('test.tif', RGBbuffer)
Also, using opencv drawing functions (cv2.line, cv2.polylines), we can do some drawings directly on the numpy array. http://docs.opencv.org/2.4/modules/core/doc/drawing_functions.html
# import pyplot
import matplotlib.pyplot as plt
# your code to plot the figure
# set tight margins
plt.margins(0.015, tight=True)