OneSidedFrequentistUpperLimitWithBands
This is a standard demo that can be used with any ROOT file prepared in the standard way. You specify:
- name for input ROOT file
- name of workspace inside ROOT file that holds model and data
- name of ModelConfig that specifies details for calculator tools
- name of dataset
With default parameters the macro will attempt to run the standard hist2workspace example and read the ROOT file that it produces.
The first ~100 lines define a new test statistic, then the main macro starts. You may want to control:
double confidenceLevel=0.95;
int nPointsToScan = 12;
int nToyMC = 150;
This uses a modified version of the profile likelihood ratio as a test statistic for upper limits (eg. test stat = 0 if muhat>mu).
Based on the observed data, one defines a set of parameter points to be tested based on the value of the parameter of interest and the conditional MLE (eg. profiled) values of the nuisance parameters.
At each parameter point, pseudo-experiments are generated using this fixed reference model and then the test statistic is evaluated. Note, the nuisance parameters are floating in the fits. For each point, the threshold that defines the 95% acceptance region is found. This forms a "Confidence Belt".
After constructing the confidence belt, one can find the confidence interval for any particular dataset by finding the intersection of the observed test statistic and the confidence belt. First this is done on the observed data to get an observed 1-sided upper limt.
Finally, there expected limit and bands (from background-only) are formed by generating background-only data and finding the upper limit. This is done by hand for now, will later be part of the RooStats tools.
On a technical note, this technique is NOT the Feldman-Cousins technique, because that is a 2-sided interval BY DEFINITION. However, like the Feldman-Cousins technique this is a Neyman-Construction. For technical reasons the easiest way to implement this right now is to use the FeldmanCousins tool and then change the test statistic that it is using.
Building the confidence belt can be computationally expensive. Once it is built, one could save it to a file and use it in a separate step.
Note, if you have a boundary on the parameter of interest (eg. cross-section) the threshold on the one-sided test statistic starts off very small because we are only including downward fluctuations. You can see the threshold in these printouts:
[#0] PROGRESS:Generation -- generated toys: 500 / 999
NeymanConstruction: Prog: 12/50
total MC = 39
this test stat = 0
SigXsecOverSM=0.69 alpha_syst1=0.136515 alpha_syst3=0.425415 beta_syst2=1.08496 [-1
e+30, 0.011215] in interval = 1
static unsigned int total
this tells you the values of the parameters being used to generate the pseudo-experiments and the threshold in this case is 0.011215. One would expect for 95% that the threshold would be ~1.35 once the cross-section is far enough away from 0 that it is essentially unaffected by the boundary. As one reaches the last points in the scan, the threshold starts to get artificially high. This is because the range of the parameter in the fit is the same as the range in the scan. In the future, these should be independently controlled, but they are not now. As a result the ~50% of pseudo-experiments that have an upward fluctuation end up with muhat = muMax. Because of this, the upper range of the parameter should be well above the expected upper limit... but not too high or one will need a very large value of nPointsToScan to resolve the relevant region. This can be improved, but this is the first version of this script.
Important note: when the model includes external constraint terms, like a Gaussian constraint to a nuisance parameter centered around some nominal value there is a subtlety. The asymptotic results are all based on the assumption that all the measurements fluctuate... including the nominal values from auxiliary measurements. If these do not fluctuate, this corresponds to an "conditional ensemble". The result is that the distribution of the test statistic can become very non-chi^2. This results in thresholds that become very large. This can be seen in the following thought experiment. Say the model is \( Pois(N | s + b)G(b0|b,sigma) \) where \( G(b0|b,sigma) \) is the external constraint and b0 is 100. If N is also 100 then the profiled value of b given s is going to be some trade off between 100-s and b0. If sigma is \( \sqrt(N) \), then the profiled value of b is probably 100 - s/2 So for s=60 we are going to have a profiled value of b~70. Now when we generate pseudo-experiments for s=60, b=70 we will have N~130 and the average shat will be 30, not 60. In practice, this is only an issue for values of s that are very excluded. For values of s near the 95% limit this should not be a big effect. This can be avoided if the nominal values of the constraints also fluctuate, but that requires that those parameters are RooRealVars in the model. This version does not deal with this issue, but it will be addressed in a future version.
FeldmanCousins: ntoys per point = 499
FeldmanCousins: nEvents per toy will fluctuate about expectation
will use global observables for unconditional ensemble
RooArgSet:: = (nominalLumi,nom_alpha_syst1,nom_alpha_syst2,nom_alpha_syst3,nom_gamma_stat_channel1_bin_0,nom_gamma_stat_channel1_bin_1)
=== Using the following for ModelConfig ===
Observables: RooArgSet:: = (obs_x_channel1,channelCat)
Parameters of Interest: RooArgSet:: = (SigXsecOverSM)
Nuisance Parameters: RooArgSet:: = (alpha_syst2,alpha_syst3,gamma_stat_channel1_bin_0,gamma_stat_channel1_bin_1)
Global Observables: RooArgSet:: = (nominalLumi,nom_alpha_syst1,nom_alpha_syst2,nom_alpha_syst3,nom_gamma_stat_channel1_bin_0,nom_gamma_stat_channel1_bin_1)
PDF: RooSimultaneous::simPdf[ indexCat=channelCat channel1=model_channel1 ] = 0.190787
FeldmanCousins: Model has nuisance parameters, will do profile construction
FeldmanCousins: # points to test = 12
lookup index = 0
NeymanConstruction: Prog: 1/12 total MC = 499 this test stat = 0
SigXsecOverSM=0.125 alpha_syst2=0.61993 alpha_syst3=0.233335 gamma_stat_channel1_bin_0=1.03212 gamma_stat_channel1_bin_1=1.04741 [-inf, 0.329584] in interval = 1
NeymanConstruction: Prog: 2/12 total MC = 499 this test stat = 0
SigXsecOverSM=0.375 alpha_syst2=0.458263 alpha_syst3=0.183235 gamma_stat_channel1_bin_0=1.02329 gamma_stat_channel1_bin_1=1.03422 [-inf, 0.888912] in interval = 1
NeymanConstruction: Prog: 3/12 total MC = 499 this test stat = 0
SigXsecOverSM=0.625 alpha_syst2=0.292479 alpha_syst3=0.126723 gamma_stat_channel1_bin_0=1.01484 gamma_stat_channel1_bin_1=1.02374 [-inf, 1.39758] in interval = 1
NeymanConstruction: Prog: 4/12 total MC = 499 this test stat = 0
SigXsecOverSM=0.875 alpha_syst2=0.130656 alpha_syst3=0.0953602 gamma_stat_channel1_bin_0=1.00646 gamma_stat_channel1_bin_1=1.01288 [-inf, 1.51992] in interval = 1
NeymanConstruction: Prog: 5/12 total MC = 499 this test stat = 0.000124169
SigXsecOverSM=1.125 alpha_syst2=-0.0146474 alpha_syst3=0.0173394 gamma_stat_channel1_bin_0=0.999267 gamma_stat_channel1_bin_1=1.003 [-inf, 1.54198] in interval = 1
NeymanConstruction: Prog: 6/12 total MC = 499 this test stat = 0.0913576
SigXsecOverSM=1.375 alpha_syst2=-0.15078 alpha_syst3=-0.0572169 gamma_stat_channel1_bin_0=0.99238 gamma_stat_channel1_bin_1=0.993157 [-inf, 1.78377] in interval = 1
NeymanConstruction: Prog: 7/12 total MC = 499 this test stat = 0.349003
SigXsecOverSM=1.625 alpha_syst2=-0.294555 alpha_syst3=-0.0932947 gamma_stat_channel1_bin_0=0.985516 gamma_stat_channel1_bin_1=0.983192 [-inf, 1.66921] in interval = 1
NeymanConstruction: Prog: 8/12 total MC = 499 this test stat = 0.767692
SigXsecOverSM=1.875 alpha_syst2=-0.424297 alpha_syst3=-0.146312 gamma_stat_channel1_bin_0=0.979248 gamma_stat_channel1_bin_1=0.973447 [-inf, 1.5144] in interval = 1
NeymanConstruction: Prog: 9/12 total MC = 499 this test stat = 1.34313
SigXsecOverSM=2.125 alpha_syst2=-0.546305 alpha_syst3=-0.198112 gamma_stat_channel1_bin_0=0.973364 gamma_stat_channel1_bin_1=0.963869 [-inf, 1.30266] in interval = 0
NeymanConstruction: Prog: 10/12 total MC = 499 this test stat = 2.07172
SigXsecOverSM=2.375 alpha_syst2=-0.66027 alpha_syst3=-0.248685 gamma_stat_channel1_bin_0=0.967841 gamma_stat_channel1_bin_1=0.954463 [-inf, 1.5387] in interval = 0
NeymanConstruction: Prog: 11/12 total MC = 499 this test stat = 2.9476
SigXsecOverSM=2.625 alpha_syst2=-0.766369 alpha_syst3=-0.29799 gamma_stat_channel1_bin_0=0.962657 gamma_stat_channel1_bin_1=0.945235 [-inf, 1.30108] in interval = 0
NeymanConstruction: Prog: 12/12 total MC = 499 this test stat = 3.96711
SigXsecOverSM=2.875 alpha_syst2=-0.865262 alpha_syst3=-0.345965 gamma_stat_channel1_bin_0=0.95779 gamma_stat_channel1_bin_1=0.936192 [-inf, 1.35603] in interval = 0
[#1] INFO:Eval -- 8 points in interval
95% interval on SigXsecOverSM is : [0.125, 1.875]
[#1] INFO:Minimization -- p.d.f. provides expected number of events, including extended term in likelihood.
[#1] INFO:Minimization -- Including the following constraint terms in minimization: (lumiConstraint,alpha_syst1Constraint,alpha_syst2Constraint,alpha_syst3Constraint,gamma_stat_channel1_bin_0_constraint,gamma_stat_channel1_bin_1_constraint)
[#1] INFO:Minimization -- The global observables are not defined , normalize constraints with respect to the parameters (Lumi,SigXsecOverSM,alpha_syst1,alpha_syst2,alpha_syst3,gamma_stat_channel1_bin_0,gamma_stat_channel1_bin_1)
[#1] INFO:Fitting -- RooAbsPdf::fitTo(simPdf) fixing normalization set for coefficient determination to observables in data
[#1] INFO:Fitting -- using generic CPU library compiled with no vectorizations
[#1] INFO:Fitting -- Creation of NLL object took 1.15071 ms
[#1] INFO:Minimization -- RooProfileLL::evaluate(RooEvaluatorWrapper_Profile[SigXsecOverSM]) Creating instance of MINUIT
[#1] INFO:Fitting -- RooAddition::defaultErrorLevel(nll_simPdf_obsData) Summation contains a RooNLLVar, using its error level
[#1] INFO:Minimization -- RooProfileLL::evaluate(RooEvaluatorWrapper_Profile[SigXsecOverSM]) determining minimum likelihood for current configurations w.r.t all observable
[#1] INFO:Minimization -- [fitFCN] No discrete parameters, performing continuous minimization only
[#1] INFO:Minimization -- RooProfileLL::evaluate(RooEvaluatorWrapper_Profile[SigXsecOverSM]) minimum found at (SigXsecOverSM=1.12136)
.[#1] INFO:Minimization -- [fitFCN] No discrete parameters, performing continuous minimization only
Will use these parameter points to generate pseudo data for bkg only
1) 0x55cc2eb45ce0 RooRealVar:: alpha_syst2 = 0.710958 +/- 0.914123 L(-5 - 5) "alpha_syst2"
2) 0x55cc2eb461f0 RooRealVar:: alpha_syst3 = 0.261478 +/- 0.929174 L(-5 - 5) "alpha_syst3"
3) 0x55cc2eb46720 RooRealVar:: gamma_stat_channel1_bin_0 = 1.03677 +/- 0.0462911 L(0 - 1.25) "gamma_stat_channel1_bin_0"
4) 0x55cc2eb46c50 RooRealVar:: gamma_stat_channel1_bin_1 = 1.05318 +/- 0.0761263 L(0 - 1.5) "gamma_stat_channel1_bin_1"
5) 0x55cc2eb471d0 RooRealVar:: SigXsecOverSM = 0 +/- 0 L(0 - 3) B(12) "SigXsecOverSM"
-2 sigma band 0
-1 sigma band 0.105 [Power Constraint)]
median of band 0.855
+1 sigma band 1.605
+2 sigma band 2.085
observed 95% upper-limit 1.875
CLb strict [P(toy>obs|0)] for observed 95% upper-limit 0.966667
CLb inclusive [P(toy>=obs|0)] for observed 95% upper-limit 0.966667
void OneSidedFrequentistUpperLimitWithBands(const char *infile = "", const char *workspaceName = "combined",
const char *modelConfigName = "ModelConfig",
const char *dataName = "obsData")
{
double confidenceLevel = 0.95;
int nPointsToScan = 12;
int nToyMC = 150;
const char *filename = "";
if (!strcmp(infile, "")) {
filename = "results/example_combined_GaussExample_model.root";
bool fileExist = !
gSystem->AccessPathName(filename);
if (!fileExist) {
cout << "will run standard hist2workspace example" << endl;
gROOT->ProcessLine(
".! prepareHistFactory .");
gROOT->ProcessLine(
".! hist2workspace config/example.xml");
cout << "\n\n---------------------" << endl;
cout << "Done creating example input" << endl;
cout << "---------------------\n\n" << endl;
}
} else
filename = infile;
if (!file) {
cout << "StandardRooStatsDemoMacro: Input file " << filename << " is not found" << endl;
return;
}
if (!w) {
cout << "workspace not found" << endl;
return;
}
if (!data || !mc) {
w->Print();
cout << "data or ModelConfig was not found" << endl;
return;
}
fc.SetConfidenceLevel(confidenceLevel);
fc.AdditionalNToysFactor(
0.5);
fc.SetNBins(nPointsToScan);
fc.CreateConfBelt(true);
if (data->numEntries() == 1)
fc.FluctuateNumDataEntries(false);
else
cout << "Not sure what to do about this model" << endl;
}
cout << "will use global observables for unconditional ensemble" << endl;
}
cout <<
"\n95% interval on " << firstPOI->
GetName() <<
" is : [" << interval->
LowerLimit(*firstPOI) <<
", "
<< interval->
UpperLimit(*firstPOI) <<
"] " << endl;
double observedUL = interval->
UpperLimit(*firstPOI);
double obsTSatObsUL = fc.GetTestStatSampler()->EvaluateTestStatistic(*data, tmpPOI);
histOfThresholds->
Fill(poiVal, arMax);
}
histOfThresholds->
Draw();
profile->getVal();
w->saveSnapshot("paramsToGenerateData", *poiAndNuisance);
cout << "\nWill use these parameter points to generate pseudo data for bkg only" << endl;
paramsToGenerateData->
Print(
"v");
double CLb = 0;
double CLbinclusive = 0;
TH1F *histOfUL =
new TH1F(
"histOfUL",
"", 100, 0, firstPOI->
getMax());
for (int imc = 0; imc < nToyMC; ++imc) {
w->loadSnapshot("paramsToGenerateData");
std::unique_ptr<RooDataSet> toyData;
if (data->numEntries() == 1)
else
cout << "Not sure what to do about this model" << endl;
} else {
}
if (!simPdf) {
allVars->assign(*values);
} else {
auto const& catName =
tt.first;
std::unique_ptr<RooDataSet> tmp{pdftmp->
generate(*globtmp, 1)};
globtmp->assign(*tmp->get(0));
}
}
double toyTSatObsUL = fc.GetTestStatSampler()->EvaluateTestStatistic(*toyData, tmpPOI);
if (obsTSatObsUL < toyTSatObsUL)
CLb += (1.) / nToyMC;
if (obsTSatObsUL <= toyTSatObsUL)
CLbinclusive += (1.) / nToyMC;
double thisUL = 0;
double thisTS = fc.GetTestStatSampler()->EvaluateTestStatistic(*toyData, tmpPOI);
if (thisTS <= arMax) {
} else {
break;
}
}
}
c1->SaveAs(
"one-sided_upper_limit_output.pdf");
double band2sigDown, band1sigDown, bandMedian, band1sigUp, band2sigUp;
for (
int i = 1; i <= cumulative->
GetNbinsX(); ++i) {
if (bins[i] < 0.5)
}
cout << "-2 sigma band " << band2sigDown << endl;
cout << "-1 sigma band " << band1sigDown << " [Power Constraint)]" << endl;
cout << "median of band " << bandMedian << endl;
cout << "+1 sigma band " << band1sigUp << endl;
cout << "+2 sigma band " << band2sigUp << endl;
cout <<
"\nobserved 95% upper-limit " << interval->
UpperLimit(*firstPOI) << endl;
cout << "CLb strict [P(toy>obs|0)] for observed 95% upper-limit " << CLb << endl;
cout << "CLb inclusive [P(toy>=obs|0)] for observed 95% upper-limit " << CLbinclusive << endl;
}
int Int_t
Signed integer 4 bytes (int).
double Double_t
Double 8 bytes.
RooFit::OwningPtr< RooArgSet > getObservables(const RooArgSet &set, bool valueOnly=true) const
Given a set of possible observables, return the observables that this PDF depends on.
RooFit::OwningPtr< RooArgSet > getVariables(bool stripDisconnected=true) const
Return RooArgSet with all variables (tree leaf nodes of expression tree).
double getRealValue(const char *name, double defVal=0.0, bool verbose=false) const
Get value of a RooAbsReal stored in set with given name.
virtual bool add(const RooAbsArg &var, bool silent=false)
Add the specified argument to list.
RooAbsArg * first() const
void Print(Option_t *options=nullptr) const override
This method must be overridden when a class wants to print itself.
Abstract base class for binned and unbinned datasets.
virtual Int_t numEntries() const
Return number of entries in dataset, i.e., count unweighted entries.
Abstract interface for all probability density functions.
bool canBeExtended() const
If true, PDF can provide extended likelihood term.
RooFit::OwningPtr< RooAbsReal > createNLL(RooAbsData &data, CmdArgs_t const &... cmdArgs)
Construct representation of -log(L) of PDF with given dataset.
RooFit::OwningPtr< RooDataSet > generate(const RooArgSet &whatVars, Int_t nEvents, const RooCmdArg &arg1, const RooCmdArg &arg2={}, const RooCmdArg &arg3={}, const RooCmdArg &arg4={}, const RooCmdArg &arg5={})
See RooAbsPdf::generate(const RooArgSet&,const RooCmdArg&,const RooCmdArg&,const RooCmdArg&,...
virtual double getMax(const char *name=nullptr) const
Get maximum of currently defined range.
virtual double getMin(const char *name=nullptr) const
Get minimum of currently defined range.
double getVal(const RooArgSet *normalisationSet=nullptr) const
Evaluate object.
RooArgSet is a container object that can hold multiple RooAbsArg objects.
RooArgSet * snapshot(bool deepCopy=true) const
Use RooAbsCollection::snapshot(), but return as RooArgSet.
TObject * clone(const char *newname=nullptr) const override
Container class to hold unbinned data.
const RooArgSet * get(Int_t index) const override
Return RooArgSet with coordinates of event 'index'.
Variable that can be changed from the outside.
void setVal(double value) override
Set value of variable to 'value'.
Facilitates simultaneous fitting of multiple PDFs to subsets of a given dataset.
RooAbsPdf * getPdf(RooStringView catName) const
Return the p.d.f associated with the given index category name.
const RooAbsCategoryLValue & indexCat() const
ConfidenceBelt is a concrete implementation of the ConfInterval interface.
double GetAcceptanceRegionMax(RooArgSet &, double cl=-1., double leftside=-1.)
The FeldmanCousins class (like the Feldman-Cousins technique) is essentially a specific configuration...
const RooArgSet * GetGlobalObservables() const
get RooArgSet for global observables (return nullptr if not existing)
const RooArgSet * GetParametersOfInterest() const
get RooArgSet containing the parameter of interest (return nullptr if not existing)
const RooArgSet * GetNuisanceParameters() const
get RooArgSet containing the nuisance parameters (return nullptr if not existing)
const RooArgSet * GetObservables() const
get RooArgSet for observables (return nullptr if not existing)
RooAbsPdf * GetPdf() const
get model PDF (return nullptr if pdf has not been specified or does not exist)
PointSetInterval is a concrete implementation of the ConfInterval interface.
double UpperLimit(RooRealVar ¶m)
return upper limit on a given parameter
double LowerLimit(RooRealVar ¶m)
return lower limit on a given parameter
ProfileLikelihoodTestStat is an implementation of the TestStatistic interface that calculates the pro...
void SetOneSided(bool flag=true)
ToyMCSampler is an implementation of the TestStatSampler interface.
virtual TestStatistic * GetTestStatistic(unsigned int i) const
void SetGlobalObservables(const RooArgSet &o) override
specify the conditional observables
Persistable container for RooFit projects.
TObject * Get(const char *namecycle) override
Return pointer to object identified by namecycle.
A file, usually with extension .root, that stores data and code in the form of serialized objects in ...
static TFile * Open(const char *name, Option_t *option="", const char *ftitle="", Int_t compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault, Int_t netopt=0)
Create / open a file.
1-D histogram with a float per channel (see TH1 documentation)
virtual Double_t GetBinCenter(Int_t bin) const
Return bin center for 1D histogram.
virtual Int_t GetNbinsX() const
virtual Int_t Fill(Double_t x)
Increment bin with abscissa X by 1.
virtual void SetContent(const Double_t *content)
Replace bin contents by the contents of array content.
void Draw(Option_t *option="") override
Draw this histogram with options.
virtual void SetMinimum(Double_t minimum=-1111)
virtual Double_t * GetIntegral()
Return a pointer to the array of bins integral.
TObject * Clone(const char *newname="") const override
Make a complete copy of the underlying object.
virtual void SetTitle(const char *title="")
Set the title of the TNamed.
const char * GetName() const override
Returns name of object.
double nll(double pdf, double weight, int binnedL, int doBinOffset)
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
RooStats::ModelConfig ModelConfig
Namespace for the RooStats classes.
double SignificanceToPValue(double Z)
returns p-value corresponding to a 1-sided significance
- Authors
- Kyle Cranmer, Haichen Wang, Daniel Whiteson
Definition in file OneSidedFrequentistUpperLimitWithBands.C.