When I first started using matplotlib, the output looked very crisp and polished compared to excel, however after seeing ggplot2, I realized that matplotlib’s default presentation settings leave a lot to be desired. I have put together a quick script that will restyle an axes to look more or less like ggplot2′s.
"""Styles an axes to appear like ggplot2
Must be called after all plot and axis manipulation operations have been carried out (needs to know final tick spacing)
"""
#set the style of the major and minor grid lines, filled blocks
ax.grid(True, 'major', color='w', linestyle='-', linewidth=1.4)
ax.grid(True, 'minor', color='0.92', linestyle='-', linewidth=0.7)
ax.patch.set_facecolor('0.85')
ax.set_axisbelow(True)
#set minor tick spacing to 1/2 of the major ticks
ax.xaxis.set_minor_locator(MultipleLocator( (plt.xticks()[0][1]-plt.xticks()[0][0]) / 2.0 ))
ax.yaxis.set_minor_locator(MultipleLocator( (plt.yticks()[0][1]-plt.yticks()[0][0]) / 2.0 ))
#remove axis border
for child in ax.get_children():
if isinstance(child, matplotlib.spines.Spine):
child.set_alpha(0)
#restyle the tick lines
for line in ax.get_xticklines() + ax.get_yticklines():
line.set_markersize(5)
line.set_color("gray")
line.set_markeredgewidth(1.4)
#remove the minor tick lines
for line in ax.xaxis.get_ticklines(minor=True) + ax.yaxis.get_ticklines(minor=True):
line.set_markersize(0)
#only show bottom left ticks, pointing out of axis
rcParams['xtick.direction'] = 'out'
rcParams['ytick.direction'] = 'out'
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
if ax.legend_ <> None:
lg = ax.legend_
lg.get_frame().set_linewidth(0)
lg.get_frame().set_alpha(0.5)
def rhist(ax, data, **keywords):
"""Creates a histogram with default style parameters to look like ggplot2
Is equivalent to calling ax.hist and accepts the same keyword parameters.
If style parameters are explicitly defined, they will not be overwritten
"""
defaults = {
'facecolor' : '0.3',
'edgecolor' : '0.28',
'linewidth' : '1',
'bins' : 100
}
for k, v in defaults.items():
if k not in keywords: keywords[k] = v
return ax.hist(data, **keywords)
def rbox(ax, data, **keywords):
"""Creates a ggplot2 style boxplot, is eqivalent to calling ax.boxplot with the following additions:
Keyword arguments:
colors -- array-like collection of colours for box fills
names -- array-like collection of box names which are passed on as tick labels
"""
hasColors = 'colors' in keywords
if hasColors:
colors = keywords['colors']
keywords.pop('colors')
if 'names' in keywords:
ax.tickNames = plt.setp(ax, xticklabels=keywords['names'] )
keywords.pop('names')
bp = ax.boxplot(data, **keywords)
pylab.setp(bp['boxes'], color='black')
pylab.setp(bp['whiskers'], color='black', linestyle = 'solid')
pylab.setp(bp['fliers'], color='black', alpha = 0.9, marker= 'o', markersize = 3)
pylab.setp(bp['medians'], color='black')
numBoxes = len(data)
for i in range(numBoxes):
box = bp['boxes'][i]
boxX = []
boxY = []
for j in range(5):
boxX.append(box.get_xdata()[j])
boxY.append(box.get_ydata()[j])
boxCoords = zip(boxX,boxY)
if hasColors:
boxPolygon = Polygon(boxCoords, facecolor = colors[i % len(colors)])
else:
boxPolygon = Polygon(boxCoords, facecolor = '0.95')
ax.add_patch(boxPolygon)
return bp
Usage is very simple, call rstyle(axes) just before showing or saving your figure. It is key to call it after all drawing and axis manipulation has been done, because it will be reading the major tick positions to work out where to put the minors.
import scipy.stats
t = arange(0.0, 100.0, 0.1)
s = sin(0.1*pi*t)*exp(-t*0.01)
fig = plt.figure()
ax = fig.add_subplot(111)
plot(t,s, label = "Original")
plot(t,s*2, label = "Doubled")
ax.legend()
rstyle(ax)
plt.show()
I have also included a function that creates a ggplot style histogram for you. This is nothing more than setting some default parameters to the hist function.
import scipy.stats
t = arange(0.0, 100.0, 0.1)
s = sin(0.1*pi*t)*exp(-t*0.01)
fig = plt.figure()
ax = fig.add_subplot(111)
data = scipy.stats.norm.rvs(size = 1000)
rhist(ax, data, label = "Histogram")
ax.legend()
rstyle(ax)
plt.show()
There is also a slightly more involved boxplot function which handles fill colours and names for you.
import scipy.stats
data = [scipy.stats.norm.rvs(size = 100), scipy.stats.norm.rvs(size = 100), scipy.stats.norm.rvs(size = 100)]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.legend()
rbox(ax, data, names = ("One", "Two", "Three"), colors = ('white', 'cyan'))
rstyle(ax)
Finally, with a bit of help from Justin Peel over at StackOverflow, you can get some really nice graphics going that you won’t be ashamed to put in your published material or presentation.
I have only used these scripts in my fairly limited scenario and there are several obvious things such as the requirement to pass an axes, the enforcement of minor ticks at 1/2 majors, and the fact that I haven’t really done much with the legend, but it should be enough to get you started in your projects. Happy visualizating!
Nice post.
There’s a package called mpltools that adds support for stylesheets using Matplotlib’s rc-parameters, which has a ggplot example (settings taken from a blog post by Huy Nguyen).
You can’t control everything with rc-parameters alone (e.g. you can’t prevent ticks on the top and right axis), so your approach is more versatile. Cheers!
That’s really neat, thanks Tony.
Thanks, I used this in a work project.
Thanks for creating this script. I used it for a graph I was putting in a paper. Both my abscissa and ordinate are on a log scale. Because of the log scale I would get the error “too many tick marks”. I just removed the minor locator settings in rstyle.
.....
#set minor tick spacing to 1/2 of the major ticks
if not xlog:
ax.xaxis.set_minor_locator(MultipleLocator( (plt.xticks()[0][1]-plt.xticks()[0][0]) / 2.0 ))
if not ylog:
ax.yaxis.set_minor_locator(MultipleLocator( (plt.yticks()[0][1]-plt.yticks()[0][0]) / 2.0 ))
.....
Hey Colin, glad to see this being used. I’d love to see your paper.
FYI, I’ve been using a slightly lighter scheme which I think looks better:
ax.patch.set_facecolor('0.94')
Also, check out http://boronine.com/husl/ for dataset color choices.
I actually ponder as to why you called this particular article, “Messy Mind
Would you mind if I fleshed this out a bit more and turned it into a complete repo (full credit to this blog post, of course)? It’s by far the nicest styling I’ve seen for matplotlib yet. Great work!
This is ace. Matplotlib also has a .rc file which can be used for styling [docs: http://matplotlib.org/users/customizing.html ]. Is it possible to port your styling to this .rc file format? There are a couple of details I’m not sure about, such as setting the major and minor ticks on the grid and so on.
Some things can be done via rcparms, others such as minor ticks can’t as far as I know. I’ll be getting in touch with matplotlib folks to look at options for turning this into a general purpose module.
ggplot2 is so powerful that I just brought all my stuff back over to R.
Is this usable without installing Polygon?
Just to let future readers know – :
style.use('ggplot')
Pingback: » Python:making matplotlib graphs look like R by default?
Pingback: 使matplotlib图形看起来像R默认? | CODE问答