Making matplotlib look like ggplot

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.

def rstyle(ax):
    """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.

from pylab import *
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.

from pylab import *
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.

from pylab import *
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!

13 thoughts on “Making matplotlib look like ggplot

  1. Tony

    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!

    Reply
  2. Colin

    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.

    def rstyle(ax, xlog=False, ylog=False):
        .....
        #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 ))
        .....
    Reply
    1. Bicubic Post author

      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.grid(True, 'minor', color='0.99', linestyle='-', linewidth=0.7)
      ax.patch.set_facecolor('0.94')

      Also, check out http://boronine.com/husl/ for dataset color choices.

      Reply
  3. Rob Story

    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!

    Reply
  4. Bicubic Post author

    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.

    Reply
  5. Pingback: » Python:making matplotlib graphs look like R by default?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>