Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
rs301_splot.py
Go to the documentation of this file.
1# \file
2# \ingroup roostats_python_tutorials
3# \notebook -js
4# SPlot tutorial
5#
6# This tutorial shows an example of using SPlot to unfold two distributions.
7# The physics context for the example is that we want to know
8# the isolation distribution for real electrons from Z events
9# and fake electrons from QCD. Isolation is our 'control' variable.
10# To unfold them, we need a model for an uncorrelated variable that
11# discriminates between Z and QCD. To do this, we use the invariant
12# mass of two electrons. We model the Z with a Gaussian and the QCD
13# with a falling exponential.
14#
15# Note, since we don't have real data in this tutorial, we need to generate
16# toy data. To do that we need a model for the isolation variable for
17# both Z and QCD. This is only used to generate the toy data, and would
18# not be needed if we had real data.
19#
20# \macro_image
21# \macro_code
22# \macro_output
23#
24# \authors P. P., Kyle Cranmer (C++ version)
25
26
27import ROOT
28
29def AddModel(wspace):
30
31 # Make models for signal (Higgs) and background (Z+jets and QCD)
32 # In real life, this part requires an intelligent modeling
33 # of signal and background -- this is only an example.
34
35 # set range of observable
36 lowRange, highRange = 0.0, 200.0
37
38 # make a ROOT.RooRealVar for the observables
39 invMass = ROOT.RooRealVar("invMass", "M_inv", lowRange, highRange, "GeV")
40 isolation = ROOT.RooRealVar("isolation", "isolation", 0.0, 20.0, "GeV")
41
42 # --------------------------------------
43 # make 2-d model for Z including the invariant mass
44 # distribution and an isolation distribution which we want to
45 # unfold from QCD.
46 print(f"make z model")
47 # mass model for Z
48 mZ = ROOT.RooRealVar("mZ", "Z Mass", 91.2, lowRange, highRange)
49 sigmaZ = ROOT.RooRealVar("sigmaZ", "Width of Gaussian", 2, 0, 10, "GeV")
50 mZModel = ROOT.RooGaussian("mZModel", "Z+jets Model", invMass, mZ, sigmaZ)
51 # we know Z mass
52 mZ.setConstant()
53 # we leave the width of the Z free during the fit in this example.
54
55 # isolation model for Z. Only used to generate toy MC.
56 # the exponential is of the form exp(c*x). If we want
57 # the isolation to decay an e-fold every R GeV, we use
58 # c = -1/R.
59 zIsolDecayConst = ROOT.RooConstVar("zIsolDecayConst", "z isolation decay constant", -1)
60 zIsolationModel = ROOT.RooExponential("zIsolationModel", "z isolation model", isolation, zIsolDecayConst)
61
62 # make the combined Z model
63 zModel = ROOT.RooProdPdf("zModel", "2-d model for Z", ROOT.RooArgSet(mZModel, zIsolationModel))
64
65 # --------------------------------------
66 # make QCD model
67
68 print(f"make qcd model")
69 # mass model for QCD.
70 # the exponential is of the form exp(c*x). If we want
71 # the mass to decay an e-fold every R GeV, we use
72 # c = -1/R.
73 # We can leave this parameter free during the fit.
74 qcdMassDecayConst = ROOT.RooRealVar(
75 "qcdMassDecayConst", "Decay const for QCD mass spectrum", -0.01, -100, 100, "1/GeV"
76 )
77 qcdMassModel = ROOT.RooExponential("qcdMassModel", "qcd Mass Model", invMass, qcdMassDecayConst)
78
79 # isolation model for QCD. Only used to generate toy MC
80 # the exponential is of the form exp(c*x). If we want
81 # the isolation to decay an e-fold every R GeV, we use
82 # c = -1/R.
83 qcdIsolDecayConst = ROOT.RooConstVar("qcdIsolDecayConst", "Et resolution constant", -0.1)
84 qcdIsolationModel = ROOT.RooExponential("qcdIsolationModel", "QCD isolation model", isolation, qcdIsolDecayConst)
85
86 # make the 2-d model
87 qcdModel = ROOT.RooProdPdf("qcdModel", "2-d model for QCD", [qcdMassModel, qcdIsolationModel])
88
89
90 # combined model
91 # These variables represent the number of Z or QCD events
92 # They will be fitted.
93 zYield = ROOT.RooRealVar("zYield", "fitted yield for Z", 500, 0.0, 5000)
94 qcdYield = ROOT.RooRealVar("qcdYield", "fitted yield for QCD", 1000, 0.0, 10000)
95
96 # now make the combined models
97 print(f"make full model")
98 model = ROOT.RooAddPdf("model", "z+qcd background models", [zModel, qcdModel], [zYield, qcdYield])
99 massModel = ROOT.RooAddPdf("massModel", "z+qcd invariant mass model", [mZModel, qcdMassModel], [zYield, qcdYield])
100
101 # interesting for debugging and visualizing the model
102 model.graphVizTree("fullModel.dot")
103
104 print(f"import model: ")
105 model.Print()
106
107 wspace.Import(model)
108 wspace.Import(massModel, RecycleConflictNodes=True)
109
110
111# Add a toy dataset
112def AddData(wspace):
113
114 # get what we need out of the workspace to make toy data
115 model = wspace["model"]
116 invMass = wspace["invMass"]
117 isolation = wspace["isolation"]
118
119 # make the toy data
120 print("make data set and import to workspace")
121 wspace.Import(model.generate([invMass, isolation]), Rename="data")
122
123
124# Perform the plot
125def DoSPlot(wspace):
126
127 print(f"Calculate sWeights")
128
129 # get what we need out of the workspace to do the fit
130 massModel = wspace["massModel"]
131 zYield = wspace["zYield"]
132 qcdYield = wspace["qcdYield"]
133 data = wspace["data"]
134
135 # The sPlot technique requires that we fix the parameters
136 # of the model that are not yields after doing the fit.
137 #
138 # This *could* be done with the lines below, however this is taken care of
139 # by the ROOT.RooStats.SPlot constructor (or more precisely the AddSWeight
140 # method).
141 #
142 # sigmaZ = ws["sigmaZ")
143 # qcdMassDecayConst = ws["qcdMassDecayConst")
144 # sigmaZ.setConstant()
145 # qcdMassDecayConst.setConstant()
146
147 ROOT.RooMsgService.instance().setSilentMode(True)
148
149 print(f"\n\n------------------------------------------\nThe dataset before creating sWeights:\n")
150 data.Print()
151
152 ROOT.RooMsgService.instance().setGlobalKillBelow(ROOT.RooFit.ERROR)
153
154 # Now we use the SPlot class to add SWeights for the isolation variable to
155 # our data set based on fitting the yields to the invariant mass variable.
156 # Any keyword arguments will be forwarded to the underlying call to RooAbsPdf::fitTo().
157 sData = ROOT.RooStats.SPlot("sData", "An SPlot", data, massModel, [zYield, qcdYield], Strategy=0)
158
159 print(f"\n\nThe dataset after creating sWeights:\n")
160 data.Print()
161
162 # Check that our weights have the desired properties
163
164 print("\n\n------------------------------------------\n\nCheck SWeights:")
165 print("Yield of Z is\t", zYield.getVal(), ". From sWeights it is ")
166 print(sData.GetYieldFromSWeight("zYield"))
167
168 print("Yield of QCD is\t", qcdYield.getVal(), ". From sWeights it is : ")
169 print(sData.GetYieldFromSWeight("qcdYield"))
170
171 for i in range(10):
172 print("Weight for event: ", i, sData.GetSWeight(i, "zYield"))
173 print("qcd Weight: ", sData.GetSWeight(i, "qcdYield"))
174 print("Total Weight: ", sData.GetSumOfEventSWeight(i))
175
176 # import this new dataset with sWeights
177 wspace.Import(data, Rename="dataWithSWeights")
178
179 ROOT.RooMsgService.instance().setGlobalKillBelow(ROOT.RooFit.INFO)
180
181
182def MakePlots(wspace):
183
184 # Here we make plots of the discriminating variable (invMass) after the fit
185 # and of the control variable (isolation) after unfolding with sPlot.
186 # make our canvas
187 cdata = ROOT.TCanvas("sPlot", "sPlot demo", 400, 600)
188 cdata.Divide(1, 3)
189
190 # get what we need out of the workspace
191 model = wspace["model"]
192 zModel = wspace["zModel"]
193 qcdModel = wspace["qcdModel"]
194
195 isolation = wspace["isolation"]
196 invMass = wspace["invMass"]
197
198 # note, we get the dataset with sWeights
199 data = wspace["dataWithSWeights"]
200
201 # create weighted data sets
202 dataw_qcd = ROOT.RooDataSet(data.GetName(), data.GetTitle(), data, data.get(), "", "qcdYield_sw")
203 dataw_z = ROOT.RooDataSet(data.GetName(), data.GetTitle(), data, data.get(), "", "zYield_sw")
204
205 # plot invMass for data with full model and individual components overlaid
206 # cdata = TCanvas()
207 cdata.cd(1)
208 frame = invMass.frame(Title="Fit of model to discriminating variable")
209 data.plotOn(frame)
210 model.plotOn(frame, Name="FullModel")
211 model.plotOn(frame, Components=zModel, LineStyle="--", LineColor="r", Name="ZModel")
212 model.plotOn(frame, Components=qcdModel, LineStyle="--", LineColor="g", Name="QCDModel")
213
214 leg = ROOT.TLegend(0.11, 0.5, 0.5, 0.8)
215 leg.AddEntry(frame.findObject("FullModel"), "Full model", "L")
216 leg.AddEntry(frame.findObject("ZModel"), "Z model", "L")
217 leg.AddEntry(frame.findObject("QCDModel"), "QCD model", "L")
218 leg.SetBorderSize(0)
219 leg.SetFillStyle(0)
220
221 frame.Draw()
222 leg.DrawClone()
223
224 # Now use the sWeights to show isolation distribution for Z and QCD.
225 # The SPlot class can make this easier, but here we demonstrate in more
226 # detail how the sWeights are used. The SPlot class should make this
227 # very easy and needs some more development.
228
229 # Plot isolation for Z component.
230 # Do this by plotting all events weighted by the sWeight for the Z component.
231 # The SPlot class adds a new variable that has the name of the corresponding
232 # yield + "_sw".
233 cdata.cd(2)
234
235 frame2 = isolation.frame(Title="Isolation distribution with s weights to project out Z")
236 # Since the data are weighted, we use SumW2 to compute the errors.
237 dataw_z.plotOn(frame2, DataError="SumW2")
238 zModel.plotOn(frame2, LineStyle="--", LineColor="r")
239
240 frame2.Draw()
241
242 # Plot isolation for QCD component.
243 # Eg. plot all events weighted by the sWeight for the QCD component.
244 # The SPlot class adds a new variable that has the name of the corresponding
245 # yield + "_sw".
246 cdata.cd(3)
247 frame3 = isolation.frame(Title="Isolation distribution with s weights to project out QCD")
248 dataw_qcd.plotOn(frame3, DataError="SumW2")
249 qcdModel.plotOn(frame3, LineStyle="--", LineColor="g")
250
251 frame3.Draw()
252
253 cdata.SaveAs("rs301_splot.png")
254
255
256def rs301_splot():
257
258 # Create a workspace to manage the project.
259 wspace = ROOT.RooWorkspace("myWS")
260
261 # add the signal and background models to the workspace.
262 # Inside this function you will find a description of our model.
263 AddModel(wspace)
264
265 # add some toy data to the workspace
266 AddData(wspace)
267
268 # do sPlot.
269 # This will make a new dataset with sWeights added for every event.
270 DoSPlot(wspace)
271
272 # Make some plots showing the discriminating variable and
273 # the control variable after unfolding.
274 MakePlots(wspace)
275
276