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 “Plot.py” 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) self.hist.SetDirectory(0) 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:]: self.hist.Add(plot.hist) # 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: self.hist.Add(plot.hist) # copy other plot attributes (drawOpt, axis titles) self.drawOpt = plotList[0].drawOpt self.hist.SetTitle(plotList[0].hist.GetTitle()) # 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 else: raise RuntimeError("Cannot process input arguments '{}'".format(args)) def setStyleSolid(self, color): """ Helper method to set style: solid histogram """ if self.hist: self.hist.SetFillColor(color) self.hist.SetFillStyle(1001) self.hist.SetMarkerStyle(0) self.drawOpt = "hist" def setStyleMarker(self, color, marker = 20): """ Helper method to set style: marker with errorbars """ if self.hist: self.hist.SetMarkerStyle(marker) self.hist.SetMarkerSize(1.) self.hist.SetMarkerColor(color) self.hist.SetLineColor(color) self.drawOpt = "" def setStyleErrorbar(self, color, fillPattern = 3345): """ Helper method to set style: errorbar """ if self.hist: self.hist.SetFillColor(color) self.hist.SetFillStyle(fillPattern) self.hist.SetMarkerStyle(0) 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 gPad.RedrawAxis()
- 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.
- Case 1: name of the ROOT file and name of the histogram are provided
- 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. “plotMe.py”:
from Plot import Plot from ROOT import TCanvas, TLegend, kRed, kGreen, kBlack # set ATLAS plot style from AtlasStyle import setStyle 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 plot_sf.setStyleSolid(kRed) plot_df.setStyleSolid(kGreen) # create a THStack plot = Plot([plot_sf, plot_df], True) # create a canvas c = TCanvas(histName, histName, 800, 600) # draw plot.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]) plotErr.setStyleErrorbar(kBlack) plotErr.draw("same") # 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") legend.Draw("same") # save the canvas c.Print("{}.pdf".format(histName)) # 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 AtlasStyle.py 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 plotMe.py
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 plotMe.py -b
If everything is done correctly, you should get two plots like these:
- 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.