Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
df105_WBosonAnalysis.py
Go to the documentation of this file.
1## \file
2## \ingroup tutorial_dataframe
3## \notebook -draw
4## The W boson mass analysis from the ATLAS Open Data release of 2020, with RDataFrame.
5##
6## This tutorial is the analysis of the W boson mass taken from the ATLAS Open Data release in 2020
7## (http://opendata.atlas.cern/release/2020/documentation/). The data was recorded with the ATLAS detector
8## during 2016 at a center-of-mass energy of 13 TeV. W bosons are produced frequently at the LHC and
9## are an important background to studies of Standard Model processes, for example the Higgs boson analyses.
10##
11## The analysis is translated to a RDataFrame workflow processing up to 60 GB of simulated events and data.
12## By default the analysis runs on a preskimmed dataset to reduce the runtime. The full dataset can be used with
13## the --full-dataset argument and you can also run only on a fraction of the original dataset using the argument --lumi-scale.
14##
15## \macro_image
16## \macro_code
17## \macro_output
18##
19## \date March 2020
20## \author Stefan Wunsch (KIT, CERN)
21
22import ROOT
23import json
24import argparse
25import os
26
27# Argument parsing
28parser = argparse.ArgumentParser()
29parser.add_argument("--lumi-scale", type=float, default=0.001,
30 help="Run only on a fraction of the total available 10 fb^-1 (only usable together with --full-dataset)")
31parser.add_argument("--full-dataset", action="store_true", default=False,
32 help="Use the full dataset (use --lumi-scale to run only on a fraction of it)")
33parser.add_argument("-b", action="store_true", default=False, help="Use ROOT batch mode")
34parser.add_argument("-t", action="store_true", default=False, help="Use implicit multi threading (for the full dataset only possible with --lumi-scale 1.0)")
35args = parser.parse_args()
36
37if args.b: ROOT.gROOT.SetBatch(True)
38if args.t: ROOT.EnableImplicitMT()
39
40if not args.full_dataset: lumi_scale = 0.001 # The preskimmed dataset contains only 0.01 fb^-1
41else: lumi_scale = args.lumi_scale
42lumi = 10064.0
43print('Run on data corresponding to {:.2f} fb^-1 ...'.format(lumi * lumi_scale / 1000.0))
44
45if args.full_dataset: dataset_path = "root://eospublic.cern.ch//eos/opendata/atlas/OutreachDatasets/2020-01-22"
46else: dataset_path = "root://eospublic.cern.ch//eos/root-eos/reduced_atlas_opendata/w"
47
48# Create a ROOT dataframe for each dataset
49# Note that we load the filenames from the external json file placed in the same folder than this script.
50files = json.load(open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "df105_WBosonAnalysis.json")))
51processes = files.keys()
52df = {}
53xsecs = {}
54sumws = {}
55samples = []
56for p in processes:
57 for d in files[p]:
58 # Construct the dataframes
59 folder = d[0] # Folder name
60 sample = d[1] # Sample name
61 xsecs[sample] = d[2] # Cross-section
62 sumws[sample] = d[3] # Sum of weights
63 num_events = d[4] # Number of events
64 samples.append(sample)
65 df[sample] = ROOT.RDataFrame("mini", "{}/1lep/{}/{}.1lep.root".format(dataset_path, folder, sample))
66
67 # Scale down the datasets if requested
68 if args.full_dataset and lumi_scale < 1.0:
69 df[sample] = df[sample].Range(int(num_events * lumi_scale))
70
71# Select events for the analysis
72
73# Just-in-time compile custom helper function performing complex computations
74ROOT.gInterpreter.Declare("""
75bool GoodElectronOrMuon(int type, float pt, float eta, float phi, float e, float trackd0pv, float tracksigd0pv, float z0)
76{
77 ROOT::Math::PtEtaPhiEVector p(pt / 1000.0, eta, phi, e / 1000.0);
78 if (abs(z0 * sin(p.theta())) > 0.5) return false;
79 if (type == 11 && abs(eta) < 2.46 && (abs(eta) < 1.37 || abs(eta) > 1.52)) {
80 if (abs(trackd0pv / tracksigd0pv) > 5) return false;
81 return true;
82 }
83 if (type == 13 && abs(eta) < 2.5) {
84 if (abs(trackd0pv / tracksigd0pv) > 3) return false;
85 return true;
86 }
87 return false;
88}
89""")
90
91for s in samples:
92 # Select events with a muon or electron trigger and with a missing transverse energy larger than 30 GeV
93 df[s] = df[s].Filter("trigE || trigM")\
94 .Filter("met_et > 30000")
95
96 # Find events with exactly one good lepton
97 df[s] = df[s].Define("good_lep", "lep_isTightID && lep_pt > 35000 && lep_ptcone30 / lep_pt < 0.1 && lep_etcone20 / lep_pt < 0.1")\
98 .Filter("ROOT::VecOps::Sum(good_lep) == 1")
99
100 # Apply additional cuts in case the lepton is an electron or muon
101 df[s] = df[s].Define("idx", "ROOT::VecOps::ArgMax(good_lep)")\
102 .Filter("GoodElectronOrMuon(lep_type[idx], lep_pt[idx], lep_eta[idx], lep_phi[idx], lep_E[idx], lep_trackd0pvunbiased[idx], lep_tracksigd0pvunbiased[idx], lep_z0[idx])")
103
104# Apply luminosity, scale factors and MC weights for simulated events
105for s in samples:
106 if "data" in s:
107 df[s] = df[s].Define("weight", "1.0")
108 else:
109 df[s] = df[s].Define("weight", "scaleFactor_ELE * scaleFactor_MUON * scaleFactor_LepTRIGGER * scaleFactor_PILEUP * mcWeight * {} / {} * {}".format(xsecs[s], sumws[s], lumi))
110
111# Compute transverse mass of the W boson using the lepton and the missing transverse energy and make a histogram
112ROOT.gInterpreter.Declare("""
113float ComputeTransverseMass(float met_et, float met_phi, float lep_pt, float lep_eta, float lep_phi, float lep_e)
114{
115 ROOT::Math::PtEtaPhiEVector met(met_et, 0, met_phi, met_et);
116 ROOT::Math::PtEtaPhiEVector lep(lep_pt, lep_eta, lep_phi, lep_e);
117 return (met + lep).Mt() / 1000.0;
118}
119""")
120
121histos = {}
122for s in samples:
123 df[s] = df[s].Define("mt_w", "ComputeTransverseMass(met_et, met_phi, lep_pt[idx], lep_eta[idx], lep_phi[idx], lep_E[idx])")
124 histos[s] = df[s].Histo1D(ROOT.RDF.TH1DModel(s, "mt_w", 24, 60, 180), "mt_w", "weight")
125
126# Run the event loop and merge histograms of the respective processes
127
128# RunGraphs allows to run the event loops of the separate RDataFrame graphs
129# concurrently. This results in an improved usage of the available resources
130# if each separate RDataFrame can not utilize all available resources, e.g.,
131# because not enough data is available.
132ROOT.RDF.RunGraphs([histos[s] for s in samples])
133
134def merge_histos(label):
135 h = None
136 for i, d in enumerate(files[label]):
137 t = histos[d[1]].GetValue()
138 if i == 0: h = t.Clone()
139 else: h.Add(t)
140 h.SetNameTitle(label, label)
141 return h
142
143data = merge_histos("data")
144wjets = merge_histos("wjets")
145zjets = merge_histos("zjets")
146ttbar = merge_histos("ttbar")
147diboson = merge_histos("diboson")
148singletop = merge_histos("singletop")
149
150# Create the plot
151
152# Set styles
153ROOT.gROOT.SetStyle("ATLAS")
154
155# Create canvas with pad
156c = ROOT.TCanvas("c", "", 600, 600)
157pad = ROOT.TPad("upper_pad", "", 0, 0, 1, 1)
158pad.SetTickx(False)
159pad.SetTicky(False)
160pad.SetLogy()
161pad.Draw()
162pad.cd()
163
164# Draw stack with MC contributions
165stack = ROOT.THStack()
166for h, color in zip(
167 [singletop, diboson, ttbar, zjets, wjets],
168 [(208, 240, 193), (195, 138, 145), (155, 152, 204), (248, 206, 104), (222, 90, 106)]):
169 h.SetLineWidth(1)
170 h.SetLineColor(1)
171 h.SetFillColor(ROOT.TColor.GetColor(*color))
172 stack.Add(h)
173stack.Draw("HIST")
174stack.GetXaxis().SetLabelSize(0.04)
175stack.GetXaxis().SetTitleSize(0.045)
176stack.GetXaxis().SetTitleOffset(1.3)
177stack.GetXaxis().SetTitle("m_{T}^{W#rightarrow l#nu} [GeV]")
178stack.GetYaxis().SetTitle("Events")
179stack.GetYaxis().SetLabelSize(0.04)
180stack.GetYaxis().SetTitleSize(0.045)
181stack.SetMaximum(1e10 * args.lumi_scale)
182stack.SetMinimum(1)
183
184# Draw data
185data.SetMarkerStyle(20)
186data.SetMarkerSize(1.2)
187data.SetLineWidth(2)
188data.SetLineColor(ROOT.kBlack)
189data.Draw("E SAME")
190
191# Add legend
192legend = ROOT.TLegend(0.60, 0.65, 0.92, 0.92)
193legend.SetTextFont(42)
194legend.SetFillStyle(0)
195legend.SetBorderSize(0)
196legend.SetTextSize(0.04)
197legend.SetTextAlign(32)
198legend.AddEntry(data, "Data" ,"lep")
199legend.AddEntry(wjets, "W+jets", "f")
200legend.AddEntry(zjets, "Z+jets", "f")
201legend.AddEntry(ttbar, "t#bar{t}", "f")
202legend.AddEntry(diboson, "Diboson", "f")
203legend.AddEntry(singletop, "Single top", "f")
204legend.Draw("SAME")
205
206# Add ATLAS label
207text = ROOT.TLatex()
208text.SetNDC()
209text.SetTextFont(72)
210text.SetTextSize(0.045)
211text.DrawLatex(0.21, 0.86, "ATLAS")
212text.SetTextFont(42)
213text.DrawLatex(0.21 + 0.16, 0.86, "Open Data")
214text.SetTextSize(0.04)
215text.DrawLatex(0.21, 0.80, "#sqrt{{s}} = 13 TeV, {:.2f} fb^{{-1}}".format(lumi * args.lumi_scale / 1000.0))
216
217# Save the plot
218c.SaveAs("df105_WBosonAnalysis.png")
219print("Saved figure to df105_WBosonAnalysis.png")
ROOT's RDataFrame offers a high level interface for analyses of data stored in TTrees,...
void RunGraphs(std::vector< RResultHandle > handles)
Trigger the event loop of multiple RDataFrames concurrently.
void EnableImplicitMT(UInt_t numthreads=0)
Enable ROOT's implicit multi-threading for all objects and methods that provide an internal paralleli...
Definition TROOT.cxx:525
A struct which stores the parameters of a TH1D.
Ta Range(0, 0, 1, 1)