Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
TwoSidedFrequentistUpperLimitWithBands.C
Go to the documentation of this file.
1 /// \file
2 /// \ingroup tutorial_roostats
3 /// \notebook -js
4 /// TwoSidedFrequentistUpperLimitWithBands
5 ///
6 ///
7 /// This is a standard demo that can be used with any ROOT file
8 /// prepared in the standard way. You specify:
9 /// - name for input ROOT file
10 /// - name of workspace inside ROOT file that holds model and data
11 /// - name of ModelConfig that specifies details for calculator tools
12 /// - name of dataset
13 ///
14 /// With default parameters the macro will attempt to run the
15 /// standard hist2workspace example and read the ROOT file
16 /// that it produces.
17 ///
18 /// You may want to control:
19 /// ~~~{.cpp}
20 /// double confidenceLevel=0.95;
21 /// double additionalToysFac = 1.;
22 /// int nPointsToScan = 12;
23 /// int nToyMC = 200;
24 /// ~~~
25 ///
26 /// This uses a modified version of the profile likelihood ratio as
27 /// a test statistic for upper limits (eg. test stat = 0 if muhat>mu).
28 ///
29 /// Based on the observed data, one defines a set of parameter points
30 /// to be tested based on the value of the parameter of interest
31 /// and the conditional MLE (eg. profiled) values of the nuisance parameters.
32 ///
33 /// At each parameter point, pseudo-experiments are generated using this
34 /// fixed reference model and then the test statistic is evaluated.
35 /// The auxiliary measurements (global observables) associated with the
36 /// constraint terms in nuisance parameters are also fluctuated in the
37 /// process of generating the pseudo-experiments in a frequentist manner
38 /// forming an 'unconditional ensemble'. One could form a 'conditional'
39 /// ensemble in which these auxiliary measurements are fixed. Note that the
40 /// nuisance parameters are not randomized, which is a Bayesian procedure.
41 /// Note, the nuisance parameters are floating in the fits. For each point,
42 /// the threshold that defines the 95% acceptance region is found. This
43 /// forms a "Confidence Belt".
44 ///
45 /// After constructing the confidence belt, one can find the confidence
46 /// interval for any particular dataset by finding the intersection
47 /// of the observed test statistic and the confidence belt. First
48 /// this is done on the observed data to get an observed 1-sided upper limt.
49 ///
50 /// Finally, there expected limit and bands (from background-only) are
51 /// formed by generating background-only data and finding the upper limit.
52 /// The background-only is defined as such that the nuisance parameters are
53 /// fixed to their best fit value based on the data with the signal rate fixed to 0.
54 /// The bands are done by hand for now, will later be part of the RooStats tools.
55 ///
56 /// On a technical note, this technique IS the generalization of Feldman-Cousins
57 /// with nuisance parameters.
58 ///
59 /// Building the confidence belt can be computationally expensive.
60 /// Once it is built, one could save it to a file and use it in a separate step.
61 ///
62 /// We can use PROOF to speed things along in parallel, however,
63 /// the test statistic has to be installed on the workers
64 /// so either turn off PROOF or include the modified test statistic
65 /// in your $ROOTSYS/roofit/roostats/inc directory,
66 /// add the additional line to the LinkDef.h file,
67 /// and recompile root.
68 ///
69 /// Note, if you have a boundary on the parameter of interest (eg. cross-section)
70 /// the threshold on the two-sided test statistic starts off at moderate values and plateaus.
71 ///
72 /// [#0] PROGRESS:Generation -- generated toys: 500 / 999
73 /// NeymanConstruction: Prog: 12/50 total MC = 39 this test stat = 0
74 /// SigXsecOverSM=0.69 alpha_syst1=0.136515 alpha_syst3=0.425415 beta_syst2=1.08496 [-1e+30, 0.011215] in interval = 1
75 ///
76 /// this tells you the values of the parameters being used to generate the pseudo-experiments
77 /// and the threshold in this case is 0.011215. One would expect for 95% that the threshold
78 /// would be ~1.35 once the cross-section is far enough away from 0 that it is essentially
79 /// unaffected by the boundary. As one reaches the last points in the scan, the
80 /// theshold starts to get artificially high. This is because the range of the parameter in
81 /// the fit is the same as the range in the scan. In the future, these should be independently
82 /// controlled, but they are not now. As a result the ~50% of pseudo-experiments that have an
83 /// upward fluctuation end up with muhat = muMax. Because of this, the upper range of the
84 /// parameter should be well above the expected upper limit... but not too high or one will
85 /// need a very large value of nPointsToScan to resolve the relevant region. This can be
86 /// improved, but this is the first version of this script.
87 ///
88 /// Important note: when the model includes external constraint terms, like a Gaussian
89 /// constraint to a nuisance parameter centered around some nominal value there is
90 /// a subtlety. The asymptotic results are all based on the assumption that all the
91 /// measurements fluctuate... including the nominal values from auxiliary measurements.
92 /// If these do not fluctuate, this corresponds to an "conditional ensemble". The
93 /// result is that the distribution of the test statistic can become very non-chi^2.
94 /// This results in thresholds that become very large.
95 ///
96 /// \macro_image
97 /// \macro_output
98 /// \macro_code
99 ///
100 /// \author Kyle Cranmer,Contributions from Aaron Armbruster, Haoshuang Ji, Haichen Wang and Daniel Whiteson
101 
102 #include "TFile.h"
103 #include "TROOT.h"
104 #include "TH1F.h"
105 #include "TCanvas.h"
106 #include "TSystem.h"
107 #include <iostream>
108 
109 #include "RooWorkspace.h"
110 #include "RooSimultaneous.h"
111 #include "RooAbsData.h"
112 
113 #include "RooStats/ModelConfig.h"
114 #include "RooStats/FeldmanCousins.h"
115 #include "RooStats/ToyMCSampler.h"
117 #include "RooStats/ConfidenceBelt.h"
118 
119 #include "RooStats/RooStatsUtils.h"
121 
122 using namespace RooFit;
123 using namespace RooStats;
124 using namespace std;
125 
126 bool useProof = false; // flag to control whether to use Proof
127 int nworkers = 0; // number of workers (default use all available cores)
128 
129 // -------------------------------------------------------
130 
131 void TwoSidedFrequentistUpperLimitWithBands(const char *infile = "", const char *workspaceName = "combined",
132  const char *modelConfigName = "ModelConfig",
133  const char *dataName = "obsData")
134 {
135 
136  double confidenceLevel = 0.95;
137  // degrade/improve number of pseudo-experiments used to define the confidence belt.
138  // value of 1 corresponds to default number of toys in the tail, which is 50/(1-confidenceLevel)
139  double additionalToysFac = 0.5;
140  int nPointsToScan = 20; // number of steps in the parameter of interest
141  int nToyMC = 200; // number of toys used to define the expected limit and band
142 
143  // -------------------------------------------------------
144  // First part is just to access a user-defined file
145  // or create the standard example file if it doesn't exist
146  const char *filename = "";
147  if (!strcmp(infile, "")) {
148  filename = "results/example_combined_GaussExample_model.root";
149  bool fileExist = !gSystem->AccessPathName(filename); // note opposite return code
150  // if file does not exists generate with histfactory
151  if (!fileExist) {
152 #ifdef _WIN32
153  cout << "HistFactory file cannot be generated on Windows - exit" << endl;
154  return;
155 #endif
156  // Normally this would be run on the command line
157  cout << "will run standard hist2workspace example" << endl;
158  gROOT->ProcessLine(".! prepareHistFactory .");
159  gROOT->ProcessLine(".! hist2workspace config/example.xml");
160  cout << "\n\n---------------------" << endl;
161  cout << "Done creating example input" << endl;
162  cout << "---------------------\n\n" << endl;
163  }
164 
165  } else
166  filename = infile;
167 
168  // Try to open the file
169  TFile *file = TFile::Open(filename);
170 
171  // if input file was specified byt not found, quit
172  if (!file) {
173  cout << "StandardRooStatsDemoMacro: Input file " << filename << " is not found" << endl;
174  return;
175  }
176 
177  // -------------------------------------------------------
178  // Now get the data and workspace
179 
180  // get the workspace out of the file
181  RooWorkspace *w = (RooWorkspace *)file->Get(workspaceName);
182  if (!w) {
183  cout << "workspace not found" << endl;
184  return;
185  }
186 
187  // get the modelConfig out of the file
188  ModelConfig *mc = (ModelConfig *)w->obj(modelConfigName);
189 
190  // get the modelConfig out of the file
191  RooAbsData *data = w->data(dataName);
192 
193  // make sure ingredients are found
194  if (!data || !mc) {
195  w->Print();
196  cout << "data or ModelConfig was not found" << endl;
197  return;
198  }
199 
200  cout << "Found data and ModelConfig:" << endl;
201  mc->Print();
202 
203  // -------------------------------------------------------
204  // Now get the POI for convenience
205  // you may want to adjust the range of your POI
206  RooRealVar *firstPOI = (RooRealVar *)mc->GetParametersOfInterest()->first();
207  /* firstPOI->setMin(0);*/
208  /* firstPOI->setMax(10);*/
209 
210  // -------------------------------------------------------
211  // create and use the FeldmanCousins tool
212  // to find and plot the 95% confidence interval
213  // on the parameter of interest as specified
214  // in the model config
215  // REMEMBER, we will change the test statistic
216  // so this is NOT a Feldman-Cousins interval
217  FeldmanCousins fc(*data, *mc);
218  fc.SetConfidenceLevel(confidenceLevel);
219  fc.AdditionalNToysFactor(additionalToysFac); // improve sampling that defines confidence belt
220  // fc.UseAdaptiveSampling(true); // speed it up a bit, but don't use for expected limits
221  fc.SetNBins(nPointsToScan); // set how many points per parameter of interest to scan
222  fc.CreateConfBelt(true); // save the information in the belt for plotting
223 
224  // -------------------------------------------------------
225  // Feldman-Cousins is a unified limit by definition
226  // but the tool takes care of a few things for us like which values
227  // of the nuisance parameters should be used to generate toys.
228  // so let's just change the test statistic and realize this is
229  // no longer "Feldman-Cousins" but is a fully frequentist Neyman-Construction.
230  // fc.GetTestStatSampler()->SetTestStatistic(&onesided);
231  // ((ToyMCSampler*) fc.GetTestStatSampler())->SetGenerateBinned(true);
232  ToyMCSampler *toymcsampler = (ToyMCSampler *)fc.GetTestStatSampler();
233  ProfileLikelihoodTestStat *testStat = dynamic_cast<ProfileLikelihoodTestStat *>(toymcsampler->GetTestStatistic());
234 
235  // Since this tool needs to throw toy MC the PDF needs to be
236  // extended or the tool needs to know how many entries in a dataset
237  // per pseudo experiment.
238  // In the 'number counting form' where the entries in the dataset
239  // are counts, and not values of discriminating variables, the
240  // datasets typically only have one entry and the PDF is not
241  // extended.
242  if (!mc->GetPdf()->canBeExtended()) {
243  if (data->numEntries() == 1)
244  fc.FluctuateNumDataEntries(false);
245  else
246  cout << "Not sure what to do about this model" << endl;
247  }
248 
249  // We can use PROOF to speed things along in parallel
250  // However, the test statistic has to be installed on the workers
251  // so either turn off PROOF or include the modified test statistic
252  // in your $ROOTSYS/roofit/roostats/inc directory,
253  // add the additional line to the LinkDef.h file,
254  // and recompile root.
255  if (useProof) {
256  ProofConfig pc(*w, nworkers, "", false);
257  toymcsampler->SetProofConfig(&pc); // enable proof
258  }
259 
260  if (mc->GetGlobalObservables()) {
261  cout << "will use global observables for unconditional ensemble" << endl;
262  mc->GetGlobalObservables()->Print();
263  toymcsampler->SetGlobalObservables(*mc->GetGlobalObservables());
264  }
265 
266  // Now get the interval
267  PointSetInterval *interval = fc.GetInterval();
268  ConfidenceBelt *belt = fc.GetConfidenceBelt();
269 
270  // print out the interval on the first Parameter of Interest
271  cout << "\n95% interval on " << firstPOI->GetName() << " is : [" << interval->LowerLimit(*firstPOI) << ", "
272  << interval->UpperLimit(*firstPOI) << "] " << endl;
273 
274  // get observed UL and value of test statistic evaluated there
275  RooArgSet tmpPOI(*firstPOI);
276  double observedUL = interval->UpperLimit(*firstPOI);
277  firstPOI->setVal(observedUL);
278  double obsTSatObsUL = fc.GetTestStatSampler()->EvaluateTestStatistic(*data, tmpPOI);
279 
280  // Ask the calculator which points were scanned
281  RooDataSet *parameterScan = (RooDataSet *)fc.GetPointsToScan();
282  RooArgSet *tmpPoint;
283 
284  // make a histogram of parameter vs. threshold
285  TH1F *histOfThresholds =
286  new TH1F("histOfThresholds", "", parameterScan->numEntries(), firstPOI->getMin(), firstPOI->getMax());
287  histOfThresholds->GetXaxis()->SetTitle(firstPOI->GetName());
288  histOfThresholds->GetYaxis()->SetTitle("Threshold");
289 
290  // loop through the points that were tested and ask confidence belt
291  // what the upper/lower thresholds were.
292  // For FeldmanCousins, the lower cut off is always 0
293  for (Int_t i = 0; i < parameterScan->numEntries(); ++i) {
294  tmpPoint = (RooArgSet *)parameterScan->get(i)->clone("temp");
295  // cout <<"get threshold"<<endl;
296  double arMax = belt->GetAcceptanceRegionMax(*tmpPoint);
297  double poiVal = tmpPoint->getRealValue(firstPOI->GetName());
298  histOfThresholds->Fill(poiVal, arMax);
299  }
300  TCanvas *c1 = new TCanvas();
301  c1->Divide(2);
302  c1->cd(1);
303  histOfThresholds->SetMinimum(0);
304  histOfThresholds->Draw();
305  c1->cd(2);
306 
307  // -------------------------------------------------------
308  // Now we generate the expected bands and power-constraint
309 
310  // First: find parameter point for mu=0, with conditional MLEs for nuisance parameters
311  RooAbsReal *nll = mc->GetPdf()->createNLL(*data);
312  RooAbsReal *profile = nll->createProfile(*mc->GetParametersOfInterest());
313  firstPOI->setVal(0.);
314  profile->getVal(); // this will do fit and set nuisance parameters to profiled values
315  RooArgSet *poiAndNuisance = new RooArgSet();
316  if (mc->GetNuisanceParameters())
317  poiAndNuisance->add(*mc->GetNuisanceParameters());
318  poiAndNuisance->add(*mc->GetParametersOfInterest());
319  w->saveSnapshot("paramsToGenerateData", *poiAndNuisance);
320  RooArgSet *paramsToGenerateData = (RooArgSet *)poiAndNuisance->snapshot();
321  cout << "\nWill use these parameter points to generate pseudo data for bkg only" << endl;
322  paramsToGenerateData->Print("v");
323 
324  RooArgSet unconditionalObs;
325  unconditionalObs.add(*mc->GetObservables());
326  unconditionalObs.add(*mc->GetGlobalObservables()); // comment this out for the original conditional ensemble
327 
328  double CLb = 0;
329  double CLbinclusive = 0;
330 
331  // Now we generate background only and find distribution of upper limits
332  TH1F *histOfUL = new TH1F("histOfUL", "", 100, 0, firstPOI->getMax());
333  histOfUL->GetXaxis()->SetTitle("Upper Limit (background only)");
334  histOfUL->GetYaxis()->SetTitle("Entries");
335  for (int imc = 0; imc < nToyMC; ++imc) {
336 
337  // set parameters back to values for generating pseudo data
338  // cout << "\n get current nuis, set vals, print again" << endl;
339  w->loadSnapshot("paramsToGenerateData");
340  // poiAndNuisance->Print("v");
341 
342  RooDataSet *toyData = 0;
343  // now generate a toy dataset for the main measurement
344  if (!mc->GetPdf()->canBeExtended()) {
345  if (data->numEntries() == 1)
346  toyData = mc->GetPdf()->generate(*mc->GetObservables(), 1);
347  else
348  cout << "Not sure what to do about this model" << endl;
349  } else {
350  // cout << "generating extended dataset"<<endl;
351  toyData = mc->GetPdf()->generate(*mc->GetObservables(), Extended());
352  }
353 
354  // generate global observables
355  // need to be careful for simpdf.
356  // In ROOT 5.28 there is a problem with generating global observables
357  // with a simultaneous PDF. In 5.29 there is a solution with
358  // RooSimultaneous::generateSimGlobal, but this may change to
359  // the standard generate interface in 5.30.
360 
361  RooSimultaneous *simPdf = dynamic_cast<RooSimultaneous *>(mc->GetPdf());
362  if (!simPdf) {
363  RooDataSet *one = mc->GetPdf()->generate(*mc->GetGlobalObservables(), 1);
364  const RooArgSet *values = one->get();
365  RooArgSet *allVars = mc->GetPdf()->getVariables();
366  *allVars = *values;
367  delete allVars;
368  delete one;
369  } else {
370  RooDataSet *one = simPdf->generateSimGlobal(*mc->GetGlobalObservables(), 1);
371  const RooArgSet *values = one->get();
372  RooArgSet *allVars = mc->GetPdf()->getVariables();
373  *allVars = *values;
374  delete allVars;
375  delete one;
376  }
377 
378  // get test stat at observed UL in observed data
379  firstPOI->setVal(observedUL);
380  double toyTSatObsUL = fc.GetTestStatSampler()->EvaluateTestStatistic(*toyData, tmpPOI);
381  // toyData->get()->Print("v");
382  // cout <<"obsTSatObsUL " <<obsTSatObsUL << "toyTS " << toyTSatObsUL << endl;
383  if (obsTSatObsUL < toyTSatObsUL) // not sure about <= part yet
384  CLb += (1.) / nToyMC;
385  if (obsTSatObsUL <= toyTSatObsUL) // not sure about <= part yet
386  CLbinclusive += (1.) / nToyMC;
387 
388  // loop over points in belt to find upper limit for this toy data
389  double thisUL = 0;
390  for (Int_t i = 0; i < parameterScan->numEntries(); ++i) {
391  tmpPoint = (RooArgSet *)parameterScan->get(i)->clone("temp");
392  double arMax = belt->GetAcceptanceRegionMax(*tmpPoint);
393  firstPOI->setVal(tmpPoint->getRealValue(firstPOI->GetName()));
394  // double thisTS = profile->getVal();
395  double thisTS = fc.GetTestStatSampler()->EvaluateTestStatistic(*toyData, tmpPOI);
396 
397  // cout << "poi = " << firstPOI->getVal()
398  // << " max is " << arMax << " this profile = " << thisTS << endl;
399  // cout << "thisTS = " << thisTS<<endl;
400  if (thisTS <= arMax) {
401  thisUL = firstPOI->getVal();
402  } else {
403  break;
404  }
405  }
406 
407  histOfUL->Fill(thisUL);
408 
409  // for few events, data is often the same, and UL is often the same
410  // cout << "thisUL = " << thisUL<<endl;
411 
412  delete toyData;
413  }
414  histOfUL->Draw();
415  c1->SaveAs("two-sided_upper_limit_output.pdf");
416 
417  // if you want to see a plot of the sampling distribution for a particular scan point:
418  /*
419  SamplingDistPlot sampPlot;
420  int indexInScan = 0;
421  tmpPoint = (RooArgSet*) parameterScan->get(indexInScan)->clone("temp");
422  firstPOI->setVal( tmpPoint->getRealValue(firstPOI->GetName()) );
423  toymcsampler->SetParametersForTestStat(tmpPOI);
424  SamplingDistribution* samp = toymcsampler->GetSamplingDistribution(*tmpPoint);
425  sampPlot.AddSamplingDistribution(samp);
426  sampPlot.Draw();
427  */
428 
429  // Now find bands and power constraint
430  Double_t *bins = histOfUL->GetIntegral();
431  TH1F *cumulative = (TH1F *)histOfUL->Clone("cumulative");
432  cumulative->SetContent(bins);
433  double band2sigDown = 0, band1sigDown = 0, bandMedian = 0, band1sigUp = 0, band2sigUp = 0;
434  for (int i = 1; i <= cumulative->GetNbinsX(); ++i) {
435  if (bins[i] < RooStats::SignificanceToPValue(2))
436  band2sigDown = cumulative->GetBinCenter(i);
437  if (bins[i] < RooStats::SignificanceToPValue(1))
438  band1sigDown = cumulative->GetBinCenter(i);
439  if (bins[i] < 0.5)
440  bandMedian = cumulative->GetBinCenter(i);
441  if (bins[i] < RooStats::SignificanceToPValue(-1))
442  band1sigUp = cumulative->GetBinCenter(i);
443  if (bins[i] < RooStats::SignificanceToPValue(-2))
444  band2sigUp = cumulative->GetBinCenter(i);
445  }
446  cout << "-2 sigma band " << band2sigDown << endl;
447  cout << "-1 sigma band " << band1sigDown << " [Power Constraint)]" << endl;
448  cout << "median of band " << bandMedian << endl;
449  cout << "+1 sigma band " << band1sigUp << endl;
450  cout << "+2 sigma band " << band2sigUp << endl;
451 
452  // print out the interval on the first Parameter of Interest
453  cout << "\nobserved 95% upper-limit " << interval->UpperLimit(*firstPOI) << endl;
454  cout << "CLb strict [P(toy>obs|0)] for observed 95% upper-limit " << CLb << endl;
455  cout << "CLb inclusive [P(toy>=obs|0)] for observed 95% upper-limit " << CLbinclusive << endl;
456 
457  delete profile;
458  delete nll;
459 }