00001 // A hypothesis testing example based on number counting with background uncertainty. 00002 00003 00004 /* 00005 HybridStandardForm 00006 00007 Authors: Kyle Cranmer, Wouter Verkerke, and Sven Kreiss 00008 date May 2010 Part 1-3 00009 date Dec 2010 Part 4-6 00010 00011 A hypothesis testing example based on number counting 00012 with background uncertainty. 00013 00014 NOTE: This example is like HybridInstructional, but the model is more clearly 00015 generalizable to an analysis with shapes. There is a lot of flexability 00016 for how one models a problem in RooFit/RooStats. Models come in a few 00017 common forms: 00018 - standard form: extended PDF of some discriminating variable m: 00019 eg: P(m) ~ S*fs(m) + B*fb(m), with S+B events expected 00020 in this case the dataset has N rows corresponding to N events 00021 and the extended term is Pois(N|S+B) 00022 00023 - fractional form: non-extended PDF of some discriminating variable m: 00024 eg: P(m) ~ s*fs(m) + (1-s)*fb(m), where s is a signal fraction 00025 in this case the dataset has N rows corresponding to N events 00026 and there is no extended term 00027 00028 - number counting form: in which there is no discriminating variable 00029 and the counts are modeled directly (see HybridInstructional) 00030 eg: P(N) = Pois(N|S+B) 00031 in this case the dataset has 1 row corresponding to N events 00032 and the extended term is the PDF itself. 00033 00034 Here we convert the number counting form into the standard form by 00035 introducing a dummy discriminating variable m with a uniform distribution. 00036 00037 This example: 00038 - demonstrates the usage of the HybridCalcultor (Part 4-6) 00039 - demonstrates the numerical integration of RooFit (Part 2) 00040 - validates the RooStats against an example with a known analytic answer 00041 - demonstrates usage of different test statistics 00042 - explains subtle choices in the prior used for hybrid methods 00043 - demonstrates usage of different priors for the nuisance parameters 00044 - demonstrates usage of PROOF 00045 00046 The basic setup here is that a main measurement has observed x events with an 00047 expectation of s+b. One can choose an ad hoc prior for the uncertainty on b, 00048 or try to base it on an auxiliary measurement. In this case, the auxiliary 00049 measurement (aka control measurement, sideband) is another counting experiment 00050 with measurement y and expectation tau*b. With an 'original prior' on b, 00051 called \eta(b) then one can obtain a posterior from the auxiliary measurement 00052 \pi(b) = \eta(b) * Pois(y|tau*b). This is a principled choice for a prior 00053 on b in the main measurement of x, which can then be treated in a hybrid 00054 Bayesian/Frequentist way. Additionally, one can try to treat the two 00055 measurements simultaneously, which is detailed in Part 6 of the tutorial. 00056 00057 This tutorial is related to the FourBin.C tutorial in the modeling, but 00058 focuses on hypothesis testing instead of interval estimation. 00059 00060 More background on this 'prototype problem' can be found in the 00061 following papers: 00062 00063 Evaluation of three methods for calculating statistical significance 00064 when incorporating a systematic uncertainty into a test of the 00065 background-only hypothesis for a Poisson process 00066 Authors: Robert D. Cousins, James T. Linnemann, Jordan Tucker 00067 http://arxiv.org/abs/physics/0702156 00068 NIM A 595 (2008) 480--501 00069 00070 Statistical Challenges for Searches for New Physics at the LHC 00071 Authors: Kyle Cranmer 00072 http://arxiv.org/abs/physics/0511028 00073 00074 Measures of Significance in HEP and Astrophysics 00075 Authors: J. T. Linnemann 00076 http://arxiv.org/abs/physics/0312059 00077 */ 00078 00079 #include "RooGlobalFunc.h" 00080 #include "RooRealVar.h" 00081 #include "RooProdPdf.h" 00082 #include "RooWorkspace.h" 00083 #include "RooDataSet.h" 00084 #include "RooDataHist.h" 00085 #include "TCanvas.h" 00086 #include "TStopwatch.h" 00087 #include "TH1.h" 00088 #include "RooPlot.h" 00089 #include "RooMsgService.h" 00090 00091 #include "RooStats/NumberCountingUtils.h" 00092 00093 #include "RooStats/HybridCalculator.h" 00094 #include "RooStats/ToyMCSampler.h" 00095 #include "RooStats/HypoTestPlot.h" 00096 00097 #include "RooStats/NumEventsTestStat.h" 00098 #include "RooStats/ProfileLikelihoodTestStat.h" 00099 #include "RooStats/SimpleLikelihoodRatioTestStat.h" 00100 #include "RooStats/RatioOfProfiledLikelihoodsTestStat.h" 00101 #include "RooStats/MaxLikelihoodEstimateTestStat.h" 00102 00103 using namespace RooFit; 00104 using namespace RooStats; 00105 00106 ////////////////////////////////////////////////// 00107 // A New Test Statistic Class for this example. 00108 // It simply returns the sum of the values in a particular 00109 // column of a dataset. 00110 // You can ignore this class and focus on the macro below 00111 ////////////////////////////////////////////////// 00112 class BinCountTestStat : public TestStatistic { 00113 public: 00114 BinCountTestStat(void) : fColumnName("tmp") {} 00115 BinCountTestStat(string columnName) : fColumnName(columnName) {} 00116 00117 virtual Double_t Evaluate(RooAbsData& data, RooArgSet& /*nullPOI*/) { 00118 // This is the main method in the interface 00119 Double_t value = 0.0; 00120 for(int i=0; i < data.numEntries(); i++) { 00121 value += data.get(i)->getRealValue(fColumnName.c_str()); 00122 } 00123 return value; 00124 } 00125 virtual const TString GetVarName() const { return fColumnName; } 00126 00127 private: 00128 string fColumnName; 00129 00130 protected: 00131 ClassDef(BinCountTestStat,1) 00132 }; 00133 00134 ClassImp(BinCountTestStat) 00135 00136 ////////////////////////////////////////////////// 00137 // The Actual Tutorial Macro 00138 ////////////////////////////////////////////////// 00139 00140 void HybridStandardForm() { 00141 00142 // This tutorial has 6 parts 00143 // Table of Contents 00144 // Setup 00145 // 1. Make the model for the 'prototype problem' 00146 // Special cases 00147 // 2. NOT RELEVANT HERE 00148 // 3. Use RooStats analytic solution for this problem 00149 // RooStats HybridCalculator -- can be generalized 00150 // 4. RooStats ToyMC version of 2. & 3. 00151 // 5. RooStats ToyMC with an equivalent test statistic 00152 // 6. RooStats ToyMC with simultaneous control & main measurement 00153 00154 // Part 4 takes ~4 min without PROOF. 00155 // Part 5 takes about ~2 min with PROOF on 4 cores. 00156 // Of course, everything looks nicer with more toys, which takes longer. 00157 00158 00159 TStopwatch t; 00160 t.Start(); 00161 TCanvas *c = new TCanvas; 00162 c->Divide(2,2); 00163 00164 /////////////////////////////////////////////////////// 00165 // P A R T 1 : D I R E C T I N T E G R A T I O N 00166 ////////////////////////////////////////////////////// 00167 // Make model for prototype on/off problem 00168 // Pois(x | s+b) * Pois(y | tau b ) 00169 // for Z_Gamma, use uniform prior on b. 00170 RooWorkspace* w = new RooWorkspace("w"); 00171 00172 00173 // replace the pdf in 'number couting form' 00174 //w->factory("Poisson::px(x[150,0,500],sum::splusb(s[0,0,100],b[100,0,300]))"); 00175 // with one in standard form. Now x is encoded in event count 00176 w->factory("Uniform::f(m[0,1])");//m is a dummy discriminanting variable 00177 w->factory("ExtendPdf::px(f,sum::splusb(s[0,0,100],b[100,0,300]))"); 00178 w->factory("Poisson::py(y[100,0,500],prod::taub(tau[1.],b))"); 00179 w->factory("PROD::model(px,py)"); 00180 w->factory("Uniform::prior_b(b)"); 00181 00182 // We will control the output level in a few places to avoid 00183 // verbose progress messages. We start by keeping track 00184 // of the current threshold on messages. 00185 RooFit::MsgLevel msglevel = RooMsgService::instance().globalKillBelow(); 00186 00187 // Use PROOF-lite on multi-core machines 00188 ProofConfig* pc = NULL; 00189 // uncomment below if you want to use PROOF 00190 pc = new ProofConfig(*w, 4, "workers=4", kFALSE); // machine with 4 cores 00191 // pc = new ProofConfig(*w, 2, "workers=2", kFALSE); // machine with 2 cores 00192 00193 ///////////////////////////////////////////////// 00194 // P A R T 3 : A N A L Y T I C R E S U L T 00195 ///////////////////////////////////////////////// 00196 // In this special case, the integrals are known analytically 00197 // and they are implemented in RooStats::NumberCountingUtils 00198 00199 // analytic Z_Bi 00200 double p_Bi = NumberCountingUtils::BinomialWithTauObsP(150, 100, 1); 00201 double Z_Bi = NumberCountingUtils::BinomialWithTauObsZ(150, 100, 1); 00202 cout << "-----------------------------------------"<<endl; 00203 cout << "Part 3" << endl; 00204 std::cout << "Z_Bi p-value (analytic): " << p_Bi << std::endl; 00205 std::cout << "Z_Bi significance (analytic): " << Z_Bi << std::endl; 00206 t.Stop(); t.Print(); t.Reset(); t.Start(); 00207 00208 //////////////////////////////////////////////////////////////// 00209 // 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 00210 //////////////////////////////////////////////////////////////// 00211 // Now we demonstrate the RooStats HybridCalculator. 00212 // 00213 // Like all RooStats calculators it needs the data and a ModelConfig 00214 // for the relevant hypotheses. Since we are doing hypothesis testing 00215 // we need a ModelConfig for the null (background only) and the alternate 00216 // (signal+background) hypotheses. We also need to specify the PDF, 00217 // the parameters of interest, and the observables. Furthermore, since 00218 // the parameter of interest is floating, we need to specify which values 00219 // of the parameter corresponds to the null and alternate (eg. s=0 and s=50) 00220 // 00221 // define some sets of variables obs={x} and poi={s} 00222 // note here, x is the only observable in the main measurement 00223 // and y is treated as a separate measurement, which is used 00224 // to produce the prior that will be used in this calculation 00225 // to randomize the nuisance parameters. 00226 w->defineSet("obs","m"); 00227 w->defineSet("poi","s"); 00228 00229 // create a toy dataset with the x=150 00230 // RooDataSet *data = new RooDataSet("d", "d", *w->set("obs")); 00231 // data->add(*w->set("obs")); 00232 RooDataSet* data = w->pdf("px")->generate(*w->set("obs"),150); 00233 00234 ////////////////////////////////////////////////////////// 00235 // Part 3a : Setup ModelConfigs 00236 // create the null (background-only) ModelConfig with s=0 00237 ModelConfig b_model("B_model", w); 00238 b_model.SetPdf(*w->pdf("px")); 00239 b_model.SetObservables(*w->set("obs")); 00240 b_model.SetParametersOfInterest(*w->set("poi")); 00241 w->var("s")->setVal(0.0); // important! 00242 b_model.SetSnapshot(*w->set("poi")); 00243 00244 // create the alternate (signal+background) ModelConfig with s=50 00245 ModelConfig sb_model("S+B_model", w); 00246 sb_model.SetPdf(*w->pdf("px")); 00247 sb_model.SetObservables(*w->set("obs")); 00248 sb_model.SetParametersOfInterest(*w->set("poi")); 00249 w->var("s")->setVal(50.0); // important! 00250 sb_model.SetSnapshot(*w->set("poi")); 00251 00252 00253 ////////////////////////////////////////////////////////// 00254 // Part 3b : Choose Test Statistic 00255 // To make an equivalent calculation we need to use x as the test 00256 // statistic. This is not a built-in test statistic in RooStats 00257 // so we define it above. The new class inherits from the 00258 // RooStats::TestStatistic interface, and simply returns the value 00259 // of x in the dataset. 00260 00261 NumEventsTestStat eventCount(*w->pdf("px")); 00262 00263 ////////////////////////////////////////////////////////// 00264 // Part 3c : Define Prior used to randomize nuisance parameters 00265 // 00266 // The prior used for the hybrid calculator is the posterior 00267 // from the auxiliary measurement y. The model for the aux. 00268 // measurement is Pois(y|tau*b), thus the likleihood function 00269 // is proportional to (has the form of) a Gamma distribution. 00270 // if the 'original prior' \eta(b) is uniform, then from 00271 // Bayes's theorem we have the posterior: 00272 // \pi(b) = Pois(y|tau*b) * \eta(b) 00273 // If \eta(b) is flat, then we arrive at a Gamma distribution. 00274 // Since RooFit will normalize the PDF we can actually supply 00275 // py=Pois(y,tau*b) that will be equivalent to multiplying by a uniform. 00276 // 00277 // Alternatively, we could explicitly use a gamma distribution: 00278 // w->factory("Gamma::gamma(b,sum::temp(y,1),1,0)"); 00279 // 00280 // or we can use some other ad hoc prior that do not naturally 00281 // follow from the known form of the auxiliary measurement. 00282 // The common choice is the equivlaent Gaussian: 00283 w->factory("Gaussian::gauss_prior(b,y, expr::sqrty('sqrt(y)',y))"); 00284 // this corresponds to the "Z_N" calculation. 00285 // 00286 // or one could use the analogous log-normal prior 00287 w->factory("Lognormal::lognorm_prior(b,y, expr::kappa('1+1./sqrt(y)',y))"); 00288 // 00289 // Ideally, the HybridCalculator would be able to inspect the full 00290 // model Pois(x | s+b) * Pois(y | tau b ) and be given the original 00291 // prior \eta(b) to form \pi(b) = Pois(y|tau*b) * \eta(b). 00292 // This is not yet implemented because in the general case 00293 // it is not easy to identify the terms in the PDF that correspond 00294 // to the auxiliary measurement. So for now, it must be set 00295 // explicitly with: 00296 // - ForcePriorNuisanceNull() 00297 // - ForcePriorNuisanceAlt() 00298 // the name "ForcePriorNuisance" was chosen because we anticipate 00299 // this to be auto-detected, but will leave the option open 00300 // to force to a different prior for the nuisance parameters. 00301 00302 ////////////////////////////////////////////////////////// 00303 // Part 3d : Construct and configure the HybridCalculator 00304 00305 HybridCalculator hc1(*data, sb_model, b_model); 00306 ToyMCSampler *toymcs1 = (ToyMCSampler*)hc1.GetTestStatSampler(); 00307 // toymcs1->SetNEventsPerToy(1); // because the model is in number counting form 00308 toymcs1->SetTestStatistic(&eventCount); // set the test statistic 00309 // toymcs1->SetGenerateBinned(); 00310 hc1.SetToys(30000,1000); 00311 hc1.ForcePriorNuisanceAlt(*w->pdf("py")); 00312 hc1.ForcePriorNuisanceNull(*w->pdf("py")); 00313 // if you wanted to use the ad hoc Gaussian prior instead 00314 // hc1.ForcePriorNuisanceAlt(*w->pdf("gauss_prior")); 00315 // hc1.ForcePriorNuisanceNull(*w->pdf("gauss_prior")); 00316 // if you wanted to use the ad hoc log-normal prior instead 00317 // hc1.ForcePriorNuisanceAlt(*w->pdf("lognorm_prior")); 00318 // hc1.ForcePriorNuisanceNull(*w->pdf("lognorm_prior")); 00319 00320 // enable proof 00321 // proof not enabled for this test statistic 00322 // if(pc) toymcs1->SetProofConfig(pc); 00323 00324 // these lines save current msg level and then kill any messages below ERROR 00325 RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); 00326 // Get the result 00327 HypoTestResult *r1 = hc1.GetHypoTest(); 00328 RooMsgService::instance().setGlobalKillBelow(msglevel); // set it back 00329 cout << "-----------------------------------------"<<endl; 00330 cout << "Part 4" << endl; 00331 r1->Print(); 00332 t.Stop(); t.Print(); t.Reset(); t.Start(); 00333 00334 c->cd(2); 00335 HypoTestPlot *p1 = new HypoTestPlot(*r1,30); // 30 bins, TS is discrete 00336 p1->Draw(); 00337 00338 return; // keep the running time sort by default 00339 //////////////////////////////////////////////////////////////////////////// 00340 // 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 00341 // 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 00342 ///////////////////////////////////////////////////////////////////////////// 00343 // 00344 // A likelihood ratio test statistics should be 1-to-1 with the count x 00345 // when the value of b is fixed in the likelihood. This is implemented 00346 // by the SimpleLikelihoodRatioTestStat 00347 00348 SimpleLikelihoodRatioTestStat slrts(*b_model.GetPdf(),*sb_model.GetPdf()); 00349 slrts.SetNullParameters(*b_model.GetSnapshot()); 00350 slrts.SetAltParameters(*sb_model.GetSnapshot()); 00351 00352 // HYBRID CALCULATOR 00353 HybridCalculator hc2(*data, sb_model, b_model); 00354 ToyMCSampler *toymcs2 = (ToyMCSampler*)hc2.GetTestStatSampler(); 00355 // toymcs2->SetNEventsPerToy(1); 00356 toymcs2->SetTestStatistic(&slrts); 00357 // toymcs2->SetGenerateBinned(); 00358 hc2.SetToys(20000,1000); 00359 hc2.ForcePriorNuisanceAlt(*w->pdf("py")); 00360 hc2.ForcePriorNuisanceNull(*w->pdf("py")); 00361 // if you wanted to use the ad hoc Gaussian prior instead 00362 // hc2.ForcePriorNuisanceAlt(*w->pdf("gauss_prior")); 00363 // hc2.ForcePriorNuisanceNull(*w->pdf("gauss_prior")); 00364 // if you wanted to use the ad hoc log-normal prior instead 00365 // hc2.ForcePriorNuisanceAlt(*w->pdf("lognorm_prior")); 00366 // hc2.ForcePriorNuisanceNull(*w->pdf("lognorm_prior")); 00367 00368 // enable proof 00369 if(pc) toymcs2->SetProofConfig(pc); 00370 00371 // these lines save current msg level and then kill any messages below ERROR 00372 RooMsgService::instance().setGlobalKillBelow(RooFit::ERROR); 00373 // Get the result 00374 HypoTestResult *r2 = hc2.GetHypoTest(); 00375 cout << "-----------------------------------------"<<endl; 00376 cout << "Part 5" << endl; 00377 r2->Print(); 00378 t.Stop(); t.Print(); t.Reset(); t.Start(); 00379 RooMsgService::instance().setGlobalKillBelow(msglevel); 00380 00381 c->cd(3); 00382 HypoTestPlot *p2 = new HypoTestPlot(*r2,30); // 30 bins 00383 p2->Draw(); 00384 00385 return; // so standard tutorial runs faster 00386 00387 /////////////////////////////////////////////////////////// 00388 // OUTPUT W/O PROOF (2.66 GHz Intel Core i7) 00389 /////////////////////////////////////////////////////////// 00390 00391 /* 00392 ----------------------------------------- 00393 Part 3 00394 Z_Bi p-value (analytic): 0.00094165 00395 Z_Bi significance (analytic): 3.10804 00396 Real time 0:00:00, CP time 0.610 00397 00398 Results HybridCalculator_result: 00399 - Null p-value = 0.00103333 +/- 0.000179406 00400 - Significance = 3.08048 sigma 00401 - Number of S+B toys: 1000 00402 - Number of B toys: 30000 00403 - Test statistic evaluated on data: 150 00404 - CL_b: 0.998967 +/- 0.000185496 00405 - CL_s+b: 0.495 +/- 0.0158106 00406 - CL_s: 0.495512 +/- 0.0158272 00407 Real time 0:04:43, CP time 283.780 00408 00409 */ 00410 /* With PROOF 00411 ----------------------------------------- 00412 Part 5 00413 00414 Results HybridCalculator_result: 00415 - Null p-value = 0.00105 +/- 0.000206022 00416 - Significance = 3.07571 sigma 00417 - Number of S+B toys: 1000 00418 - Number of B toys: 20000 00419 - Test statistic evaluated on data: 10.8198 00420 - CL_b: 0.99895 +/- 0.000229008 00421 - CL_s+b: 0.491 +/- 0.0158088 00422 - CL_s: 0.491516 +/- 0.0158258 00423 Real time 0:02:22, CP time 0.990 00424 */ 00425 00426 ////////////////////////////////////////// 00427 // Comparison 00428 /////////////////////////////////////////// 00429 // LEPStatToolsForLHC 00430 // https://plone4.fnal.gov:4430/P0/phystat/packages/0703002 00431 // Uses Gaussian prior 00432 // CL_b = 6.218476e-04, Significance = 3.228665 sigma 00433 // 00434 ////////////////////////////////////////// 00435 // Comparison 00436 /////////////////////////////////////////// 00437 // Asymptotics 00438 // From the value of the profile likelihood ratio (5.0338) 00439 // The significance can be estimated using Wilks's theorem 00440 // significance = sqrt(2*profileLR) = 3.1729 sigma 00441 00442 00443 }