Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
HybridStandardForm.py
Go to the documentation of this file.
1# \file
2# \ingroup tutorial_roostats
3# \notebook -js
4# A hypothesis testing example based on number counting with background uncertainty.
5#
6# A hypothesis testing example based on number counting
7# with background uncertainty.
8#
9# NOTE: This example is like HybridInstructional, but the model is more clearly
10# generalizable to an analysis with shapes. There is a lot of flexibility
11# for how one models a problem in RooFit/RooStats. Models come in a few
12# common forms:
13# - standard form: extended PDF of some discriminating variable m:
14# eg: P(m) ~ S*fs(m) + B*fb(m), with S+B events expected
15# in this case the dataset has N rows corresponding to N events
16# and the extended term is Pois(N|S+B)
17#
18# - fractional form: non-extended PDF of some discriminating variable m:
19# eg: P(m) ~ s*fs(m) + (1-s)*fb(m), where s is a signal fraction
20# in this case the dataset has N rows corresponding to N events
21# and there is no extended term
22#
23# - number counting form: in which there is no discriminating variable
24# and the counts are modeled directly (see HybridInstructional)
25# eg: P(N) = Pois(N|S+B)
26# in this case the dataset has 1 row corresponding to N events
27# and the extended term is the PDF itself.
28#
29# Here we convert the number counting form into the standard form by
30# introducing a dummy discriminating variable m with a uniform distribution.
31#
32# This example:
33# - demonstrates the usage of the HybridCalcultor (Part 4-6)
34# - demonstrates the numerical integration of RooFit (Part 2)
35# - validates the RooStats against an example with a known analytic answer
36# - demonstrates usage of different test statistics
37# - explains subtle choices in the prior used for hybrid methods
38# - demonstrates usage of different priors for the nuisance parameters
39# - demonstrates usage of PROOF
40#
41# The basic setup here is that a main measurement has observed x events with an
42# expectation of s+b. One can choose an ad hoc prior for the uncertainty on b,
43# or try to base it on an auxiliary measurement. In this case, the auxiliary
44# measurement (aka control measurement, sideband) is another counting experiment
45# with measurement y and expectation tau*b. With an 'original prior' on b,
46# called \f$ \eta(b) \f$ then one can obtain a posterior from the auxiliary measurement
47# \f$ \pi(b) = \eta(b) * Pois(y|tau*b) \f$. This is a principled choice for a prior
48# on b in the main measurement of x, which can then be treated in a hybrid
49# Bayesian/Frequentist way. Additionally, one can try to treat the two
50# measurements simultaneously, which is detailed in Part 6 of the tutorial.
51#
52# This tutorial is related to the FourBin.C tutorial in the modeling, but
53# focuses on hypothesis testing instead of interval estimation.
54#
55# More background on this 'prototype problem' can be found in the
56# following papers:
57#
58# - Evaluation of three methods for calculating statistical significance
59# when incorporating a systematic uncertainty into a test of the
60# background-only hypothesis for a Poisson process
61# Authors: Robert D. Cousins, James T. Linnemann, Jordan Tucker
62# http:#arxiv.org/abs/physics/0702156
63# NIM A 595 (2008) 480--501
64#
65# - Statistical Challenges for Searches for New Physics at the LHC
66# Author: Kyle Cranmer
67# http:#arxiv.org/abs/physics/0511028
68#
69# - Measures of Significance in HEP and Astrophysics
70# Author: J. T. Linnemann
71# http:#arxiv.org/abs/physics/0312059
72#
73# \macro_image
74# \macro_output
75# \macro_code
76#
77# \authors Kyle Cranmer, Wouter Verkerke, Sven Kreiss, and Jolly Chen (Python translation)
78
79import ROOT
80
81# User configuration parameters
82ntoys = 6000
83nToysRatio = 20 # ratio Ntoys Null/ntoys ALT
84
85# -------------------------------------------------------
86# A New Test Statistic Class for this example.
87# It simply returns the sum of the values in a particular
88# column of a dataset.
89# You can ignore this class and focus on the macro below
90
91ROOT.gInterpreter.Declare(
92 """
93using namespace RooFit;
94using namespace RooStats;
95
96class BinCountTestStat : public TestStatistic {
97public:
98 BinCountTestStat(void) : fColumnName("tmp") {}
99 BinCountTestStat(string columnName) : fColumnName(columnName) {}
100
101 virtual Double_t Evaluate(RooAbsData &data, RooArgSet & /*nullPOI*/)
102 {
103 // This is the main method in the interface
104 Double_t value = 0.0;
105 for (int i = 0; i < data.numEntries(); i++) {
106 value += data.get(i)->getRealValue(fColumnName.c_str());
107 }
108 return value;
109 }
110 virtual const TString GetVarName() const { return fColumnName; }
111
112private:
113 string fColumnName;
114
115protected:
116 ClassDef(BinCountTestStat, 1)
117};
118"""
119)
120
121
122# -----------------------------
123# The Actual Tutorial Macro
124# -----------------------------
125
126# This tutorial has 6 parts
127# Table of Contents
128# Setup
129# 1. Make the model for the 'prototype problem'
130# Special cases
131# 2. NOT RELEVANT HERE
132# 3. Use RooStats analytic solution for this problem
133# RooStats HybridCalculator -- can be generalized
134# 4. RooStats ToyMC version of 2. & 3.
135# 5. RooStats ToyMC with an equivalent test statistic
136# 6. RooStats ToyMC with simultaneous control & main measurement
137
138# Part 4 takes ~4 min without PROOF.
139# Part 5 takes about ~2 min with PROOF on 4 cores.
140# Of course, everything looks nicer with more toys, which takes longer.
141
142
143t = ROOT.TStopwatch()
144t.Start()
145c = ROOT.TCanvas()
146c.Divide(2, 2)
147
148# -----------------------------------------------------
149# P A R T 1 : D I R E C T I N T E G R A T I O N
150# ====================================================
151# Make model for prototype on/off problem
152# Pois(x | s+b) * Pois(y | tau b )
153# for Z_Gamma, use uniform prior on b.
154w = ROOT.RooWorkspace("w")
155
156# replace the pdf in 'number counting form'
157# w.factory("Poisson::px(x[150,0,500],sum::splusb(s[0,0,100],b[100,0,300]))")
158# with one in standard form. Now x is encoded in event count
159w.factory("Uniform::f(m[0,1])") # m is a dummy discriminating variable
160w.factory("ExtendPdf::px(f,sum::splusb(s[0,0,100],b[100,0.1,300]))")
161w.factory("Poisson::py(y[100,0.1,500],prod::taub(tau[1.],b))")
162w.factory("PROD::model(px,py)")
163w.factory("Uniform::prior_b(b)")
164
165# We will control the output level in a few places to avoid
166# verbose progress messages. We start by keeping track
167# of the current threshold on messages.
168msglevel = ROOT.RooMsgService.instance().globalKillBelow()
169
170# -----------------------------------------------
171# P A R T 3 : A N A L Y T I C R E S U L T
172# ==============================================
173# In this special case, the integrals are known analytically
174# and they are implemented in RooStats::NumberCountingUtils
175
176# analytic Z_Bi
177p_Bi = ROOT.RooStats.NumberCountingUtils.BinomialWithTauObsP(150, 100, 1)
178Z_Bi = ROOT.RooStats.NumberCountingUtils.BinomialWithTauObsZ(150, 100, 1)
179print("-----------------------------------------")
180print("Part 3")
181print(f"Z_Bi p-value (analytic): {p_Bi}")
182print(f"Z_Bi significance (analytic) {Z_Bi}")
183t.Stop()
184t.Print()
185t.Reset()
186t.Start()
187
188# --------------------------------------------------------------
189# P A R T 4 : U S I N G H Y B R I D C A L C U L A T O R
190# ==============================================================
191# Now we demonstrate the RooStats HybridCalculator.
192#
193# Like all RooStats calculators it needs the data and a ModelConfig
194# for the relevant hypotheses. Since we are doing hypothesis testing
195# we need a ModelConfig for the null (background only) and the alternate
196# (signal+background) hypotheses. We also need to specify the PDF,
197# the parameters of interest, and the observables. Furthermore, since
198# the parameter of interest is floating, we need to specify which values
199# of the parameter corresponds to the null and alternate (eg. s=0 and s=50)
200#
201# define some sets of variables obs={x} and poi={s}
202# note here, x is the only observable in the main measurement
203# and y is treated as a separate measurement, which is used
204# to produce the prior that will be used in this calculation
205# to randomize the nuisance parameters.
206w.defineSet("obs", "m")
207w.defineSet("poi", "s")
208
209# create a toy dataset with the x=150
210# data = ROOT.RooDataSet("d", "d", w.set("obs"))
211# data.add(w.set("obs"))
212data = w.pdf("px").generate(w.set("obs"), 150)
213
214# Part 3a : Setup ModelConfigs
215# -------------------------------------------------------
216# create the null (background-only) ModelConfig with s=0
217b_model = ROOT.RooStats.ModelConfig("B_model", w)
218b_model.SetPdf(w.pdf("px"))
219b_model.SetObservables(w.set("obs"))
220b_model.SetParametersOfInterest(w.set("poi"))
221w.var("s").setVal(0.0) # important!
222b_model.SetSnapshot(w.set("poi"))
223
224# create the alternate (signal+background) ModelConfig with s=50
225sb_model = ROOT.RooStats.ModelConfig("S+B_model", w)
226sb_model.SetPdf(w.pdf("px"))
227sb_model.SetObservables(w.set("obs"))
228sb_model.SetParametersOfInterest(w.set("poi"))
229w.var("s").setVal(50.0) # important!
230sb_model.SetSnapshot(w.set("poi"))
231
232# Part 3b : Choose Test Statistic
233# --------------------------------------------------------------
234# To make an equivalent calculation we need to use x as the test
235# statistic. This is not a built-in test statistic in RooStats
236# so we define it above. The new class inherits from the
237# RooStats.TestStatistic interface, and simply returns the value
238# of x in the dataset.
239
240eventCount = ROOT.RooStats.NumEventsTestStat(w.pdf("px"))
241
242# Part 3c : Define Prior used to randomize nuisance parameters
243# -------------------------------------------------------------
244#
245# The prior used for the hybrid calculator is the posterior
246# from the auxiliary measurement y. The model for the aux.
247# measurement is Pois(y|tau*b), thus the likelihood function
248# is proportional to (has the form of) a Gamma distribution.
249# if the 'original prior' $\eta(b)$ is uniform, then from
250# Bayes's theorem we have the posterior:
251# $\pi(b) = Pois(y|tau*b) * \eta(b)$
252# If $\eta(b)$ is flat, then we arrive at a Gamma distribution.
253# Since RooFit will normalize the PDF we can actually supply
254# py=Pois(y,tau*b) that will be equivalent to multiplying by a Uniform.
255#
256# Alternatively, we could explicitly use a gamma distribution:
257#
258# `w.factory("Gamma::gamma(b,sum::temp(y,1),1,0)")`
259#
260# or we can use some other ad hoc prior that do not naturally
261# follow from the known form of the auxiliary measurement.
262# The common choice is the equivalent Gaussian:
263w.factory("Gaussian::gauss_prior(b,y, expr::sqrty('sqrt(y)',y))")
264# this corresponds to the "Z_N" calculation.
265#
266# or one could use the analogous log-normal prior
267w.factory("Lognormal::lognorm_prior(b,y, expr::kappa('1+1./sqrt(y)',y))")
268#
269# Ideally, the HybridCalculator would be able to inspect the full
270# model Pois(x | s+b) * Pois(y | tau b ) and be given the original
271# prior $\eta(b)$ to form $\pi(b) = Pois(y|tau*b) * \eta(b)$.
272# This is not yet implemented because in the general case
273# it is not easy to identify the terms in the PDF that correspond
274# to the auxiliary measurement. So for now, it must be set
275# explicitly with:
276# - ForcePriorNuisanceNull()
277# - ForcePriorNuisanceAlt()
278# the name "ForcePriorNuisance" was chosen because we anticipate
279# this to be auto-detected, but will leave the option open
280# to force to a different prior for the nuisance parameters.
281
282# Part 3d : Construct and configure the HybridCalculator
283# -------------------------------------------------------
284
285hc1 = ROOT.RooStats.HybridCalculator(data, sb_model, b_model)
286toymcs1 = ROOT.RooStats.ToyMCSampler()
287toymcs1 = hc1.GetTestStatSampler()
288# toymcs1.SetNEventsPerToy(1) # because the model is in number counting form
289toymcs1.SetTestStatistic(eventCount) # set the test statistic
290# toymcs1.SetGenerateBinned()
291hc1.SetToys(ntoys, ntoys // nToysRatio)
292hc1.ForcePriorNuisanceAlt(w.pdf("py"))
293hc1.ForcePriorNuisanceNull(w.pdf("py"))
294# if you wanted to use the ad hoc Gaussian prior instead
295# ~~~
296# hc1.ForcePriorNuisanceAlt(w.pdf("gauss_prior"))
297# hc1.ForcePriorNuisanceNull(w.pdf("gauss_prior"))
298# ~~~
299# if you wanted to use the ad hoc log-normal prior instead
300# ~~~
301# hc1.ForcePriorNuisanceAlt(w.pdf("lognorm_prior"))
302# hc1.ForcePriorNuisanceNull(w.pdf("lognorm_prior"))
303# ~~~
304
305# these lines save current msg level and then kill any messages below ERROR
306ROOT.RooMsgService.instance().setGlobalKillBelow(ROOT.RooFit.ERROR)
307# Get the result
308r1 = hc1.GetHypoTest()
309ROOT.RooMsgService.instance().setGlobalKillBelow(msglevel) # set it back
310print("-----------------------------------------")
311print("Part 4")
312r1.Print()
313t.Stop()
314t.Print()
315t.Reset()
316t.Start()
317
318c.cd(2)
319p1 = ROOT.RooStats.HypoTestPlot(r1, 30) # 30 bins, TS is discrete
320p1.Draw()
321
322quit() # keep the running time short by default
323# -------------------------------------------------------------------------
324# # P A R T 5 : U S I N G H Y B R I D C A L C U L A T O R W I T H
325# # A N A L T E R N A T I V E T E S T S T A T I S T I C
326#
327# A likelihood ratio test statistics should be 1-to-1 with the count x
328# when the value of b is fixed in the likelihood. This is implemented
329# by the SimpleLikelihoodRatioTestStat
330
331slrts = ROOT.RooStats.SimpleLikelihoodRatioTestStat(b_model.GetPdf(), sb_model.GetPdf())
332slrts.SetNullParameters(b_model.GetSnapshot())
333slrts.SetAltParameters(sb_model.GetSnapshot())
334
335# HYBRID CALCULATOR
336hc2 = ROOT.RooStats.HybridCalculator(data, sb_model, b_model)
337toymcs2 = ROOT.RooStats.ToyMCSampler()
338toymcs2 = hc2.GetTestStatSampler()
339# toymcs2.SetNEventsPerToy(1)
340toymcs2.SetTestStatistic(slrts)
341# toymcs2.SetGenerateBinned()
342hc2.SetToys(ntoys, ntoys // nToysRatio)
343hc2.ForcePriorNuisanceAlt(w.pdf("py"))
344hc2.ForcePriorNuisanceNull(w.pdf("py"))
345# if you wanted to use the ad hoc Gaussian prior instead
346# ~~~
347# hc2.ForcePriorNuisanceAlt(w.pdf("gauss_prior"))
348# hc2.ForcePriorNuisanceNull(w.pdf("gauss_prior"))
349# ~~~
350# if you wanted to use the ad hoc log-normal prior instead
351# ~~~
352# hc2.ForcePriorNuisanceAlt(w.pdf("lognorm_prior"))
353# hc2.ForcePriorNuisanceNull(w.pdf("lognorm_prior"))
354# ~~~
355
356# these lines save current msg level and then kill any messages below ERROR
357ROOT.RooMsgService.instance().setGlobalKillBelow(ROOT.RooFit.ERROR)
358# Get the result
359r2 = hc2.GetHypoTest()
360print("-----------------------------------------")
361print("Part 5")
362r2.Print()
363t.Stop()
364t.Print()
365t.Reset()
366t.Start()
367ROOT.RooMsgService.instance().setGlobalKillBelow(msglevel)
368
369c.cd(3)
370p2 = ROOT.RooStats.HypoTestPlot(r2, 30) # 30 bins
371p2.Draw()
372
373quit() # so standard tutorial runs faster
374
375# ---------------------------------------------
376# OUTPUT W/O PROOF (2.66 GHz Intel Core i7)
377# ============================================
378
379# -----------------------------------------
380# Part 3
381# Z_Bi p-value (analytic): 0.00094165
382# Z_Bi significance (analytic): 3.10804
383# Real time 0:00:00, CP time 0.610
384
385# Results HybridCalculator_result:
386# - Null p-value = 0.00103333 +/- 0.000179406
387# - Significance = 3.08048 sigma
388# - Number of S+B toys: 1000
389# - Number of B toys: 30000
390# - Test statistic evaluated on data: 150
391# - CL_b: 0.998967 +/- 0.000185496
392# - CL_s+b: 0.495 +/- 0.0158106
393# - CL_s: 0.495512 +/- 0.0158272
394# Real time 0:04:43, CP time 283.780
395
396# With PROOF
397# -----------------------------------------
398# Part 5
399
400# Results HybridCalculator_result:
401# - Null p-value = 0.00105 +/- 0.000206022
402# - Significance = 3.07571 sigma
403# - Number of S+B toys: 1000
404# - Number of B toys: 20000
405# - Test statistic evaluated on data: 10.8198
406# - CL_b: 0.99895 +/- 0.000229008
407# - CL_s+b: 0.491 +/- 0.0158088
408# - CL_s: 0.491516 +/- 0.0158258
409# Real time 0:02:22, CP time 0.990
410
411# -------------------------------------------------------
412# Comparison
413# -------------------------------------------------------
414# LEPStatToolsForLHC
415# https:#plone4.fnal.gov:4430/P0/phystat/packages/0703002
416# Uses Gaussian prior
417# CL_b = 6.218476e-04, Significance = 3.228665 sigma
418#
419# -------------------------------------------------------
420# Comparison
421# -------------------------------------------------------
422# Asymptotic
423# From the value of the profile likelihood ratio (5.0338)
424# The significance can be estimated using Wilks's theorem
425# significance = sqrt(2*profileLR) = 3.1729 sigma