Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
rs301_splot.py
Go to the documentation of this file.
1# \file
2# \ingroup tutorial_roostats
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 Kyle Cranmer (C++ version), and P. P. (Python translation)
25
26
27import ROOT
28
29
30def AddModel(wspace):
31
32 # Make models for signal (Higgs) and background (Z+jets and QCD)
33 # In real life, this part requires an intelligent modeling
34 # of signal and background -- this is only an example.
35
36 # set range of observable
37 lowRange, highRange = 0.0, 200.0
38
39 # make a ROOT.RooRealVar for the observables
40 invMass = ROOT.RooRealVar("invMass", "M_inv", lowRange, highRange, "GeV")
41 isolation = ROOT.RooRealVar("isolation", "isolation", 0.0, 20.0, "GeV")
42
43 # --------------------------------------
44 # make 2-d model for Z including the invariant mass
45 # distribution and an isolation distribution which we want to
46 # unfold from QCD.
47 print(f"make z model")
48 # mass model for Z
49 mZ = ROOT.RooRealVar("mZ", "Z Mass", 91.2, lowRange, highRange)
50 sigmaZ = ROOT.RooRealVar("sigmaZ", "Width of Gaussian", 2, 0, 10, "GeV")
51 mZModel = ROOT.RooGaussian("mZModel", "Z+jets Model", invMass, mZ, sigmaZ)
52 # we know Z mass
54 # we leave the width of the Z free during the fit in this example.
55
56 # isolation model for Z. Only used to generate toy MC.
57 # the exponential is of the form exp(c*x). If we want
58 # the isolation to decay an e-fold every R GeV, we use
59 # c = -1/R.
60 zIsolDecayConst = ROOT.RooConstVar("zIsolDecayConst", "z isolation decay constant", -1)
61 zIsolationModel = ROOT.RooExponential("zIsolationModel", "z isolation model", isolation, zIsolDecayConst)
62
63 # make the combined Z model
64 zModel = ROOT.RooProdPdf("zModel", "2-d model for Z", ROOT.RooArgSet(mZModel, zIsolationModel))
65
66 # --------------------------------------
67 # make QCD model
68
69 print(f"make qcd model")
70 # mass model for QCD.
71 # the exponential is of the form exp(c*x). If we want
72 # the mass to decay an e-fold every R GeV, we use
73 # c = -1/R.
74 # We can leave this parameter free during the fit.
75 qcdMassDecayConst = ROOT.RooRealVar(
76 "qcdMassDecayConst", "Decay const for QCD mass spectrum", -0.01, -100, 100, "1/GeV"
77 )
78 qcdMassModel = ROOT.RooExponential("qcdMassModel", "qcd Mass Model", invMass, qcdMassDecayConst)
79
80 # isolation model for QCD. Only used to generate toy MC
81 # the exponential is of the form exp(c*x). If we want
82 # the isolation to decay an e-fold every R GeV, we use
83 # c = -1/R.
84 qcdIsolDecayConst = ROOT.RooConstVar("qcdIsolDecayConst", "Et resolution constant", -0.1)
85 qcdIsolationModel = ROOT.RooExponential("qcdIsolationModel", "QCD isolation model", isolation, qcdIsolDecayConst)
86
87 # make the 2-d model
88 qcdModel = ROOT.RooProdPdf("qcdModel", "2-d model for QCD", [qcdMassModel, qcdIsolationModel])
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: ")
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
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
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.get(), Import=data, WeightVar="qcdYield_sw")
203 dataw_z = ROOT.RooDataSet(data.GetName(), data.GetTitle(), data.get(), Import=data, WeightVar="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")
220
221 frame.Draw()
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
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
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
277rs301_splot()
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.