Analysis Tutorial #9: Making plots

In this section we learn how to make nice plots from the histograms we have created using our analysis program. You can always plot the histograms using the ROOT command line or TBrowser and then save them as PDF or EPS file using the window menu. However, this is not very practical if one wants to overlay or combine multiple histograms into a single plot. Also, default ROOT plotting style is not very nice. Therefore, it is also worth to prepare some basic tools that will help you to make nice plots.

Firstly, let’s make a simple class that will help us to add plots, set their style, and combine them into stacks. We will call it simple “Plot” and put it in the “” module:

from ROOT import THStack, TCanvas, TFile, gPad
class Plot(object):
 """Plot helper class
 def __init__(self, *args): 
  """ The constructor. Possible arguments:
      1: fileName, histName - loads plot "histName" from file "fileName"
      2: plotList - list of Plot class instances. Merged into one plot
      3: plotList, True - list of Plot class instances. Combined using THStack
  self.hist = None
  self.drawOpt = ""
  self.xAxisTitle = None
  self.yAxisTitle = None
  self.zAxisTitle = None 

  # Case 1: load the plot from the ROOT file 
  if len(args)==2 and type(args[0])==str and type(args[1])==str:
   fileName = args[0]
   histName = args[1]
   f = TFile.Open(fileName)
   if f:
    self.hist = f.Get(histName)
    print "Loaded histogram '{}' from file '{}'".format(histName,fileName)
  # case 2: list of plots gets added into a single plot
  elif len(args)==1 and type(args[0])==list and len(args[0])>0:
   plotList = args[0]
   self.hist = plotList[0].hist.Clone()
   for plot in plotList[1:]:
  # case 3: use THStack class to combine plots
  elif len(args)==2 and type(args[0])==list and type(args[1])==bool and args[1] and len(args[0])>0:
   plotList = args[0]
   self.hist = THStack()
   for plot in plotList:
    # copy other plot attributes (drawOpt, axis titles)
    self.drawOpt = plotList[0].drawOpt
    # for THStack, axis titles can only be set after the plot is drawn :-(
    self.xAxisTitle = plotList[0].hist.GetXaxis().GetTitle()
    self.yAxisTitle = plotList[0].hist.GetYaxis().GetTitle()
    self.zAxisTitle = plotList[0].hist.GetZaxis().GetTitle()

  # case 4: error
   raise RuntimeError("Cannot process input arguments '{}'".format(args))
 def setStyleSolid(self, color):
  """ Helper method to set style: solid histogram
  if self.hist:
   self.drawOpt = "hist"

 def setStyleMarker(self, color, marker = 20):
  """ Helper method to set style: marker with errorbars
  if self.hist:
   self.drawOpt = ""

 def setStyleErrorbar(self, color, fillPattern = 3345):
  """ Helper method to set style: errorbar
  if self.hist:
   self.drawOpt = "E2"

 def draw(self, drawOpt = ""):
  self.hist.Draw("{} {}".format(self.drawOpt, drawOpt))
  # set axis titles if this is a THStack 
  if self.xAxisTitle: self.hist.GetXaxis().SetTitle(self.xAxisTitle)
  if self.yAxisTitle: self.hist.GetYaxis().SetTitle(self.yAxisTitle)
  if self.zAxisTitle: self.hist.GetZaxis().SetTitle(self.zAxisTitle)
  # we have to redraw axes
  • The class has an attribute “self.hist” which holds the instance of the ROOT histogram class (TH1D, TH2D, etc). The class “Plot” serves as a wrapper around this ROOT class to ease  manipulations with the histogram.
  • We want the constructor to do different things depending on what kind of arguments are passed in. This can be done using the “*args” construct. Variable “args” is interpreted as a “tuple” inside the constructor. We then do different things depending on what arguments are used when creating the “Plot” class instance:
    • Case 1: name of the ROOT file and name of the histogram are provided
      plot = Plot("fileName", "plotName").

      The ROOT file is opened and the plot “plotName” is loaded. It is stored in the member attribute “self.hist”

    • Case 2: The list of “Plot” class instances are provided.
      plot = Plot([plot1, plot2, plot3]).

      A copy (clone) of the first histogram is made and other histograms are added to it. The resulting histogram is sum of constituent plots and is stored in the “self.hist” attribute.

    • Case 3: Two arguments are provided, the first is the list of “Plot” class instances and the second is “True”.
      plot = Plot([plot1, plot2, plot3], True)

      The histograms are added to the THStack. THStack merges the histograms but also draws individual constituent histograms. This is useful when one wants to draw a distribution including its individual contributions.

  • The “setStyleSolid”, “setStyleMarker” and “setStyleErrorbar” allows to set some predefined often-used plot styles. In ROOT, setting the plot styles requires call of many different methods, so usually it’s worth to wrap them up into some easy to use function. The full documentation of the style options can be found here: TAttFill, TAttLine, and TAttMarker. In our example, “setStyleSolid” sets the style to solid histograms without the error bars. This style is usually used to draw histograms from Monte Carlo samples. “setStyleMarker” sets the style to the marker point graph. This is usually used to draw histograms from real data. Finally, “setStyleErrorbar” sets the style to the shaded band around the bin contents. This is usually used to draw errors on top of the Monte Carlo histograms
  • Finally, the “draw” method draws the histogram onto the screen. It’s mostly just the call of the underlying “Draw” method of the ROOT histogram class, but special attention has to be given to the “drawOpt” parameter depending on the chosen style (the errorbars are drawn with “E2” drawOpt while solid histograms with “hist” draw option. See here for full documentation of the draw options). Also, when drawing stacked histograms, the code needs to set the axes titles manually.

Now that we have the basic tool available, we can write a simple plotting script for the histograms we’ve created in the previous examples. We will do the following plots:

  • We will merge histograms from “ggH” and “VBFH” samples
  • We will make histograms separately for same-flavour and different-flavour events.
  • We will make a stack plot with the SF and DF histograms
  • We put an errorbar on top of the stacked plot
  • We do this for both “ditau_m” and “ditau_visM” plot
  • We will draw a legend onto each of the plot
  • We will save the result into the PDF file

So let’s do it. “”:

from Plot import Plot
from ROOT import TCanvas, TLegend, kRed, kGreen, kBlack

# set ATLAS plot style
from AtlasStyle import setStyle

# draw plots
histNames = ['ditau_m', 'ditau_visM']
sampleNames = ['ggH', 'VBFH']
canvases = []
plots = []
legends = []

for histName in histNames:

 # load individual plots
 plots_sf = []
 plots_df = []
 for sampleName in sampleNames:
  plots_sf += [ Plot("histograms.{}.AlgSF.root".format(sampleName), histName) ]
  plots_df += [ Plot("histograms.{}.AlgDF.root".format(sampleName), histName) ] 

 # add plots from two samples into a single plot
 plot_sf = Plot(plots_sf)
 plot_df = Plot(plots_df)

 # set style

 # create a THStack
 plot = Plot([plot_sf, plot_df], True)

 # create a canvas
 c = TCanvas(histName, histName, 800, 600)

 # draw

 # Create the same plot, but this time merging the histograms
 # We will set to the "errorbar style" and overlay the TH stack
 plotErr = Plot([plot_sf, plot_df])

 # draw legend
 legend = TLegend(0.6, 0.9, 0.9, 0.65)
 legend.AddEntry(plot_sf.hist, "SF leptons", "f")
 legend.AddEntry(plot_df.hist, "DF leptons", "f")
 legend.AddEntry(plotErr.hist, "Stat. uncertainty", "f")

 # save the canvas

 # save the instances so they are not deleted by the garbage collector
 canvases += [ c ]
 plots += [ plot, plotErr ]
 legends += [ legend ]

  • The ROOT classes for making canvas and legend are imported.
  • Because default ROOT plot style is not very nice, we import style used for the ATLAS experiment publications. It is configured in the module. This is not strictly needed, you can comment the appropriate lines out if you prefer the standard ROOT style.
  • We decide which plots we want to draw (“ditau_m”, “ditau_visM”) and which samples should be combined (“ggH”, “VBFH”). We also prepare some empty lists that will be needed later.
  • The first loop over “histNames”. The final plots are created for each item in this list.
  • The second loop over “sampleNames”. Inside the loop we create instances of our “Plots” class using the first type of the constructor. The instances are stored in lists “plots_sf” and “plots_df”.
  • When the lists are filled, we again create new instances of the “Plot” class, this time using the constructor version 2. The histograms from individual samples are now merged (added) into new plots “plot_sf” and “plot_df”. We also set the style for these plots.
  • Finally, we merge these two plots again, this time using the constructor version 3. The resulting plot is the THStack.
  • Before plots are drawn onto the screen, one has to create so called “canvas“. We name the canvas with the same name as the plot and we give it dimensions 800×600 pixels (ATLAS standard). The stack plot is drawn onto this canvas using the mothod “plot.draw()”.
  • Then we create a merged plot again, but this time without the stack option and we set the style using the “setStyleErrorbar” method. We draw it onto the same canvas by calling plotErr.draw(“same”). The “same” option is important. If omitted, the first histogram would be overwritten.
  • Finally, we create legend using TLegend class. The four numbers in the class’s constructor are relative coordinates on the canvas ranging from 0 to 1. One should always try to position the legend so that it does not cover the histograms!
  • “c.Print” saves the plot drawn on the canvas into the PDF file (you can also save to EPS, PNG and other formats. Just replace the file extension).
  • Finally, we store the instances of the plot, canvas, and legend classes. Python has a garbage collector and any instance that looses all the references is deleted. Because we have reached the end of the loop iteration, instances “plot”, “plotErr”, “c” and “legend” would get deleted if didn’t store them in the prepared lists. This is important only if you want to view the plots on the screen in an interactive windows. If you only care about the resulting PDF files, you do not need to preserve these instances

We can run the code in two ways. If we want the plots displayed on the screen as windows, you have to run python in interactive mode like this:

> python -i

On the other hand, if you only want to create PDF files and do not want to show anything on the screen (useful when working on a remote machine with slow internet connection) use option “-b” attached to the end of the script:

> python -b

If everything is done correctly, you should get two plots like these:

Screenshot 2021-07-15 at 17.01.14

Screenshot 2021-07-15 at 17.02.13

  • The errorbar is very small. This is because we used large samples so the statistical precision is very good.
  • Note that you can see a cutoff at 75 GeV for the SF leptons plot. This is because of the selection we have implemented couple of examples before. This is proof that things continue to work as expected :-)
  • Note that in this example we have only skimmed the very surface of the plotting business. When doing a real analysis, you will likely need many more operations than just adding histograms and creating stacks.

Next section →