rf212_plottingInRanges_blinding

Plot a PDF in disjunct ranges, and get normalisation right.

Usually, when comparing a fit to data, one should first plot the data, and then the PDF. In this case, the PDF is automatically normalised to match the number of data events in the plot. However, when plotting only a sub-range, when e.g. a signal region has to be blinded, one has to exclude the blinded region from the computation of the normalisation.

In this tutorial, we show how to explicitly choose the normalisation when plotting using NormRange().

Thanks to Marc Escalier for asking how to do this correctly.

Author: Stephan Hageboeck
This notebook tutorial was automatically generated with ROOTBOOK-izer from the macro found in the ROOT repository on Wednesday, November 30, 2022 at 11:22 AM.

In [1]:
%%cpp -d
#include <RooDataSet.h>
#include <RooExponential.h>
#include <RooPlot.h>
#include <RooRealVar.h>
#include <TCanvas.h>

using namespace RooFit;

Make a fit model

In [2]:
RooRealVar x("x", "The observable", 1, 30);
RooRealVar tau("tau", "The exponent", -0.1337, -10., -0.1);
RooExponential expo("expo", "A falling exponential function", x, tau);

Define the sidebands (e.g. background regions)

In [3]:
x.setRange("full", 1, 30);
x.setRange("left", 1, 10);
x.setRange("right", 20, 30);
[#1] INFO:Eval -- RooRealVar::setRange(x) new range named 'full' created with bounds [1,30]
[#1] INFO:Eval -- RooRealVar::setRange(x) new range named 'left' created with bounds [1,10]
[#1] INFO:Eval -- RooRealVar::setRange(x) new range named 'right' created with bounds [20,30]

Generate toy data, and cut out the blinded region.

In [4]:
std::unique_ptr<RooDataSet> data{expo.generate(x, 1000)};
std::unique_ptr<RooAbsData> blindedData{data->reduce(CutRange("left,right"))};
input_line_52:2:2: warning: 'data' shadows a declaration with the same name in the 'std' namespace; use '::data' to reference this declaration
 std::unique_ptr<RooDataSet> data{expo.generate(x, 1000)};
 ^

Kick tau a bit, and run an unbinned fit where the blinded data are missing.

The fit should be done only in the unblinded regions, otherwise it would try to make the model adapt to the empty bins in the blinded region.

In [5]:
tau.setVal(-2.);
expo.fitTo(*blindedData, Range("left,right"));
[#1] INFO:Eval -- RooRealVar::setRange(x) new range named 'fit_nll_expo_expoData_left' created with bounds [1,10]
[#1] INFO:Eval -- RooRealVar::setRange(x) new range named 'fit_nll_expo_expoData_right' created with bounds [20,30]
[#1] INFO:Fitting -- RooAbsOptTestStatistic::ctor(nll_expo_expoData) constructing test statistic for sub-range named left,right
[#1] INFO:Minimization -- RooAbsMinimizerFcn::setOptimizeConst: activating const optimization
 **********
 **    1 **SET PRINT           1
 **********
 **********
 **    2 **SET NOGRAD
 **********
 PARAMETER DEFINITIONS:
    NO.   NAME         VALUE      STEP SIZE      LIMITS
     1 tau         -2.00000e+00  9.50000e-01   -1.00000e+01 -1.00000e-01
 **********
 **    3 **SET ERR         0.5
 **********
 **********
 **    4 **SET PRINT           1
 **********
 **********
 **    5 **SET STR           1
 **********
 NOW USING STRATEGY  1: TRY TO BALANCE SPEED AGAINST RELIABILITY
 **********
 **    6 **MIGRAD         500           1
 **********
 FIRST CALL TO USER FUNCTION AT NEW START POINT, WITH IFLAG=4.
 START MIGRAD MINIMIZATION.  STRATEGY  1.  CONVERGENCE WHEN EDM .LT. 1.00e-03
 FCN=6752.91 FROM MIGRAD    STATUS=INITIATE        4 CALLS           5 TOTAL
                     EDM= unknown      STRATEGY= 1      NO ERROR MATRIX       
  EXT PARAMETER               CURRENT GUESS       STEP         FIRST   
  NO.   NAME      VALUE            ERROR          SIZE      DERIVATIVE 
   1  tau         -2.00000e+00   9.50000e-01   2.51381e-01  -1.27116e+04
                               ERR DEF= 0.5
 MIGRAD MINIMIZATION HAS CONVERGED.
 MIGRAD WILL VERIFY CONVERGENCE AND ERROR MATRIX.
 COVARIANCE MATRIX CALCULATED SUCCESSFULLY
 FCN=1797.14 FROM MIGRAD    STATUS=CONVERGED      36 CALLS          37 TOTAL
                     EDM=1.06174e-09    STRATEGY= 1      ERROR MATRIX ACCURATE 
  EXT PARAMETER                                   STEP         FIRST   
  NO.   NAME      VALUE            ERROR          SIZE      DERIVATIVE 
   1  tau         -1.42992e-01   6.76562e-03   3.07840e-04  -3.13515e-03
                               ERR DEF= 0.5
 EXTERNAL ERROR MATRIX.    NDIM=  25    NPAR=  1    ERR DEF=0.5
  4.578e-05 
 **********
 **    7 **SET ERR         0.5
 **********
 **********
 **    8 **SET PRINT           1
 **********
 **********
 **    9 **HESSE         500
 **********
 COVARIANCE MATRIX CALCULATED SUCCESSFULLY
 FCN=1797.14 FROM HESSE     STATUS=OK              5 CALLS          42 TOTAL
                     EDM=1.05963e-09    STRATEGY= 1      ERROR MATRIX ACCURATE 
  EXT PARAMETER                                INTERNAL      INTERNAL  
  NO.   NAME      VALUE            ERROR       STEP SIZE       VALUE   
   1  tau         -1.42992e-01   6.76562e-03   1.23136e-05   1.43890e+00
                               ERR DEF= 0.5
 EXTERNAL ERROR MATRIX.    NDIM=  25    NPAR=  1    ERR DEF=0.5
  4.578e-05 
[#1] INFO:Minimization -- RooAbsMinimizerFcn::setOptimizeConst: deactivating const optimization

Clear the "fitrange" attribute of the PDF. Otherwise, the fitrange would be automatically taken as the NormRange() for plotting. We want to avoid this, because the point of this tutorial is to show what can go wrong when the NormRange() is not specified.

In [6]:
expo.removeStringAttribute("fitrange");

Here we will plot the results

In [7]:
TCanvas *canvas=new TCanvas("canvas","canvas",800,600);
canvas->Divide(2,1);

Wrong:

Plotting each slice on its own normalises the PDF over its plotting range. For the full curve, that means that the blinded region where data is missing is included in the normalisation calculation. The PDF therefore comes out too low, and doesn't match up with the slices in the side bands, which are normalised to "their" data.

In [8]:
std::cout << "Now plotting with unique normalisation for each slice." << std::endl;
canvas->cd(1);
RooPlot* plotFrame = x.frame(RooFit::Title("Wrong: Each slice normalised over its plotting range"));
Now plotting with unique normalisation for each slice.

Plot only the blinded data, and then plot the PDF over the full range as well as both sidebands

In [9]:
blindedData->plotOn(plotFrame);
expo.plotOn(plotFrame, LineColor(kRed),   Range("full"));
expo.plotOn(plotFrame, LineColor(kBlue),  Range("left"));
expo.plotOn(plotFrame, LineColor(kGreen), Range("right"));

plotFrame->Draw();
[#1] INFO:Plotting -- RooAbsPdf::plotOn(expo) only plotting range 'full', curve is normalized to data in given range
[#1] INFO:Plotting -- RooAbsPdf::plotOn(expo) only plotting range 'left', curve is normalized to data in given range
[#1] INFO:Plotting -- RooAbsPdf::plotOn(expo) only plotting range 'right', curve is normalized to data in given range

Right:

Make the same plot, but normalise each piece with respect to the regions "left" AND "right". This requires setting a "NormRange", which tells RooFit over which range the PDF has to be integrated to normalise. This means that the normalisation of the blue and green curves is slightly different from the left plot, because they get a common scale factor.

In [10]:
std::cout << "\n\nNow plotting with correct norm ranges:" << std::endl;
canvas->cd(2);
RooPlot* plotFrameWithNormRange = x.frame(RooFit::Title("Right: All slices have common normalisation"));

Now plotting with correct norm ranges:

Plot only the blinded data, and then plot the PDF over the full range as well as both sidebands

In [11]:
blindedData->plotOn(plotFrameWithNormRange);
expo.plotOn(plotFrameWithNormRange, LineColor(kBlue),  Range("left"),  RooFit::NormRange("left,right"));
expo.plotOn(plotFrameWithNormRange, LineColor(kGreen), Range("right"), RooFit::NormRange("left,right"));
expo.plotOn(plotFrameWithNormRange, LineColor(kRed),   Range("full"),  RooFit::NormRange("left,right"), LineStyle(10));

plotFrameWithNormRange->Draw();

canvas->Draw();
[#1] INFO:Plotting -- RooAbsPdf::plotOn(expo) only plotting range 'left'
[#1] INFO:Plotting -- RooAbsPdf::plotOn(expo) p.d.f. curve is normalized using explicit choice of ranges 'left,right'
[#1] INFO:Plotting -- RooAbsPdf::plotOn(expo) only plotting range 'right'
[#1] INFO:Plotting -- RooAbsPdf::plotOn(expo) p.d.f. curve is normalized using explicit choice of ranges 'left,right'
[#1] INFO:Plotting -- RooAbsPdf::plotOn(expo) only plotting range 'full'
[#1] INFO:Plotting -- RooAbsPdf::plotOn(expo) p.d.f. curve is normalized using explicit choice of ranges 'left,right'

Draw all canvases

In [12]:
%jsroot on
gROOT->GetListOfCanvases()->Draw()