Skip to content

Instantly share code, notes, and snippets.

@giorgiopizz
Created May 11, 2023 14:35
Show Gist options
  • Save giorgiopizz/f05ef9a6c4b3f3996307fc15ab30d1d7 to your computer and use it in GitHub Desktop.
Save giorgiopizz/f05ef9a6c4b3f3996307fc15ab30d1d7 to your computer and use it in GitHub Desktop.
mkPlot.py and mkDatacards.py port to mkShapesRDF
def flatten_samples(samples):
## flatten the subsamples (create full samples named sample_subsample)
subsamplesmap = []
for sname in list(samples.keys()):
sample = samples[sname]
if 'subsamples' not in sample:
continue
subsamplesmap.append((sname, []))
for sub in sample['subsamples']:
samples['%s_%s' % (sname, sub)] = sample
subsamplesmap[-1][1].append('%s_%s' % (sname, sub))
sample.pop('subsamples')
samples.pop(sname)
return subsamplesmap
def flatten_cuts(cuts):
## flatten the categories (create full cuts named cut_category)
categoriesmap = []
for cname in list(cuts.keys()):
cut = cuts[cname]
if 'categories' not in cut:
continue
categoriesmap.append((cname, []))
for cat in cut['categories']:
cuts['%s_%s' % (cname, cat)] = cut
categoriesmap[-1][1].append('%s_%s' % (cname, cat))
cut.pop('categories')
cuts.pop(cname)
return categoriesmap
def update_variables_with_categories(variables, categoriesmap):
## variables can have "cuts" specifications
for variable in variables.items():
if 'cuts' not in variable:
continue
cutspec = variable['cuts']
for cname, categories in categoriesmap:
if cname not in cutspec:
continue
# original cut is in the spec
cutspec.remove(cname)
# if a category (subcut) is also in the spec, we won't touch this variable
if len(set(cutspec) & set(categories)) != 0:
continue
# otherwise we replace the cut with all the categories
cutspec.extend(categories)
def update_nuisances_with_subsamples(nuisances, subsamplesmap):
for nuisance in nuisances.items():
if 'samples' not in nuisance:
continue
samplespec = nuisance['samples']
for sname, subsamples in subsamplesmap:
if sname not in samplespec:
continue
# original sample is in the spec
sconfig = samplespec.pop(sname)
# if a subsample is also in the spec, we won't toucn this any more
if len(set(samplespec.keys()) & set(subsamples)) != 0:
continue
# otherwise we replace the sample with all the subsamples
samplespec.update((subsample, sconfig) for subsample in subsamples)
def update_nuisances_with_categories(nuisances, categoriesmap):
for nuisance in nuisances.values():
if 'cuts' not in nuisance:
continue
cutspec = nuisance['cuts']
for cname, categories in categoriesmap:
if cname not in cutspec:
continue
# original cut is in the spec
cutspec.remove(cname)
# if a category (subcut) is also in the spec, we won't touch this nuisance
if len(set(cutspec) & set(categories)) != 0:
continue
# otherwise we replace the cut with all the categories
cutspec.extend(categories)
#!/usr/bin/env python
import json
import sys
argv = sys.argv
sys.argv = argv[:1]
import ROOT
ROOT.gROOT.SetBatch(True)
import optparse
#import LatinoAnalysis.Gardener.hwwtools as hwwtools
#import logging
import collections
import os.path
import shutil
# Common Tools & batch
#from LatinoAnalysis.Tools.commonTools import *
# ----------------------------------------------------- DatacardFactory --------------------------------------
class DatacardFactory:
#_logger = logging.getLogger('DatacardFactory')
# _____________________________________________________________________________
def __init__(self):
self._fileIn = None
self._skipMissingNuisance = False
# _____________________________________________________________________________
# a datacard for each "cut" and each "variable" will be produced, in separate sub-folders, names after "cut/variable"
# _____________________________________________________________________________
def makeDatacards( self, inputFile, outputDirDatacard, variables, cuts, samples, structureFile, nuisances):
print("=======================")
print("==== makeDatacards ====")
print("=======================")
if os.path.isdir(inputFile):
# ONLY COMPATIBLE WITH OUTPUTS MERGED TO SAMPLE LEVEL!!
self._fileIn = {}
for sampleName in samples:
self._fileIn[sampleName] = ROOT.TFile.Open(inputFile+'/plots_%s_ALL_%s.root' % (self._tag, sampleName))
if not self._fileIn[sampleName]:
raise RuntimeError('Input file for sample ' + sampleName + ' missing')
else:
self._fileIn = ROOT.TFile(inputFile, "READ")
ROOT.gROOT.GetListOfFiles().Remove(self._fileIn) #Speed up
# categorize the sample names
signal_ids = collections.OrderedDict() # id map of alternative_signals + cumulative_signals
alternative_signals = ['']
cumulative_signals = []
data = []
background_ids = collections.OrderedDict()
# divide the list of samples among signal, background and data
isig = 0
ibkg = 1
for sampleName in samples:
if structureFile[sampleName]['isSignal'] != 0:
signal_ids[sampleName] = isig
isig -= 1
if structureFile[sampleName]['isSignal'] == 2 :
alternative_signals.append(sampleName)
if structureFile[sampleName]['isSignal'] == 1 :
cumulative_signals.append(sampleName)
if structureFile[sampleName]['isData'] == 1 :
data.append(sampleName)
if structureFile[sampleName]['isSignal'] == 0 and structureFile[sampleName]['isData'] == 0:
background_ids[sampleName] = ibkg
ibkg += 1
print("Number of Signals: " + str(len(signal_ids)))
print("Number of Alternative Signals: " + str(len(alternative_signals) - 1))
print("Number of Cumulative Signals: " + str(len(cumulative_signals)))
print("Number of Backgrounds: " + str(len(background_ids)))
if not os.path.isdir(outputDirDatacard + "/") :
os.mkdir(outputDirDatacard + "/")
# loop over cuts. One directory per cut will be created
for cutName in cuts:
print("cut = ", cutName)
try:
shutil.rmtree(outputDirDatacard + "/" + cutName)
except OSError:
pass
os.mkdir(outputDirDatacard + "/" + cutName)
#
# prepare the signals and background list of samples
# after removing the ones not to be used in this specific phase space
#
cut_signals = []
cut_backgrounds = []
for snameList, idmap in [(cut_signals, signal_ids), (cut_backgrounds, background_ids)]:
for sampleName in idmap:
if 'removeFromCuts' in structureFile[sampleName] and cutName in structureFile[sampleName]['removeFromCuts']:
# remove from the list
print(' remove ', sampleName, ' from ', cutName)
else:
snameList.append(sampleName)
print(" sampleNames = ", cut_signals + cut_backgrounds)
# loop over variables
for variableName, variable in variables.items():
if 'cuts' in variable and cutName not in variable['cuts']:
continue
print(" variableName = ", variableName)
tagNameToAppearInDatacard = cutName
# e.g. hww2l2v_13TeV_of0j
# to be defined in cuts.py
os.mkdir(outputDirDatacard + "/" + cutName + "/" + variableName)
os.mkdir(outputDirDatacard + "/" + cutName + "/" + variableName + "/shapes/") # and the folder for the root files
self._outFile = ROOT.TFile.Open(outputDirDatacard + "/" + cutName + "/" + variableName + "/shapes/histos_" + tagNameToAppearInDatacard + ".root", 'recreate')
ROOT.gROOT.GetListOfFiles().Remove(self._outFile)
# prepare yields
yieldsSig = {}
yieldsBkg = {}
yieldsData = {}
# Bin to be killed
#killBinSig = {}
#killBinBkg = {}
for snameList, yields in [(cut_signals, yieldsSig), (cut_backgrounds, yieldsBkg), (data, yieldsData)]:
for sampleName in snameList:
histo = self._getHisto(cutName, variableName, sampleName)
histo.SetDirectory(self._outFile)
# get the integral == rate from histogram
if structureFile[sampleName]['isData'] == 1:
yields['data'] = histo.Integral()
histo.SetName("histo_Data")
else:
# if 'removeNegNomVal' in structureFile[sampleName] and structureFile[sampleName]['removeNegNomVal'] :
# for iBin in range(0,histo.GetNbinsX()+2) :
# BinContent = histo.GetBinContent(iBin)
# if BinContent < 0.1 and not BinContent == 0:
# print('Killing Bin :' , sampleName , iBin , BinContent)
# if not sampleName in killBinSig : killBinSig[sampleName] = []
# killBinSig[sampleName].append(iBin)
# histo.SetBinContent(iBin,0.)
if 'scaleSampleForDatacard' in structure[sampleName]:
scaleFactor = 1.
if type(structure[sampleName]['scaleSampleForDatacard']) is dict:
try:
scaleFactor = structure[sampleName]['scaleSampleForDatacard'][cutName]
except:
pass
if type(structure[sampleName]['scaleSampleForDatacard']) is int or type(structure[sampleName]['scaleSampleForDatacard']) is float:
scaleFactor = structure[sampleName]['scaleSampleForDatacard']
histo.Scale(scaleFactor)
yields[sampleName] = histo.Integral()
#
# Remove statistical uncertainty from the histogram
# This may be used to suppress the effect on "autoMCstat" of a specific
# sample, by means of putting its uncertainty to 0
#
if 'removeStatUnc' in structureFile[sampleName] and structureFile[sampleName]['removeStatUnc']:
self._removeStatUncertainty (histo)
self._outFile.cd()
histo.Write()
# Loop over alternative signal samples. One card per signal (there is always at least one entry (""))
for signalName in alternative_signals:
if signalName == '':
signals = [name for name in cumulative_signals if name in cut_signals]
else:
if signalName not in cut_signals:
continue
signals = [signalName] + [name for name in cumulative_signals if name in cut_signals]
processes = signals + cut_backgrounds
print(" Alternative signal: " + str(signalName))
alternativeSignalName = str(signalName)
alternativeSignalTitle = ""
if alternativeSignalName != "":
alternativeSignalTitle = "_" + str(signalName)
# start creating the datacard
cardPath = outputDirDatacard + "/" + cutName + "/" + variableName + "/datacard" + alternativeSignalTitle + ".txt"
print(' Writing to ' + cardPath )
card = open( cardPath ,"w")
print(' headers..')
card.write('## Shape input card\n')
card.write('imax 1 number of channels\n')
card.write('jmax * number of background\n')
card.write('kmax * number of nuisance parameters\n')
card.write('-'*100+'\n')
card.write('bin %s' % tagNameToAppearInDatacard+'\n')
if len(data) == 0:
self._logger.warning( 'no data, no fun! ')
#raise RuntimeError('No Data found!')
yieldsData['data'] = 0
card.write('observation %.0f\n' % yieldsData['data'])
card.write('shapes * * '+
'shapes/histos_' + tagNameToAppearInDatacard + ".root" +
' histo_$PROCESS histo_$PROCESS_$SYSTEMATIC' + '\n')
card.write('shapes data_obs * '+
'shapes/histos_' + tagNameToAppearInDatacard + ".root" +
' histo_Data' + '\n')
# shapes * * shapes/hww-19.36fb.mH125.of_vh2j_shape_mll.root histo_$PROCESS histo_$PROCESS_$SYSTEMATIC
# shapes data_obs * shapes/hww-19.36fb.mH125.of_vh2j_shape_mll.root histo_Data
columndef = 30
# adapt column length to long bin names
if len(tagNameToAppearInDatacard) >= (columndef -5) :
columndef = len(tagNameToAppearInDatacard) + 7
for name in signals :
if len(name)>= (columndef -5) :
columndef = len(name) + 7
print(' processes and rates..')
card.write('bin'.ljust(80))
card.write(''.join([tagNameToAppearInDatacard.ljust(columndef)] * (len(signals) + len(cut_backgrounds)))+'\n')
card.write('process'.ljust(80))
card.write(''.join(name.ljust(columndef) for name in signals))
card.write(''.join(name.ljust(columndef) for name in cut_backgrounds))
card.write('\n')
card.write('process'.ljust(80))
card.write(''.join(('%d' % signal_ids[name]).ljust(columndef) for name in signals))
card.write(''.join(('%d' % background_ids[name]).ljust(columndef) for name in cut_backgrounds))
card.write('\n')
card.write('rate'.ljust(80))
card.write(''.join(('%-.4f' % yieldsSig[name]).ljust(columndef) for name in signals))
card.write(''.join(('%-.4f' % yieldsBkg[name]).ljust(columndef) for name in cut_backgrounds))
card.write('\n')
card.write('-'*100+'\n')
print(' nuisances..')
nuisanceGroups = collections.defaultdict(list)
# add normalization and shape nuisances
for nuisanceName, nuisance in nuisances.items():
if 'type' not in nuisance:
raise RuntimeError('Nuisance ' + nuisanceName + ' is missing the type specification')
if nuisanceName == 'stat' or nuisance['type'] == 'rateParam':
# will deal with these later
continue
# check if a nuisance can be skipped because not in this particular cut
if 'cuts' in nuisance and cutName not in nuisance['cuts']:
continue
if nuisance['type'] in ['lnN', 'lnU']:
# why is adding CMS_ not the default for lnN/lnU? (Y.I. 2019.11.06)
entryName = nuisance['name']
if 'perRecoBin' in nuisance.keys() and nuisance['perRecoBin'] == True:
entryName += "_"+cutName
card.write(entryName.ljust(80-20))
if 'AsShape' in nuisance and float(nuisance['AsShape']) >= 1.:
print(">>>>>", nuisance['name'], " was derived as a lnN uncertainty but is being treated as a shape")
card.write(('shape').ljust(20))
for sampleName in processes:
if 'cuts_samples' in nuisance and sampleName in nuisance['cuts_samples'] and cutName not in nuisance['cuts_samples'][sampleName]:
# If the cuts_samples options is there and the sample is inserted in the dictionary
# check if the current cutName is included. Excluded the cuts not in the list
print("Removing nuisance ", nuisanceName, " for sample ", sampleName, " from cut ", cutName)
card.write(('-').ljust(columndef))
elif ('all' in nuisance and nuisance['all'] == 1) or \
('samples' in nuisance and sampleName in nuisance['samples']):
histo = self._getHisto(cutName, variableName, sampleName)
histoUp = histo.Clone('%s_%sUp' % (histo.GetName(), nuisance['name']))
histoDown = histo.Clone('%s_%sDown' % (histo.GetName(), nuisance['name']))
histoUp.SetDirectory(self._outFile)
histoDown.SetDirectory(self._outFile)
if 'scaleSampleForDatacard' in structure[sampleName]:
scaleFactor = 1.
if type(structure[sampleName]['scaleSampleForDatacard']) is dict:
try:
scaleFactor = structure[sampleName]['scaleSampleForDatacard'][cutName]
except:
pass
if type(structure[sampleName]['scaleSampleForDatacard']) is int or type(structure[sampleName]['scaleSampleForDatacard']) is float:
scaleFactor = structure[sampleName]['scaleSampleForDatacard']
histoUp.Scale(scaleFactor)
histoDown.Scale(scaleFactor)
if '/' in nuisance['samples'][sampleName]:
up, down = nuisance['samples'][sampleName].split('/')
histoUp.Scale(float(up))
histoDown.Scale(float(down))
else:
histoUp.Scale(float(nuisance['samples'][sampleName]))
histoDown.Scale(1. / float(nuisance['samples'][sampleName]))
self._outFile.cd()
histoUp.Write()
histoDown.Write()
card.write('1.000'.ljust(columndef))
else:
card.write(('-').ljust(columndef))
else:
card.write((nuisance['type']).ljust(20))
if 'all' in nuisance and nuisance ['all'] == 1: # for all samples
card.write(''.join(('%-.4f' % nuisance['value']).ljust(columndef) for _ in processes))
else:
# apply only to selected samples
for sampleName in processes:
if sampleName in nuisance['samples']:
# in this case nuisance['samples'] is a dict mapping sample name to nuisance values in string
card.write(('%s' % nuisance['samples'][sampleName]).ljust(columndef))
else:
card.write(('-').ljust(columndef))
elif nuisance['type'] == 'shape':
#
# 'skipCMS' is a flag not to introduce "CMS" automatically in the name
# of the nuisance.
# It may be needed for combination purposes with ATLAS
# and agreed upon for all CMS analyses.
# For example: theoretical uncertainties on ggH with migration scheme 2017
#
if 'skipCMS' in nuisance and nuisance['skipCMS'] == 1:
entryName = nuisance['name']
else:
entryName = 'CMS_' + nuisance['name']
if 'perRecoBin' in nuisance.keys() and nuisance['perRecoBin'] == True:
entryName += "_"+cutName
card.write(entryName.ljust(80-20))
if 'AsLnN' in nuisance and float(nuisance['AsLnN']) >= 1.:
print(">>>>>", nuisance['name'], " was derived as a shape uncertainty but is being treated as a lnN")
card.write(('lnN').ljust(20))
for sampleName in processes:
if 'cuts_samples' in nuisance and sampleName in nuisance['cuts_samples'] and cutName not in nuisance['cuts_samples'][sampleName]:
# If the cuts_samples options is there and the sample is inserted in the dictionary
# check if the current cutName is included. Excluded the cuts not in the list
print("Removing nuisance ", nuisanceName, " for sample ", sampleName, " from cut ", cutName)
card.write(('-').ljust(columndef))
elif ('all' in nuisance and nuisance['all'] == 1) or \
('samples' in nuisance and sampleName in nuisance['samples']):
histo = self._getHisto(cutName, variableName, sampleName)
histoUp = self._getHisto(cutName, variableName, sampleName, '_' + nuisance['name'] + 'Up')
histoDown = self._getHisto(cutName, variableName, sampleName, '_' + nuisance['name'] + 'Down')
if (not histoUp or not histoDown):
print('Histogram for nuisance', nuisance['name'], 'on sample', sampleName, 'missing')
if self._skipMissingNuisance:
card.write(('-').ljust(columndef))
continue
histoIntegral = histo.Integral()
histoUpIntegral = histoUp.Integral()
histoDownIntegral = histoDown.Integral()
if histoIntegral > 0. and histoUpIntegral > 0.:
diffUp = (histoUpIntegral - histoIntegral)/histoIntegral/float(nuisance['AsLnN'])
else:
diffUp = 0.
if histoIntegral > 0. and histoDownIntegral > 0.:
diffDo = (histoDownIntegral - histoIntegral)/histoIntegral/float(nuisance['AsLnN'])
else:
diffDo = 0.
if 'symmetrize' in nuisance and nuisance['symmetrize']:
diff = (diffUp - diffDo) * 0.5
if diff >= 1.:
# can't symmetrize
diffUp = diff * 2. - 0.999
diffDo = -0.999
elif diff <= -1.:
diffUp = -0.999
diffDo = -diff * 2. - 0.999
else:
diffUp = diff
diffDo = -diff
lnNUp = 1. + diffUp
lnNDo = 1. + diffDo
#
# avoid 0.00/XXX and XXX/0.00 in the lnN --> ill defined nuisance
# if this happens put 0.00 ---> 1.00, meaning *no* effect of this nuisance
# Done like this because it is very likely what you are experiencing
# is a MC fluctuation in the varied sample, that is the up/down histogram has 0 entries!
#
# NB: with the requirement "histoUpIntegral > 0" and "histoDownIntegral > 0" this should never happen,
# except for some strange coincidence of "AsLnN" ...
# This fix is left, just for sake of safety
#
if lnNUp==0: lnNUp = 1.
if lnNDo==0: lnNDo = 1.
if abs(lnNUp - 1.) < 5.e-4 and abs(lnNDo - 1.) < 5.e-4:
card.write(('-').ljust(columndef))
else:
card.write((('%-.4f' % lnNUp)+"/"+('%-.4f' % lnNDo)).ljust(columndef))
else:
card.write(('-').ljust(columndef))
else:
card.write('shape'.ljust(20))
for sampleName in processes:
if 'cuts_samples' in nuisance and sampleName in nuisance['cuts_samples'] and cutName not in nuisance['cuts_samples'][sampleName]:
# If the cuts_samples options is there and the sample is inserted in the dictionary
# check if the current cutName is included. Excluded the cuts not in the list
print("Removing nuisance ", nuisanceName, " for sample ", sampleName, " from cut ", cutName)
card.write(('-').ljust(columndef))
elif ('all' in nuisance and nuisance ['all'] == 1) or \
('samples' in nuisance and sampleName in nuisance['samples']):
# save the nuisance histograms in the root file
if ('skipCMS' in nuisance.keys()) and nuisance['skipCMS'] == 1:
suffixOut = None
else:
suffixOut = '_CMS_' + nuisance['name']
if 'perRecoBin' in nuisance.keys() and nuisance['perRecoBin'] == True:
if ('skipCMS' in nuisance.keys()) and nuisance['skipCMS'] == 1:
suffixOut = "_"+nuisance['name']
else:
suffixOut = '_CMS_' + nuisance['name']
suffixOut += "_"+cutName
symmetrize = 'symmetrize' in nuisance and nuisance['symmetrize']
saved = self._saveNuisanceHistos(cutName, variableName, sampleName, '_' + nuisance['name'], suffixOut, symmetrize)
if saved:
card.write('1.000'.ljust(columndef))
else:
# saved can be false if skipMissingNuisance is true and histogram cannot be found
card.write(('-').ljust(columndef))
else:
card.write(('-').ljust(columndef))
# closing block if type == lnN or shape
card.write('\n')
if 'group' in nuisance:
nuisanceGroups[nuisance['group']].append(entryName)
for group in sorted(nuisanceGroups.keys()):
card.write('%s group = %s\n' % (group, ' '.join(nuisanceGroups[group])))
# done with normalization and shape nuisances.
# now add the stat nuisances
if 'stat' in nuisances:
nuisance = nuisances['stat']
if nuisance['type'] == 'auto':
# use autoMCStats feature of combine
card.write('* autoMCStats ' + nuisance['maxPoiss'] + ' ' + nuisance['includeSignal'])
# nuisance ['maxPoiss'] = Number of threshold events for Poisson modelling
# nuisance ['includeSignal'] = Include MC stat nuisances on signal processes (1=True, 0=False)
card.write('\n')
else:
# otherwise we process stat nuisances sample-by-sample
for sampleName in processes:
if sampleName not in nuisance['samples']:
continue
sampleNuisance = nuisance['samples'][sampleName]
sampleIndex = processes.index(sampleName)
if sampleNuisance['typeStat'] == 'uni' : # unified approach
# save the nuisance histograms in the root file
saved = self._saveNuisanceHistos(cutName, variableName, sampleName, 'stat', '_CMS_' + tagNameToAppearInDatacard + "_" + sampleName + "_stat")
if saved:
card.write(('CMS_' + tagNameToAppearInDatacard + "_" + sampleName + "_stat").ljust(80-20))
card.write((nuisance['type']).ljust(20))
# write the line in datacard. Only the column for this sample gets 1.000
for idx in range(len(processes)):
if idx == sampleIndex:
card.write(('1.000').ljust(columndef))
else:
card.write(('-').ljust(columndef))
card.write('\n')
elif sampleNuisance['typeStat'] == 'bbb': # bin-by-bin
histoTemplate = self._getHisto(cutName, variableName, sampleName)
for iBin in range(1, histoTemplate.GetNbinsX()+1):
if 'correlate' in sampleNuisance:
#specify the sample that is source of the variation
tag = "_ibin"+sampleName+"_"
correlate = list(sampleNuisance["correlate"]) # list of sample names to correlate bin-by-bin with this sample
else:
tag = "_ibin_"
correlate = []
# save the nuisance histograms in the root file
saved = self._saveNuisanceHistos(cutName, variableName, sampleName, tag + str(iBin) + '_stat',
'_CMS_' + tagNameToAppearInDatacard + "_" + sampleName + '_ibin_' + str(iBin) + '_stat')
if not saved:
continue
for other in list(correlate):
saved = self._saveNuisanceHistos(cutName, variableName, other, tag + str(iBin) + '_stat',
'_CMS_' + tagNameToAppearInDatacard + "_" + sampleName + '_ibin_' + str(iBin) + '_stat')
if not saved:
correlate.remove(other)
card.write(('CMS_' + tagNameToAppearInDatacard + "_" + sampleName + "_ibin_" + str(iBin) + "_stat").ljust(100-20))
card.write((nuisance['type']).ljust(20))
# write line in datacard. One line per sample per bin
for idx in range(len(processes)):
if idx == sampleIndex or processes[idx] in correlate:
card.write(('1.000').ljust(columndef))
else:
card.write(('-').ljust(columndef))
card.write('\n')
# now add the "rateParam" for the normalization
# e.g.: z_norm rateParam htsearch zll 1
# see: https://twiki.cern.ch/twiki/bin/view/CMS/HiggsWG/SWGuideNonStandardCombineUses#Rate_Parameters
# 'rateParam' has a separate treatment -> it's just a line at the end of the datacard. It defines "free floating" samples
# I do it here and not before because I want the freee floating parameters at the end of the datacard
for nuisance in nuisances.values():
if nuisance['type'] != 'rateParam':
continue
# check if a nuisance can be skipped because not in this particular cut
if 'cuts' in nuisance and cutName not in nuisance['cuts']:
continue
card.write(nuisance['name'].ljust(80-20))
card.write('rateParam'.ljust(25))
card.write(tagNameToAppearInDatacard.ljust(columndef)) # the bin
# there can be only 1 sample per rateParam
if len(nuisance['samples']) != 1:
raise RuntimeError('Invalid rateParam: number of samples != 1')
sampleName, initialValue = list(nuisance['samples'].items())[0]
if sampleName not in samples:
raise RuntimeError('Invalid rateParam: unknown sample %s' % sampleName)
card.write(sampleName.ljust(25))
card.write(('%-.4f' % float(initialValue)).ljust(columndef))
card.write('\n')
# now add other nuisances
# Are there other kind of nuisances I forgot?
card.write('-'*100+'\n')
card.write('\n')
card.close()
print(' done.')
self._outFile.Close()
if type(self._fileIn) is dict:
for source in self._fileIn.values():
source.Close()
else:
self._fileIn.Close()
# _____________________________________________________________________________
def _saveNuisanceHistos(self, cutName, variableName, sampleName, suffixIn, suffixOut = None, symmetrize = False):
histoUp = self._getHisto(cutName, variableName, sampleName, suffixIn + 'Up')
histoDown = self._getHisto(cutName, variableName, sampleName, suffixIn + 'Down')
if not histoUp or not histoDown:
print('Up/down histogram for', cutName, variableName, sampleName, suffixIn, 'missing')
if self._skipMissingNuisance:
return False
# else let ROOT raise
if 'scaleSampleForDatacard' in structure[sampleName]:
scaleFactor = 1.
if type(structure[sampleName]['scaleSampleForDatacard']) is dict:
try:
scaleFactor = structure[sampleName]['scaleSampleForDatacard'][cutName]
except:
pass
if type(structure[sampleName]['scaleSampleForDatacard']) is int or type(structure[sampleName]['scaleSampleForDatacard']) is float:
scaleFactor = structure[sampleName]['scaleSampleForDatacard']
histoUp.Scale(scaleFactor)
histoDown.Scale(scaleFactor)
if symmetrize:
histoNom = self._getHisto(cutName, variableName, sampleName)
histoDiff = histoUp.Clone('histoDiff')
histoDiff.Add(histoDown, -1.)
histoDiff.Scale(0.5)
histoUp.Reset()
histoUp.Add(histoNom)
histoUp.Add(histoDiff)
histoDown.Reset()
histoDown.Add(histoNom)
histoDown.Add(histoDiff, -1.)
histoUp.SetDirectory(self._outFile)
histoDown.SetDirectory(self._outFile)
if suffixOut:
histoUp.SetName('histo_%s%sUp' % (sampleName, suffixOut))
histoDown.SetName('histo_%s%sDown' % (sampleName, suffixOut))
self._outFile.cd()
histoUp.Write()
histoDown.Write()
return True
# _____________________________________________________________________________
def _getHisto(self, cutName, variableName, sampleName, suffix = None):
shapeName = '%s/%s/histo_%s' % (cutName, variableName, sampleName)
if suffix:
shapeName += suffix
if type(self._fileIn) is dict:
# by-sample ROOT file
histo = self._fileIn[sampleName].Get(shapeName)
else:
# Merged single ROOT file
histo = self._fileIn.Get(shapeName)
if not histo:
print(shapeName, 'not found')
if 'met' in variableName.lower() and 'met' in suffix.lower():
print(variableName, 'does not contain', suffix)
sys.exit()
if suffix:
print('Getting the nominal instead of varied')
return self._getHisto(cutName, variableName, sampleName)
return histo
# _____________________________________________________________________________
def _removeStatUncertainty(self, histo):
for iBin in range(0,histo.GetNbinsX()+2) :
histo.SetBinError(iBin,0.)
class list_maker:
def __init__(self, var, sep=',', type=None ):
self._type= type
self._var = var
self._sep = sep
def __call__(self,option, opt_str, value, parser):
if not hasattr(parser.values,self._var):
setattr(parser.values,self._var,[])
try:
array = value.split(self._sep)
if self._type:
array = [ self._type(e) for e in array ]
setattr(parser.values, self._var, array)
except:
print('Malformed option (comma separated list expected):',value)
def main():
sys.argv = argv
print('''
--------------------------------------------------------------------------------------------------
__ \ | | \ | |
| | _` | __| _` | __| _` | __| _` | |\/ | _` | | / _ \ __|
| | ( | | ( | ( ( | | ( | | | ( | < __/ |
____/ \__,_| \__| \__,_| \___| \__,_| _| \__,_| _| _| \__,_| _|\_\ \___| _|
--------------------------------------------------------------------------------------------------
''')
usage = 'usage: %prog [options]'
parser = optparse.OptionParser(usage)
parser.add_option('--tag' , dest='tag' , help='Tag used for the shape file name' , default=None)
parser.add_option('--sigset' , dest='sigset' , help='Signal samples [SM]' , default='SM')
parser.add_option('--outputDirDatacard' , dest='outputDirDatacard' , help='output directory' , default='./datacards')
parser.add_option('--inputFile' , dest='inputFile' , help='input directory' , default='./input.root')
parser.add_option('--structureFile' , dest='structureFile' , help='file with datacard configurations' , default=None )
parser.add_option('--nuisancesFile' , dest='nuisancesFile' , help='file with nuisances configurations' , default=None )
parser.add_option('--cardList' , dest="cardList" , help="List of cuts to produce datacards" , default=[], type='string' , action='callback' , callback=list_maker('cardList',','))
parser.add_option('--skipMissingNuisance', dest='skipMissingNuisance', help="Don't write nuisance lines when histograms are missing", default=False, action='store_true')
# read default parsing options as well
# hwwtools.addOptions(parser)
# hwwtools.loadOptDefaults(parser)
(opt, args) = parser.parse_args()
#sys.argv.append( '-b' )
#print(" configuration file = ", opt.pycfg)
global cuts, plot
import glob
import json
from mkShapesRDF.shapeAnalysis.ConfigLib import ConfigLib
lFile = list(filter(lambda k: os.path.isfile(k), glob.glob('configs/*json')))
lFile.sort(key= lambda x: os.path.getmtime(x))
lFile = lFile[-1]
with open(lFile) as file:
d = json.load(file)
ConfigLib.loadDict(d, globals())
cuts = cuts['cuts']
groupPlot = plot['groupPlot']
legend = plot['legend']
plot = plot['plot']
inputFile = outputFolder + '/' + outputFile
# print(" inputFile = ", opt.inputFile)
# print(" outputDirDatacard = ", opt.outputDirDatacard)
# if not opt.debug:
# pass
# elif opt.debug == 2:
# print('Logging level set to DEBUG (%d)' % opt.debug)
# logging.basicConfig( level=logging.DEBUG )
# elif opt.debug == 1:
# print('Logging level set to INFO (%d)' % opt.debug)
# logging.basicConfig( level=logging.INFO )
# if opt.nuisancesFile == None :
# print(" Please provide the nuisances structure if you want to add nuisances ")
#
# if opt.structureFile == None :
# print(" Please provide the datacard structure ")
# exit ()
ROOT.TH1.SetDefaultSumw2(True)
factory = DatacardFactory()
factory._tag = tag
factory._skipMissingNuisance = opt.skipMissingNuisance
# ## load the samples
# ## Use flag to skip loading samples files, as currently done in mkPlot.py
# _samples_noload = True
# samples = {}
# if os.path.exists(opt.samplesFile) :
# handle = open(opt.samplesFile,'r')
# exec(handle)
# handle.close()
#
# ## load the cuts
# cuts = {}
# if os.path.exists(opt.cutsFile) :
# handle = open(opt.cutsFile,'r')
# exec(handle)
# handle.close()
#
# ## load the variables
# variables = {}
# if os.path.exists(opt.variablesFile) :
# handle = open(opt.variablesFile,'r')
# exec(handle)
# handle.close()
#
# ## load the nuisances
# nuisances = collections.OrderedDict()
# if os.path.exists(opt.nuisancesFile) :
# handle = open(opt.nuisancesFile,'r')
# exec(handle)
# handle.close()
import mkShapesRDF.shapeAnalysis.LatinosUtils as utils
subsamplesmap = utils.flatten_samples(samples)
categoriesmap = utils.flatten_cuts(cuts)
utils.update_variables_with_categories(variables, categoriesmap)
utils.update_nuisances_with_subsamples(nuisances, subsamplesmap)
utils.update_nuisances_with_categories(nuisances, categoriesmap)
#sys.exit()
# ## load the structure file (use flattened sample and cut names)
# structure = collections.OrderedDict()
# if os.path.exists(opt.structureFile) :
# handle = open(opt.structureFile,'r')
# exec(handle)
# handle.close()
## command-line cuts restrictions
if len(opt.cardList)>0:
try:
newCuts = []
for iCut in opt.cardList:
for iOptim in optim:
newCuts.append(iCut+'_'+iOptim)
opt.cardList = newCuts
print(opt.cardList)
except:
print("No optim dictionary")
cut2del = []
for iCut in cuts:
if not iCut in opt.cardList : cut2del.append(iCut)
for iCut in cut2del : del cuts[iCut]
factory.makeDatacards( inputFile , opt.outputDirDatacard, variables, cuts, samples, structure, nuisances)
if __name__ == '__main__':
main()
#!/usr/bin/env python
import sys
argv = sys.argv
sys.argv = argv[:1]
import optparse
#import LatinoAnalysis.Gardener.hwwtools as hwwtools
import os
import os.path
#import logging
from collections import OrderedDict
from multiprocessing import Process
#from multiprocessing import Process
#import os.path
# X. Janssen - 21 March 2018
# PlotFactory splitted from this file and moved to python to be able to use in other scripts (mkACPlot.py)
#
from mkShapesRDF.shapeAnalysis.PlotFactory import PlotFactory
if __name__ == '__main__':
main()
def main():
sys.argv = argv
usage = 'usage: %prog [options]'
parser = optparse.OptionParser(usage)
parser.add_option('--scaleToPlot' , dest='scaleToPlot' , help='scale of maxY to maxHistoY' , default=3.0 , type=float )
parser.add_option('--minLogC' , dest='minLogC' , help='min Y in log plots' , default=0.01 , type=float )
parser.add_option('--maxLogC' , dest='maxLogC' , help='max Y in log plots' , default=100 , type=float )
parser.add_option('--minLogCratio' , dest='minLogCratio' , help='min Y in log ratio plots' , default=0.001 , type=float )
parser.add_option('--maxLogCratio' , dest='maxLogCratio' , help='max Y in log ratio plots' , default=10 , type=float )
parser.add_option('--maxLinearScale' , dest='maxLinearScale' , help='scale factor for max Y in linear plots (1.45 magic number as default)' , default=1.45 , type=float )
parser.add_option('--outputDirPlots' , dest='outputDirPlots' , help='output directory' , default='./')
parser.add_option('--inputFile' , dest='inputFile' , help='input file with histograms' , default='input.root')
parser.add_option('--tag' , dest='tag' , help='Tag used for the shape file name. Used if inputFile is a directory', default=None)
parser.add_option('--nuisancesFile' , dest='nuisancesFile' , help='file with nuisances configurations' , default=None )
parser.add_option('--onlyVariable' , dest='onlyVariable' , help='draw only one variable (may be needed in post-fit plots)' , default=None)
parser.add_option('--onlyCut' , dest='onlyCut' , help='draw only one cut phase space (may be needed in post-fit plots)' , default=None)
parser.add_option('--onlyPlot' , dest='onlyPlot' , help='draw only specified plot type (comma-separated c, cratio, and/or cdifference)', default=None)
parser.add_option('--linearOnly' , dest='linearOnly' , help='Make linear plot only.', action='store_true', default=False)
parser.add_option('--logOnly' , dest='logOnly' , help='Make log plot only.', action='store_true', default=False)
parser.add_option('--fileFormats' , dest='fileFormats' , help='Output plot file formats (comma-separated png, pdf, root, C, and/or eps). Default "png,root"', default='png,root')
parser.add_option('--plotNormalizedIncludeData' , dest='plotNormalizedIncludeData' , help='plot also normalized distributions for data, for shape comparison purposes', default=None )
parser.add_option('--plotNormalizedDistributions' , dest='plotNormalizedDistributions' , help='plot also normalized distributions for optimization purposes' , action='store_true' , default=None )
parser.add_option('--plotNormalizedDistributionsTHstack' , dest='plotNormalizedDistributionsTHstack' , help='plot also normalized distributions for optimization purposes, with stacked sig and bkg' , action='store_true' , default=None )
parser.add_option('--showIntegralLegend' , dest='showIntegralLegend' , help='show the integral, the yields, in the legend' , default=0, type=float )
parser.add_option('--showRelativeRatio' , dest='showRelativeRatio' , help='draw instead of data-expected, (data-expected) / expected' , action='store_true', default=False)
parser.add_option('--showDataMinusBkgOnly', dest='showDataMinusBkgOnly', help='draw instead of data-expected, data-expected background only' , action='store_true', default=False)
parser.add_option('--removeWeight', dest='removeWeight', help='Remove weight S/B for PR plots, just do the sum' , action='store_true', default=False)
parser.add_option('--invertXY', dest='invertXY', help='Invert the weighting for X <-> Y. Instead of slices along Y, do slices along X' , action='store_true', default=False)
parser.add_option('--postFit', dest='postFit', help='Plot sum of post-fit backgrounds, and the data/post-fit ratio.' , default='n')
parser.add_option('--skipMissingNuisance', dest='skipMissingNuisance', help='Do not trigger errors if a nuisance is missing. To be used with absolute care!!!' , action='store_true', default=False)
parser.add_option('--removeMCStat', dest='removeMCStat', help='Do not plot the MC statistics contribution in the uncertainty band', action='store_true', default=False)
parser.add_option('--extraLegend' , dest='extraLegend' , help='User-specified additional legend' , default=None)
parser.add_option('--customize', dest='customizeKey', help="Optional parameters for the customizations script", default=None)
parser.add_option('--plotFancy', dest='plotFancy', help='Plot fancy data - bkg plot' , action='store_true', default=False)
parser.add_option('--NoPreliminary', dest='NoPreliminary', help='Remove preliminary status in plots' , action='store_true', default=False)
parser.add_option('--RemoveAllMC', dest='RemoveAllMC', help='Remove all MC in legend' , action='store_true', default=False)
parser.add_option('--parallelPlotting', dest='parallelPlotting', help='Plot each cut in parallel' , action='store_true', default=False)
# read default parsing options as well
# hwwtools.addOptions(parser)
# hwwtools.loadOptDefaults(parser)
(opt, args) = parser.parse_args()
# ROOT.gROOT.SetBatch()
print("")
#print(" configuration file =", opt.pycfg)
#print(" lumi =", opt.lumi)
print(" inputFile =", opt.inputFile)
print(" outputDirPlots =", opt.outputDirPlots)
print(" plotNormalizedDistributions =", opt.plotNormalizedDistributions)
print(" plotNormalizedIncludeData =", opt.plotNormalizedIncludeData )
print(" plotNormalizedDistributionsTHstack =", opt.plotNormalizedDistributionsTHstack)
print(" showIntegralLegend =", opt.showIntegralLegend)
print(" scaleToPlot =", opt.scaleToPlot)
print(" minLogC =", opt.minLogC)
print(" maxLogC =", opt.maxLogC)
print(" minLogCratio =", opt.minLogCratio)
print(" maxLogCratio =", opt.maxLogCratio)
print(" showRelativeRatio =", opt.showRelativeRatio)
print(" showDataMinusBkgOnly =", opt.showDataMinusBkgOnly)
print(" removeWeight =", opt.removeWeight)
print(" invertXY =", opt.invertXY )
print(" skipMissingNuisance =", opt.skipMissingNuisance)
print(" postFit =", opt.postFit)
print(" removeMCStat =", opt.removeMCStat)
print(" plotFancy =", opt.plotFancy)
print(" NoPreliminary =", opt.NoPreliminary )
print(" RemoveAllMC =", opt.RemoveAllMC )
print(" parallelPlotting =", opt.parallelPlotting)
print("")
opt.scaleToPlot = float(opt.scaleToPlot)
opt.minLogC = float(opt.minLogC)
opt.maxLogC = float(opt.maxLogC)
opt.minLogCratio = float(opt.minLogCratio)
opt.maxLogCratio = float(opt.maxLogCratio)
# if not opt.debug:
# pass
# elif opt.debug == 2:
# print 'Logging level set to DEBUG (%d)' % opt.debug
# logging.basicConfig( level=logging.DEBUG )
# elif opt.debug == 1:
# print 'Logging level set to INFO (%d)' % opt.debug
# logging.basicConfig( level=logging.INFO )
#samples = {}
#samples = OrderedDict()
# if opt.samplesFile == None :
# print " Please provide the samples structure (not strictly needed in mkPlot, since list of samples read from plot.py) "
# elif os.path.exists(opt.samplesFile) :
# # This line is needed for mkplot not to look for samples in eos.
# # Imagine the samples have been removed in eos, but the file with histograms
# # has been already generated, there is no need to check the existence of the samples on eos
# # NB: in samples.py the function "nanoGetSampleFiles" must handle this, if needed
# _samples_noload = True
# handle = open(opt.samplesFile,'r')
# exec(handle)
# handle.close()
# cuts = {}
# if os.path.exists(opt.cutsFile) :
# handle = open(opt.cutsFile,'r')
# exec(handle)
# handle.close()
#
# variables = {}
# if os.path.exists(opt.variablesFile) :
# handle = open(opt.variablesFile,'r')
# exec(handle)
# handle.close()
#
# nuisances = {}
# if opt.nuisancesFile == None :
# print " Please provide the nuisances structure if you want to add nuisances "
# elif os.path.exists(opt.nuisancesFile) :
# handle = open(opt.nuisancesFile,'r')
# exec(handle)
# handle.close()
import mkShapesRDF.shapeAnalysis.LatinosUtils as utils
# import glob
# import json
# lFile = list(filter(lambda k: os.path.isfile(k), glob.glob('configs/*json')))
# lFile.sort(key= lambda x: os.path.getmtime(x))
# lFile = lFile[-1]
# with open(lFile) as file:
# d = json.load(file)
from mkShapesRDF.shapeAnalysis.ConfigLib import ConfigLib
global plot
global cuts
ConfigLib.loadLatestJSON('configs', globals())
#ConfigLib.loadDict(d, globals())
print(dir())
print(globals().keys())
cuts = cuts['cuts']
groupPlot = plot['groupPlot']
legend = plot['legend']
plot = plot['plot']
inputFile = outputFolder + '/' + outputFile
subsamplesmap = utils.flatten_samples(samples)
categoriesmap = utils.flatten_cuts(cuts)
utils.update_variables_with_categories(variables, categoriesmap)
utils.update_nuisances_with_subsamples(nuisances, subsamplesmap)
utils.update_nuisances_with_categories(nuisances, categoriesmap)
# check if only one cut or only one variable
# is requested, and filter th elist of cuts and variables
# using this piece of information
if opt.onlyVariable != None :
list_to_remove = []
for variableName, variable in variables.iteritems():
if variableName != opt.onlyVariable :
list_to_remove.append(variableName)
for toRemove in list_to_remove:
del variables[toRemove]
print( " variables = ", variables)
if opt.onlyCut != None :
list_to_remove = []
for cutName, cutExtended in cuts.iteritems():
if cutName not in opt.onlyCut :
list_to_remove.append(cutName)
for toRemove in list_to_remove:
del cuts[toRemove]
print( " cuts = ", cuts)
# groupPlot = OrderedDict()
# plot = {}
# legend = {}
# if os.path.exists(opt.plotFile) :
# handle = open(opt.plotFile,'r')
# exec(handle)
# handle.close()
#energy = '13TeV'
#sys.exit()
#=====================
def launch_plot(inputFile, outputDirPlots, variables, cuts, samples, plot, nuisances, legend, groupPlot):
factory = PlotFactory()
factory._tag = tag
#factory._energy = opt.energy
factory._lumi = lumi
factory._plotNormalizedDistributions = opt.plotNormalizedDistributions
factory._plotNormalizedIncludeData = opt.plotNormalizedIncludeData
factory._plotNormalizedDistributionsTHstack = opt.plotNormalizedDistributionsTHstack
factory._showIntegralLegend = opt.showIntegralLegend
if opt.onlyPlot is not None:
factory._plotsToWrite = opt.onlyPlot.split(',')
factory._plotLinear = opt.linearOnly or not opt.logOnly
factory._plotLog = opt.logOnly or not opt.linearOnly
factory._scaleToPlot = opt.scaleToPlot
factory._minLogC = opt.minLogC
factory._maxLogC = opt.maxLogC
factory._minLogCratio = opt.minLogCratio
factory._maxLogCratio = opt.maxLogCratio
factory._maxLinearScale = opt.maxLinearScale
factory._minLogCdifference = opt.minLogCratio
factory._maxLogCdifference = opt.maxLogCratio
factory._showRelativeRatio = opt.showRelativeRatio
factory._showDataMinusBkgOnly = opt.showDataMinusBkgOnly
factory._removeWeight = opt.removeWeight
factory._invertXY = opt.invertXY
factory._fileFormats = opt.fileFormats.split(',')
factory._postFit = opt.postFit
factory._removeMCStat = opt.removeMCStat
factory._plotFancy = opt.plotFancy
factory._SkipMissingNuisance = opt.skipMissingNuisance
factory._extraLegend = opt.extraLegend
factory._preliminary = not opt.NoPreliminary
factory._removeAllMC = opt.RemoveAllMC
factory.makePlot(inputFile ,outputDirPlots, variables, cuts, samples, plot, nuisances, legend, groupPlot)
#===============================
if opt.parallelPlotting:
for cut in cuts:
p = Process(target=launch_plot( inputFile , plotPath, variables, [cut], samples, plot, nuisances,legend, groupPlot) )
p.start()
else:
launch_plot( inputFile , plotPath , variables, cuts, samples,
plot, nuisances, legend, groupPlot)
#!/usr/bin/env python
import json
import sys
from sys import exit
import ROOT
ROOT.gROOT.SetBatch(True)
import optparse
#import LatinoAnalysis.Gardener.hwwtools as hwwtools
import os.path
import string
import logging
#import LatinoAnalysis.Gardener.odict as odict
import traceback
from array import array
from collections import OrderedDict
import math
import numpy as np
#import root_numpy as rnp
import re
def hist2array(h, overflow=False, copy=True):
shift = 0
if overflow:
shift = 1
d = []
for i in range(1 - shift, h.GetNbinsX() + 1 + shift):
d.append(h.GetBinContent(i))
d = np.array(d)
return d
def np_array(_TArrayD):
return np.array(_TArrayD)
# ----------------------------------------------------- PlotFactory --------------------------------------
class PlotFactory:
_logger = logging.getLogger('PlotFactory')
# _____________________________________________________________________________
def __init__(self):
self._tag = ''
variables = {}
self._variables = variables
cuts = {}
self._cuts = cuts
samples = OrderedDict()
self._samples = samples
self._plotsToWrite = ['c', 'cratio', 'cdifference']
self._plotLinear = True
self._plotLog = True
outputDirPlots = {}
self._outputDirPlots = outputDirPlots
self._showIntegralLegend = 1
# 0 is no
self._FigNamePF = ''
self._fileFormats = ['png', 'root']
self._preliminary = True
self._removeAllMC = False
# _____________________________________________________________________________
def makePlot(self, inputFile, outputDirPlots, variables, cuts, samples, plot, nuisances, legend, groupPlot):
print ("==================")
print ("==== makePlot ====")
print ("==================")
self.defineStyle()
self._variables = variables
self._samples = samples
self._cuts = cuts
self._outputDirPlots = outputDirPlots
os.system ("mkdir " + outputDirPlots + "/")
#
# prepare plotter.html
#
text_file_html = open(self._outputDirPlots + "/" + "plotter.html", "w")
text_file_html.write("<script type=\"text/javascript\" src=\"https://root.cern.ch/js/latest/scripts/JSRootCore.js?gui\"></script>\n")
text_file_html.write("<div id=\"simpleGUI\" path=\"./\" files=\"\n")
#tcanvas = ROOT.TCanvas( "cc", "cc" , 800, 600 )
#tcanvasRatio = ROOT.TCanvas( "ccRatio", "ccRatio", 800, 800 )
## weight_X_tcanvas = ROOT.TCanvas( "weight_X_tcanvas", "weight_X_tcanvas", 800, 800 )
#weight_X_tcanvasRatio = ROOT.TCanvas( "weight_X_tcanvasRatio", "weight_X_tcanvasRatio", 800, 800 )
ROOT.TH1.SetDefaultSumw2(True)
dataColor = 1
#thsData = ROOT.THStack ("thsData", "thsData")
#thsSignal = ROOT.THStack ("thsSignal", "thsSignal")
#thsBackground = ROOT.THStack ("thsBackground","thsBackground")
#thsSignal_grouped = ROOT.THStack ("thsSignal_grouped", "thsSignal_grouped")
#thsBackground_grouped = ROOT.THStack ("thsBackground_grouped","thsBackground_grouped")
ROOT.gROOT.cd()
list_thsData = {}
list_thsSignal = {}
list_thsBackground = {}
list_thsSignal_grouped = {}
list_thsBackground_grouped = {}
list_thsSignal_grouped_normalized = {}
list_thsBackground_grouped_normalized = {}
list_tcanvas = {}
list_tcanvasRatio = {}
list_weight_X_tcanvasRatio = {}
list_tcanvasDifference = {}
list_weight_X_tcanvasDifference = {}
list_tcanvasSigVsBkg = {}
list_tcanvasSigVsBkgTHstack = {}
# standalont data-bkg plot
list_tcanvasDifference_Fancy = {}
generalCounter = 0
if os.path.isdir(inputFile):
# ONLY COMPATIBLE WITH OUTPUTS MERGED TO SAMPLE LEVEL!!
fileIn = {}
allFiles = os.listdir(inputFile)
for sampleName in self._samples:
fileIn[sampleName] = ROOT.TFile.Open(inputFile+'/plots_%s_ALL_%s.root' % (self._tag, sampleName))
if not fileIn[sampleName]:
raise RuntimeError('Input file for sample ' + sampleName + ' missing')
if os.path.exists(inputFile+'/plots_total.root'):
fileIn['total'] = ROOT.TFile.Open(inputFile+'/plots_total.root')
else:
fileIn = ROOT.TFile(inputFile, "READ")
#---- save one TCanvas for every cut and every variable
for cutName in self._cuts :
print ("cut =", cutName)
for variableName, variable in self._variables.items():
if 'cuts' in variable and cutName not in variable['cuts']:
continue
if type(fileIn) is not dict and not fileIn.GetDirectory(cutName+"/"+variableName):
continue
print("variableName =", variableName)
if not "divideByBinWidth" in variable.keys():
variable["divideByBinWidth"] = 0
tcanvas = ROOT.TCanvas( "cc" + cutName + "_" + variableName, "cc" , 800, 600 )
tcanvasRatio = ROOT.TCanvas( "ccRatio" + cutName + "_" + variableName, "ccRatio", 800, 800 )
weight_X_tcanvasRatio = ROOT.TCanvas( "weight_X_tcanvasRatio" + cutName + "_" + variableName, "weight_X_tcanvasRatio", 800, 800 )
tcanvasDifference = ROOT.TCanvas( "ccDifference" + cutName + "_" + variableName, "ccDifference", 800, 800 )
weight_X_tcanvasDifference = ROOT.TCanvas( "weight_X_tcanvasDifference" + cutName + "_" + variableName, "weight_X_tcanvasDifference", 800, 800 )
if self._plotNormalizedDistributions :
tcanvasSigVsBkg = ROOT.TCanvas( "ccSigVsBkg" + cutName + "_" + variableName, "cc" , 800, 700 )
if self._plotNormalizedDistributionsTHstack :
tcanvasSigVsBkgTHstack = ROOT.TCanvas( "ccTHstackSigVsBkg" + cutName + "_" + variableName, "cc" , 800, 600 )
list_tcanvas [generalCounter] = tcanvas
list_tcanvasRatio [generalCounter] = tcanvasRatio
list_weight_X_tcanvasRatio [generalCounter] = weight_X_tcanvasRatio
list_tcanvasDifference [generalCounter] = tcanvasDifference
list_weight_X_tcanvasDifference [generalCounter] = weight_X_tcanvasDifference
if self._plotNormalizedDistributions :
list_tcanvasSigVsBkg [generalCounter] = tcanvasSigVsBkg
if self._plotNormalizedDistributionsTHstack :
list_tcanvasSigVsBkgTHstack [generalCounter] = tcanvasSigVsBkgTHstack
tcanvasDifference_Fancy = ROOT.TCanvas( "ccDifference_Fancy" + cutName + "_" + variableName, "ccDifference_Fancy", 800, 800 )
list_tcanvasDifference_Fancy [generalCounter] = tcanvasDifference_Fancy
histos = {}
histos_grouped = {}
canvasNameTemplateRatio = 'ccRatio_' + cutName + "_" + variableName
canvasNameTemplateDifference = 'ccDifference_' + cutName + "_" + variableName
#tcanvasRatio = ROOT.TCanvas( canvasNameTemplateRatio, variableName, 800, 800 )
canvasNameTemplate = 'c_' + cutName + "_" + variableName
#tcanvas = ROOT.TCanvas( canvasNameTemplate, variableName , 800, 600 )
tcanvas.cd()
#print(" and now this ...")
tgrData_vx = array('f')
tgrData_evx = array('f')
tgrData_vy = array('f')
tgrData_evy_up = array('f')
tgrData_evy_do = array('f')
# at least 1 "MC" should be around ... otherwise what are we plotting? Only data?
tgrMC_vx = array('f')
tgrMC_evx = array('f')
#these vectors are needed for nuisances accounting
nuisances_vy_up = {}
nuisances_vy_do = {}
ROOT.gROOT.cd()
thsData = ROOT.THStack ("thsData_" + cutName + "_" + variableName, "thsData_" + cutName + "_" + variableName)
#print('really before thstack ... one')
thsSignal = ROOT.THStack ("thsSignal_" + cutName + "_" + variableName, "thsSignal_" + cutName + "_" + variableName)
#print('really before thstack ... two')
thsBackground = ROOT.THStack ("thsBackground_" + cutName + "_" + variableName,"thsBackground_" + cutName + "_" + variableName)
#print('really before thstack ... three')
thsSignal_grouped = ROOT.THStack ("thsSignal_grouped_" + cutName + "_" + variableName, "thsSignal_grouped_" + cutName + "_" + variableName)
#print('really before thstack ... four')
thsBackground_grouped = ROOT.THStack ("thsBackground_grouped_" + cutName + "_" + variableName,"thsBackground_grouped_" + cutName + "_" + variableName)
list_thsData [generalCounter] = thsData
list_thsSignal [generalCounter] = thsSignal
list_thsBackground [generalCounter] = thsBackground
list_thsSignal_grouped [generalCounter] = thsSignal_grouped
list_thsBackground_grouped [generalCounter] = thsBackground_grouped
# for special case of plotting normalized
thsSignal_grouped_normalized = ROOT.THStack ("thsSignal_grouped_normalized_" + cutName + "_" + variableName, "thsSignal_grouped_normalized_" + cutName + "_" + variableName)
thsBackground_grouped_normalized = ROOT.THStack ("thsBackground_grouped_normalized_" + cutName + "_" + variableName,"thsBackground_grouped_normalized_" + cutName + "_" + variableName)
list_thsSignal_grouped_normalized [generalCounter] = thsSignal_grouped_normalized
list_thsBackground_grouped_normalized [generalCounter] = thsBackground_grouped_normalized
generalCounter += 1
#print('... after thstack ...')
sigSupList = []
sigSupList_grouped = []
# list of additional histograms to be used in the ratio plot
sigForAdditionalRatioList = {}
sigForAdditionalDifferenceList = {}
# enhanced list of nuisances, including bin-by-bin
mynuisances = {}
nexpected = 0
for sampleName, plotdef in plot.items():
if 'samples' in variable and sampleName not in variable['samples']:
continue
shapeName = cutName+"/"+variableName+'/histo_' + sampleName
print(' -> shapeName = ', shapeName)
if type(fileIn) is dict:
histo = fileIn[sampleName].Get(shapeName)
else:
histo = fileIn.Get(shapeName)
if not histo: continue
print(' --> ', histo)
print('new_histo_' + sampleName + '_' + cutName + '_' + variableName)
histos[sampleName] = histo.Clone('new_histo_' + sampleName + '_' + cutName + '_' + variableName)
#print(" -> sampleName = ", sampleName, " --> ", histos[sampleName].GetTitle(), " --> ", histos[sampleName].GetName(), " --> ", histos[sampleName].GetNbinsX())
#for iBinAmassiro in range(1, histos[sampleName].GetNbinsX()+1):
#print(" i = ", iBinAmassiro, " [" , sampleName, " ==> ", histos[sampleName].GetBinContent(iBinAmassiro))
# allow arbitrary scaling in MC (and DATA??), if needed
# for example to "see" a signal
if 'scale' in plotdef.keys() :
histos[sampleName].Scale(plotdef['scale'])
#print(" >> scale ", sampleName, " to ", plotdef['scale'])
# apply cut dependent scale factors
# for example when plotting different phase spaces
if 'cuts' in plotdef.keys() and cutName in plotdef['cuts']:
histos[sampleName].Scale( float( plotdef['cuts'][cutName] ) )
# data style
if plotdef['isData'] == 1 :
if variable['divideByBinWidth'] == 1:
histos[sampleName].Scale(1,"width")
#print(' plot[', sampleName, '][color] = ' , plotdef['color'])
histos[sampleName].SetMarkerColor(plotdef['color'])
histos[sampleName].SetMarkerSize(1)
histos[sampleName].SetMarkerStyle(20)
histos[sampleName].SetLineColor(self._getColor(plotdef['color']))
# blind data
if 'isBlind' in plotdef.keys() and plotdef['isBlind'] == 1:
histos[sampleName].Reset()
# Per variable blinding
if 'blind' in variable:
if cutName in variable['blind']:
blind_range = variable['blind'][cutName]
if blind_range == "full":
for iBin in range(1, histos[sampleName].GetNbinsX()+1):
histos[sampleName].SetBinContent(iBin, 0)
histos[sampleName].SetBinError (iBin, 0)
histos[sampleName].Reset()
elif type(blind_range) in [list,tuple] and len(blind_range)==2:
b0 = histos[sampleName].FindBin(blind_range[0])
b1 = histos[sampleName].FindBin(blind_range[1])
for iBin in range(1, histos[sampleName].GetNbinsX()+1):
if iBin >= b0 and iBin <= b1:
histos[sampleName].SetBinContent(iBin, 0)
histos[sampleName].SetBinError (iBin, 0)
# Allow to also pass arbitrary set of bin indexes to blind (e.g. for unrolled 2D histos)
elif type(blind_range) in [list,tuple] and len(blind_range)>2:
for iBin in blind_range:
histos[sampleName].SetBinContent(iBin, 0)
histos[sampleName].SetBinError (iBin, 0)
thsData.Add(histos[sampleName])
# first time fill vectors X axis
if len(tgrData_vx) == 0 :
dataColor = histos[sampleName].GetMarkerColor()
for iBin in range(1, histos[sampleName].GetNbinsX()+1):
tgrData_vx.append( histos[sampleName].GetBinCenter (iBin))
tgrData_evx.append( histos[sampleName].GetBinWidth (iBin) / 2.)
tgrData_vy.append( histos[sampleName].GetBinContent (iBin))
#print(" plot[", sampleName, "].keys() = ", plotdef.keys())
if ('isSignal' not in plotdef.keys() or plotdef['isSignal'] != 3) and not ('isBlind' in plotdef.keys() and plotdef['isBlind'] == 1) :
if variable['divideByBinWidth'] == 1:
tgrData_evy_up.append( self.GetPoissError(histos[sampleName].GetBinContent (iBin) * histos[sampleName].GetBinWidth (iBin) , 0, 1) / histos[sampleName].GetBinWidth (iBin) )
tgrData_evy_do.append( self.GetPoissError(histos[sampleName].GetBinContent (iBin) * histos[sampleName].GetBinWidth (iBin) , 1, 0) / histos[sampleName].GetBinWidth (iBin) )
else:
tgrData_evy_up.append( self.GetPoissError(histos[sampleName].GetBinContent (iBin) , 0, 1) )
tgrData_evy_do.append( self.GetPoissError(histos[sampleName].GetBinContent (iBin) , 1, 0) )
else :
tgrData_evy_up.append( 0 )
tgrData_evy_do.append( 0 )
else :
for iBin in range(1, histos[sampleName].GetNbinsX()+1):
tgrData_vx[iBin-1] = ( histos[sampleName].GetBinCenter (iBin))
tgrData_evx.append( histos[sampleName].GetBinWidth (iBin) / 2.)
tgrData_vy[iBin-1] += histos[sampleName].GetBinContent (iBin)
if 'isSignal' not in plotdef.keys() or plotdef['isSignal'] == 3 :
if variable['divideByBinWidth'] == 1:
tgrData_evy_up[iBin-1] = SumQ ( tgrData_evy_up[iBin-1], self.GetPoissError(histos[sampleName].GetBinContent (iBin) * histos[sampleName].GetBinWidth (iBin) , 0, 1) / histos[sampleName].GetBinWidth (iBin))
tgrData_evy_do[iBin-1] = SumQ ( tgrData_evy_do[iBin-1], self.GetPoissError(histos[sampleName].GetBinContent (iBin) * histos[sampleName].GetBinWidth (iBin) , 1, 0) / histos[sampleName].GetBinWidth (iBin))
else:
tgrData_evy_up[iBin-1] = SumQ ( tgrData_evy_up[iBin-1], self.GetPoissError(histos[sampleName].GetBinContent (iBin) , 0, 1) )
tgrData_evy_do[iBin-1] = SumQ ( tgrData_evy_do[iBin-1], self.GetPoissError(histos[sampleName].GetBinContent (iBin) , 1, 0) )
# if plotdef['isData'] == 1:
else:
# MC style
# only background "filled" histogram
if plotdef['isSignal'] == 0:
histos[sampleName].SetFillColorAlpha(self._getColor(plotdef['color']), 0.60)
histos[sampleName].SetLineColor(self._getColor(plotdef['color']))
if 'fill' in plotdef:
histos[sampleName].SetFillStype(plotdef['fill'])
#else:
# histos[sampleName].SetFillStyle(3001)
else :
histos[sampleName].SetFillStyle(0)
histos[sampleName].SetLineWidth(2)
histos[sampleName].SetLineColor(self._getColor(plotdef['color']))
# scale to luminosity if MC
#histos[sampleName].Scale(self._lumi) ---> NO! They are already scaled to luminosity in mkShape!
if plotdef['isSignal'] == 1 :
if variable['divideByBinWidth'] == 1:
histos[sampleName].Scale(1,"width")
thsSignal.Add(histos[sampleName])
elif plotdef['isSignal'] == 2 or plotdef['isSignal'] == 3 :
#print("SigSup histo: ", histos[sampleName])
if variable['divideByBinWidth'] == 1:
histos[sampleName].Scale(1,"width")
sigSupList.append(histos[sampleName])
if plotdef['isSignal'] == 3 :
#print("sigForAdditionalRatio histo: ", histos[sampleName])
sigForAdditionalRatioList[sampleName] = histos[sampleName]
sigForAdditionalDifferenceList[sampleName] = histos[sampleName]
else :
nexpected += histos[sampleName].Integral(1,histos[sampleName].GetNbinsX()) # it was (-1, -1) in the past, correct now. Overflow and underflow bins not taken into account
if variable['divideByBinWidth'] == 1:
histos[sampleName].Scale(1,"width")
# this is really background only, meaning if "isSignal" is 1,2,3 then it's not included here
thsBackground.Add(histos[sampleName])
#print(" adding to background: ", sampleName)
# handle 'stat' nuisance to create the bin-by-bin list of nuisances
# "massage" the list of nuisances accordingly
for nuisanceName, nuisance in nuisances.items():
if 'cuts' in nuisance and cutName not in nuisance['cuts']:
continue
# run only if this nuisance will affect the phase space defined in "cut"
#print(" nuisanceName = ", nuisanceName)
if nuisanceName == 'stat' : # 'stat' has a separate treatment, it's the MC/data statistics
#print(" nuisance = ", nuisance)
if 'samples' in nuisance.keys():
if sampleName in nuisance['samples'].keys() :
#print(" stat nuisances for ", sampleName)
if nuisance['samples'][sampleName]['typeStat'] == 'uni' : # unified approach
print('In principle nothing to be done here ... just wait')
if nuisance['samples'][sampleName]['typeStat'] == 'bbb' : # bin-by-bin
# add N ad hoc nuisances, one for each bin
for iBin in range(1, histos[sampleName].GetNbinsX()+1):
if ('ibin_' + str(iBin) + '_stat') not in mynuisances.keys() : # if new, add the new nuisance
# Name of the histogram: histo_" + sampleName + "_ibin_" + str(iBin) + "_statUp"
# Then the nuisance is "ibin_" + str(iBin) + "_stat"
mynuisances['ibin_' + str(iBin) + '_stat'] = {
'samples' : { sampleName : '1.00', },
}
else : # otherwise just add the new sample in the list of samples to be considered
mynuisances['ibin_' + str(iBin) + '_stat']['samples'][sampleName] = '1.00'
else :
if nuisanceName not in mynuisances.keys() :
if 'type' in nuisance.keys() and (nuisance['type'] == 'rateParam' or nuisance['type'] == 'lnU') :
pass
#print("skip this nuisance since 100 percent uncertainty :: ", nuisanceName)
else :
mynuisances[nuisanceName] = nuisances[nuisanceName]
nuisanceHistos = ({}, {})
for nuisanceName, nuisance in mynuisances.items():
# is this nuisance to be considered for this background?
if 'samples' in nuisance:
if sampleName not in nuisance['samples']:
continue
elif 'all' not in nuisance or nuisance['all'] != 1:
continue
if 'cuts' in nuisance and cutName not in nuisance['cuts']:
continue
if 'name' in nuisance:
shapeNameVars = tuple(cutName+"/"+variableName+'/histo_' + sampleName+"_"+nuisance['name']+var for var in ['Up', 'Down'])
else:
shapeNameVars = tuple(cutName+"/"+variableName+'/histo_' + sampleName+"_"+nuisanceName+var for var in ['Up', 'Down'])
if 'type' in nuisance and nuisance['type'] == 'lnN':
if 'samples' in nuisance:
values = nuisance['samples'][sampleName]
# example:
# 'samples' : {
# 'WW' : '1.00',
# 'ggH': '1.23/0.97'
# },
else: # 'all'
values = nuisance['value']
if '/' in values:
variations = map(float, values.split('/'))
else:
variations = (float(values), 2. - float(values))
# don't use histos[sampleName], or the second "scale" will fail!!!
for ivar, shapeNameVar in enumerate(shapeNameVars):
histoVar = histo.Clone(shapeNameVar.replace('/', '__'))
histoVar.Scale(variations[ivar])
nuisanceHistos[ivar][nuisanceName] = histoVar
else:
for ivar, shapeNameVar in enumerate(shapeNameVars):
if type(fileIn) is dict:
histoVar = fileIn[sampleName].Get(shapeNameVar)
else:
histoVar = fileIn.Get(shapeNameVar)
if histoVar != None :
nuisanceHistos[ivar][nuisanceName] = histoVar
elif not self._SkipMissingNuisance :
print(" This is bad, the nuisance ", nuisanceName, " is missing! You need to add it, maybe some jobs crashed?")
nuisanceHistos[ivar][nuisanceName]= fileIn.Get(cutName+"/"+variableName+'/histo_' + sampleName)
#nuisanceHistos[ivar][nuisanceName] = histoVar
else :
# if you had self._SkipMissingNuisance set to true, put the variation the same as the nominal
histoVar = histo.Clone(shapeNameVar.replace('/', '__'))
nuisanceHistos[ivar][nuisanceName] = histoVar
#
# I fill the nuisances_vy_up and nuisances_vy_do with the sum of all "up" and all "down" variations
#
for ivar, nuisances_vy in enumerate([nuisances_vy_up, nuisances_vy_do]):
for nuisanceName, nuisance in mynuisances.items():
#print(nuisanceName)
try:
histoVar = nuisanceHistos[ivar][nuisanceName]
#test = rnp.hist2array(histoVar, copy=False)
test = hist2array(histoVar, copy=False)
except KeyError:
# now, even if not considered this nuisance, I need to add it,
# so that in case is "empty" it will add the nominal value
# for this sample that is not affected by the nuisance
#print(nuisanceName, sampleName)
histoVar = histos[sampleName]
except TypeError:
histoVar = histos[sampleName]
else:
if 'scale' in plotdef:
histoVar.Scale(plotdef['scale'])
# apply cut dependent scale factors
# for example when plotting different phase spaces
if 'cuts' in plotdef and cutName in plotdef['cuts']:
histoVar.Scale(float(plotdef['cuts'][cutName]))
if variable["divideByBinWidth"] == 1:
histoVar.Scale(1., "width")
try:
vy = nuisances_vy[nuisanceName]
#print(sampleName, nuisanceName, vy)
except KeyError:
#vy = nuisances_vy[nuisanceName] = np.zeros_like(rnp.hist2array(histo, copy=False))
vy = nuisances_vy[nuisanceName] = np.zeros_like(hist2array(histo, copy=False))
# get the background sum
if plotdef['isSignal'] == 0: # ---> add the signal too????? See ~ 20 lines below
#vy += rnp.hist2array(histoVar, copy=False)
vy += hist2array(histoVar, copy=False)
# print(sampleName, nuisanceName, rnp.hist2array(histoVar, copy=False))
# create the group of histograms to plot
# this has to be done after the scaling of the previous lines
# andl also after all the rest, so that we inherit the style of the histograms
for sampleNameGroup, sampleConfiguration in groupPlot.items():
if sampleName in sampleConfiguration['samples']:
if sampleNameGroup in histos_grouped.keys() :
histos_grouped[sampleNameGroup].Add(histos[sampleName])
else :
histos_grouped[sampleNameGroup] = histos[sampleName].Clone('new_histo_group_' + sampleNameGroup + '_' + cutName + '_' + variableName)
# end sample loop
# set the colors for the groups of samples
for sampleNameGroup, sampleConfiguration in groupPlot.items():
if sampleNameGroup in histos_grouped.keys() :
histos_grouped[sampleNameGroup].SetLineColor(self._getColor(sampleConfiguration['color']))
if sampleConfiguration['isSignal'] == 0:
histos_grouped[sampleNameGroup].SetFillColorAlpha(self._getColor(sampleConfiguration['color']), 0.60)
histos_grouped[sampleNameGroup].SetLineColor(self._getColor(sampleConfiguration['color']))
if 'fill' in sampleConfiguration:
histos_grouped[sampleNameGroup].SetFillStyle(sampleConfiguration['fill'])
#else:
# histos_grouped[sampleNameGroup].SetFillStyle(3001)
else :
histos_grouped[sampleNameGroup].SetFillStyle(0)
histos_grouped[sampleNameGroup].SetLineWidth(2)
# fill the reference distribution with the background only distribution
# save the central values of the bkg sum for use for the nuisance band
#
# How could this be ==0 ?
# At least one MC sample should be defined ...
# but still, let's leave the possibility
#
if thsBackground.GetNhists() != 0:
last = thsBackground.GetStack().Last()
#tgrMC_vy = rnp.hist2array(last, copy=True)
tgrMC_vy = hist2array(last, copy=True)
for iBin in range(1,thsBackground.GetStack().Last().GetNbinsX()+1):
tgrMC_vx .append(thsBackground.GetStack().Last().GetBinCenter(iBin))
tgrMC_evx.append(thsBackground.GetStack().Last().GetBinWidth(iBin) / 2.)
# nuisances_err2_up = rnp.array(last.GetSumw2())[1:-1]
# nuisances_err2_do = rnp.array(last.GetSumw2())[1:-1]
nuisances_err2_up = np_array(last.GetSumw2())[1:-1]
nuisances_err2_do = np_array(last.GetSumw2())[1:-1]
if (self._removeMCStat):
nuisances_err2_up.fill(0)
nuisances_err2_do.fill(0)
else:
tgrMC_vy = np.zeros((0,))
nuisances_err2_up = np.zeros((0,))
nuisances_err2_do = np.zeros((0,))
#
# and now let's add the signal on top of the background stack
# It is important to do this after setting (without signal) tgrMC_vy
#
for sampleName, plotdef in plot.items():
if 'samples' in variable and sampleName not in variable['samples']:
continue
# MC style
if plotdef['isData'] == 0 :
if plotdef['isSignal'] == 1 :
thsBackground.Add(histos[sampleName])
#
# you need to add the signal as well, since the signal was considered in the nuisances vector
# otherwise you would introduce an uncertainty as big as the signal itself!!!
#
# IF these lines below are commented it means that the uncertainty band is drawn only on bkg-only stacked historgram,
# meaning that you will get the dashed band on bkg stacked, and signal (typicalli empty red) drawn on top
# without the uncertainty band.
# Is this what you want? I hope so ...
#
#if thsSignal.GetNhists() != 0:
#for iBin in range(1,thsSignal.GetStack().Last().GetNbinsX()+1):
#tgrMC_vy[iBin] += (thsSignal.GetStack().Last().GetBinContent(iBin))
#print(" nominal: ", iBin, " ===> ", thsBackground.GetStack().Last().GetBinContent(iBin))
#print(" tgrMC_vy = ", tgrMC_vy)
#else :
# for iBin in range(1, histos[sampleName].GetNbinsX()+1):
# tgrBkg_vx[iBin-1] = ( histos[sampleName].GetBinCenter (iBin))
# tgrBkg_evx.append( histos[sampleName].GetBinWidth (iBin) / 2.)
# tgrBkg_vy[iBin-1] += histos[sampleName].GetBinContent (iBin)
# tgrBkg_evy_up[iBin-1] = SumQ ( tgrBkg_evy_up[iBin-1], self.GetPoissError(histos[sampleName].GetBinContent (iBin) , 0, 1) )
# tgrBkg_evy_do[iBin-1] = SumQ ( tgrBkg_evy_do[iBin-1], self.GetPoissError(histos[sampleName].GetBinContent (iBin) , 1, 0) )
#print(">>>> Sample name: ", sampleName)
for nuisanceName in mynuisances.keys():
# now we need to tell wthether the variation is actually up or down ans sum in quadrature those with the same sign
up = nuisances_vy_up[nuisanceName]
do = nuisances_vy_do[nuisanceName]
#
# NB: the test underneath is to be read "for each bin check if up[bin] > tgrMC_vy[bin] and store in an array of True or False
# In other words, up_is_up is an array([False, False, False])
#
up_is_up = (up > tgrMC_vy)
#
# this one above is an array of booleans
#
dup2 = np.square(up - tgrMC_vy)
ddo2 = np.square(do - tgrMC_vy)
nuisances_err2_up += np.where(up_is_up, dup2, ddo2)
nuisances_err2_do += np.where(up_is_up, ddo2, dup2)
nuisances_err_up = np.sqrt(nuisances_err2_up)
nuisances_err_do = np.sqrt(nuisances_err2_do)
#
# NB: reminder: only background uncertainties have been considered in the nuisances, no impacts on the signal (isSignal = 1,2,3)
#
tgrData = ROOT.TGraphAsymmErrors(thsBackground.GetStack().Last().GetNbinsX())
for iBin in range(0, len(tgrData_vx)) :
tgrData.SetPoint (iBin, tgrData_vx[iBin], tgrData_vy[iBin])
tgrData.SetPointError(iBin, tgrData_evx[iBin], tgrData_evx[iBin], tgrData_evy_do[iBin], tgrData_evy_up[iBin])
tgrData.SetMarkerColor(dataColor)
tgrData.SetLineColor(dataColor)
## Default: --postFit 0 --> No additional line is drawn
## ----------------------------------------------------
## --postFit 1 --> line is prefit
if self._postFit == 'p':
tgrDataOverPF = tgrData.Clone("tgrDataOverPF") # use this for ratio with Post-Fit MC
histoPF = fileIn.Get(cutName+"/"+variableName+'/histo_total_prefit')
## --postFit 2 --> line is (S+B) postfit
if self._postFit == 's':
tgrDataOverPF = tgrData.Clone("tgrDataOverPF") # use this for ratio with Post-Fit MC
histoPF = fileIn.Get(cutName+"/"+variableName+'/histo_total_postfit_s')
## --postFit 3 --> line is B-only postfit
if self._postFit == 'b':
tgrDataOverPF = tgrData.Clone("tgrDataOverPF") # use this for ratio with Post-Fit MC
histoPF = fileIn.Get(cutName+"/"+variableName+'/histo_total_postfit_b')
# at this stage "thsBackground" and then "last" includes ALSO the signal
last = thsBackground.GetStack().Last()
tgrDataOverMC = tgrData.Clone("tgrDataOverMC")
tgrDataMinusMC = tgrData.Clone("tgrDataMinusMC")
tgrMCSigMinusMCBkg = tgrData.Clone("tgrMCSigMinusMCBkg") # is like saying "signal"
for iBin in range(0, len(tgrData_vx)) :
tgrDataOverMC.SetPoint (iBin, tgrData_vx[iBin], self.Ratio(tgrData_vy[iBin] , last.GetBinContent(iBin+1)) )
tgrDataOverMC.SetPointError(iBin, tgrData_evx[iBin], tgrData_evx[iBin], self.Ratio(tgrData_evy_do[iBin], last.GetBinContent(iBin+1)) , self.Ratio(tgrData_evy_up[iBin], last.GetBinContent(iBin+1)) )
if self._postFit == 'p' or self._postFit == 's' or self._postFit == 'b':
tgrDataOverPF.SetPoint(iBin, tgrData_vx[iBin], self.Ratio(tgrData_vy[iBin] , histoPF.GetBinContent(iBin+1)) )
tgrDataOverPF.SetPointError(iBin, tgrData_evx[iBin], tgrData_evx[iBin], self.Ratio(tgrData_evy_do[iBin], histoPF.GetBinContent(iBin+1)) , self.Ratio(tgrData_evy_up[iBin], last.GetBinContent(iBin+1)) )
print("Pre-fit ratio: " + str(self.Ratio(tgrData_vy[iBin] , histoPF.GetBinContent(iBin+1))))
print("Post-fit ratio: " + str(self.Ratio(tgrData_vy[iBin] , last.GetBinContent(iBin+1))))
print(iBin)
#
# data - MC :
# MC could be background only
# or it can include the signal.
# Default is background+signal (check isSignal = 1,2,3 options).
# tgrMC_vy is "background only" !!
# You can activate the data - "background only" by
# using the flag "showDataMinusBkgOnly".
# NB: this will change also the case of "(data - expected) / expected"
#
#
if self._showRelativeRatio :
if self._showDataMinusBkgOnly :
tgrDataMinusMC.SetPoint (iBin, tgrData_vx[iBin], self.Ratio( self.Difference(tgrData_vy[iBin] , tgrMC_vy[iBin]), tgrMC_vy[iBin] ) )
tgrDataMinusMC.SetPointError(iBin, tgrData_evx[iBin], tgrData_evx[iBin], self.Ratio(tgrData_evy_do[iBin], tgrMC_vy[iBin]) , self.Ratio(tgrData_evy_up[iBin], tgrMC_vy[iBin]) )
else :
tgrDataMinusMC.SetPoint (iBin, tgrData_vx[iBin], self.Ratio( self.Difference(tgrData_vy[iBin] , last.GetBinContent(iBin+1)), last.GetBinContent(iBin+1)) )
tgrDataMinusMC.SetPointError(iBin, tgrData_evx[iBin], tgrData_evx[iBin], self.Ratio(tgrData_evy_do[iBin], last.GetBinContent(iBin+1)) , self.Ratio(tgrData_evy_up[iBin], last.GetBinContent(iBin+1)) )
else :
if self._showDataMinusBkgOnly :
tgrDataMinusMC.SetPoint (iBin, tgrData_vx[iBin], self.Difference(tgrData_vy[iBin] , tgrMC_vy[iBin] ) )
tgrDataMinusMC.SetPointError(iBin, tgrData_evx[iBin], tgrData_evx[iBin], tgrData_evy_do[iBin] , tgrData_evy_up[iBin] )
tgrMCSigMinusMCBkg.SetPoint (iBin, tgrData_vx[iBin], self.Difference(last.GetBinContent(iBin+1) , tgrMC_vy[iBin] ) )
tgrMCSigMinusMCBkg.SetPointError(iBin, tgrData_evx[iBin], tgrData_evx[iBin], 0 , 0 ) # error set to 0 for sake of simplicity
else :
tgrDataMinusMC.SetPoint (iBin, tgrData_vx[iBin], self.Difference(tgrData_vy[iBin] , last.GetBinContent(iBin+1)) )
tgrDataMinusMC.SetPointError(iBin, tgrData_evx[iBin], tgrData_evx[iBin], tgrData_evy_do[iBin] , tgrData_evy_up[iBin] )
#
# if there is an histogram called 'histo_total'
# it means that post-fit plots are provided,
# and we can neglect about the nuisances as "quadrature sum" here
# and use directly the error bars (so far symmetric
# see https://hypernews.cern.ch/HyperNews/CMS/get/higgs-combination/995.html )
# from the histogram itself
#
# NB: the post-fit plot includes the signal!
#
#
special_shapeName = cutName+"/"+variableName+'/histo_total'
if type(fileIn) is dict:
if 'total' in fileIn:
histo_total = fileIn['total'].Get(special_shapeName)
else:
histo_total = None
else:
histo_total = fileIn.Get(special_shapeName)
if variable['divideByBinWidth'] == 1 and histo_total != None:
histo_total.Scale(1,"width")
print('--> histo_total = ', histo_total)
# if there is "histo_total" there is no need of explicit nuisances
if (not self._removeMCStat) or len(mynuisances.keys()) != 0 or histo_total!= None:
tgrMC = ROOT.TGraphAsymmErrors()
for iBin in range(0, len(tgrMC_vx)) :
tgrMC.SetPoint (iBin, tgrMC_vx[iBin], tgrMC_vy[iBin])
if histo_total:
#
# if there is histo_total, we plot the uncertainty band on top of sig+bkg, since histo_total IS sig+bkg and it's uncertainty accordingly
# This fix, in case of histo_total overrules the comment
# few lines above "Is this what you want? I hope so ..."
#
tgrMC.SetPoint(iBin, tgrMC_vx[iBin], histo_total.GetBinContent(iBin+1))
tgrMC.SetPointError(iBin, tgrMC_evx[iBin], tgrMC_evx[iBin], histo_total.GetBinError(iBin+1), histo_total.GetBinError(iBin+1))
else :
tgrMC.SetPointError(iBin, tgrMC_evx[iBin], tgrMC_evx[iBin], nuisances_err_do[iBin], nuisances_err_up[iBin])
tgrMCOverMC = tgrMC.Clone("tgrMCOverMC")
tgrMCMinusMC = tgrMC.Clone("tgrMCMinusMC")
for iBin in range(0, len(tgrMC_vx)) :
tgrMCOverMC.SetPoint (iBin, tgrMC_vx[iBin], 1.) # the ratio MC / MC is by construction 1
tgrMCMinusMC.SetPoint (iBin, tgrMC_vx[iBin], 0.) # the difference MC - MC is by construction 0
if histo_total:
# histo_total include also the signal
tgrMCOverMC.SetPointError(iBin, tgrMC_evx[iBin], tgrMC_evx[iBin], self.Ratio(histo_total.GetBinError(iBin+1), last.GetBinContent(iBin+1)), self.Ratio(histo_total.GetBinError(iBin+1), last.GetBinContent(iBin+1) ))
if self._showDataMinusBkgOnly :
if self._showRelativeRatio :
tgrMCMinusMC.SetPointError(iBin, tgrMC_evx[iBin], tgrMC_evx[iBin], self.Ratio(histo_total.GetBinError(iBin+1), tgrMC_vy[iBin]), self.Ratio(histo_total.GetBinError(iBin+1), tgrMC_vy[iBin]))
else :
# ok, this should have been the error on background only, without signal, properly propagated ... in first approximation it's the uncertainty on sig+bkg = histo_total
tgrMCMinusMC.SetPointError(iBin, tgrMC_evx[iBin], tgrMC_evx[iBin], histo_total.GetBinError(iBin+1), histo_total.GetBinError(iBin+1))
else :
if self._showRelativeRatio :
# not 100% sure if in the relative ratio I should put an uncertainty bar on the expected ... but I assume yes, as in the ratio plot, since expected-rate uncertainty is not propagated to "data"
tgrMCMinusMC.SetPointError(iBin, tgrMC_evx[iBin], tgrMC_evx[iBin], self.Ratio(histo_total.GetBinError(iBin+1), last.GetBinContent(iBin+1)), self.Ratio(histo_total.GetBinError(iBin+1), last.GetBinContent(iBin+1)))
else :
tgrMCMinusMC.SetPointError(iBin, tgrMC_evx[iBin], tgrMC_evx[iBin], histo_total.GetBinError(iBin+1), histo_total.GetBinError(iBin+1))
else :
# nuisances_err_do and nuisances_err_up do NOT include the signal
# thus in first approximation we say "uncertainty_(sig+bgk) / (sig+bkg) = uncertainty_(bkg) / bkg
# this is why everywhere here we have "tgrMC_vy[iBin]" instead of "last.GetBinContent(iBin+1)"
tgrMCOverMC.SetPointError(iBin, tgrMC_evx[iBin], tgrMC_evx[iBin], self.Ratio(nuisances_err_do[iBin], tgrMC_vy[iBin]), self.Ratio(nuisances_err_up[iBin], tgrMC_vy[iBin]))
if self._showDataMinusBkgOnly :
if self._showRelativeRatio :
tgrMCMinusMC.SetPointError(iBin, tgrMC_evx[iBin], tgrMC_evx[iBin], self.Ratio(nuisances_err_do[iBin], tgrMC_vy[iBin]), self.Ratio(nuisances_err_up[iBin], tgrMC_vy[iBin]))
else :
tgrMCMinusMC.SetPointError(iBin, tgrMC_evx[iBin], tgrMC_evx[iBin], nuisances_err_do[iBin], nuisances_err_up[iBin])
else :
if self._showRelativeRatio :
tgrMCMinusMC.SetPointError(iBin, tgrMC_evx[iBin], tgrMC_evx[iBin], self.Ratio(nuisances_err_do[iBin], tgrMC_vy[iBin]), self.Ratio(nuisances_err_up[iBin], tgrMC_vy[iBin]))
else :
tgrMCMinusMC.SetPointError(iBin, tgrMC_evx[iBin], tgrMC_evx[iBin], nuisances_err_do[iBin], nuisances_err_up[iBin])
tgrRatioList = {}
for samplesToRatioName, samplesToRatio in sigForAdditionalRatioList.items() :
tgrDataOverMCTemp = tgrData.Clone("tgrDataOverMC"+samplesToRatioName)
for iBin in range(0, len(tgrData_vx)) :
tgrDataOverMCTemp.SetPoint (iBin, tgrData_vx[iBin], self.Ratio(tgrData_vy[iBin] , samplesToRatio.GetBinContent(iBin+1)) )
tgrDataOverMCTemp.SetPointError(iBin, tgrData_evx[iBin], tgrData_evx[iBin], self.Ratio(tgrData_evy_do[iBin], samplesToRatio.GetBinContent(iBin+1)) , self.Ratio(tgrData_evy_up[iBin], samplesToRatio.GetBinContent(iBin+1)) )
if variableName == 'events' :
print(' >> ratio[', cutName, '][', samplesToRatioName, '] = ', self.Ratio(tgrData_vy[0] , samplesToRatio.GetBinContent(0+1)) )
tgrDataOverMCTemp.SetLineColor(samplesToRatio.GetLineColor())
tgrDataOverMCTemp.SetMarkerColor(samplesToRatio.GetLineColor())
tgrDataOverMCTemp.SetMarkerSize(0.3)
tgrRatioList[samplesToRatioName] = tgrDataOverMCTemp
tgrDifferenceList = {}
for samplesToDifferenceName, samplesToDifference in sigForAdditionalDifferenceList.items() :
tgrDataMinusMCTemp = tgrData.Clone("tgrDataMinusMC"+samplesToDifferenceName)
for iBin in range(0, len(tgrData_vx)) :
tgrDataMinusMCTemp.SetPoint (iBin, tgrData_vx[iBin], self.Difference(tgrData_vy[iBin] , samplesToDifference.GetBinContent(iBin+1)) )
tgrDataMinusMCTemp.SetPointError(iBin, tgrData_evx[iBin], tgrData_evx[iBin], tgrData_evy_do[iBin] , tgrData_evy_up[iBin] )
if variableName == 'events' :
print(' >> difference[', cutName, '][', samplesToDifferenceName, '] = ', self.Difference(tgrData_vy[0] , samplesToDifference.GetBinContent(0+1)) )
tgrDataMinusMCTemp.SetLineColor(samplesToDifference.GetLineColor())
tgrDataMinusMCTemp.SetMarkerColor(samplesToDifference.GetLineColor())
tgrDataMinusMCTemp.SetMarkerSize(0.3)
tgrDifferenceList[samplesToDifferenceName] = tgrDataMinusMCTemp
groupFlag = False
#---- prepare the grouped histograms
for sampleNameGroup, sampleConfiguration in groupPlot.items():
if 'samples' in variable and len(set(sampleConfiguration['samples']) & set(variable['samples'])) == 0:
continue
if sampleConfiguration['isSignal'] == 1 :
print("############################################################## isSignal 1", sampleNameGroup)
#
# if, for some reason, you want to scale only the overlaid signal
# for example to show the shape of the signal, without affecting the actual stacked (true) distribution
#
if 'scaleMultiplicativeOverlaid' in sampleConfiguration.keys() :
# may this clone not mess up too much with "gDirectory", see TH1::Copy
temp_overlaid = histos_grouped[sampleNameGroup].Clone()
temp_overlaid.Scale(sampleConfiguration['scaleMultiplicativeOverlaid'])
thsSignal_grouped.Add(temp_overlaid)
else :
thsSignal_grouped.Add(histos_grouped[sampleNameGroup])
elif sampleConfiguration['isSignal'] == 2 :
print("############################################################## isSignal 2", sampleNameGroup)
groupFlag = True
sigSupList_grouped.append(histos_grouped[sampleNameGroup])
# the signal is added on top of the background
# the signal has to be the last one in the dictionary!
# make it sure in plot.py
if groupFlag == False:
thsBackground_grouped.Add(histos_grouped[sampleNameGroup])
#---- now plot
if thsBackground.GetNhists() != 0:
print(" MC = ", thsBackground.GetStack().Last().Integral())
for ihisto in range(thsBackground.GetNhists()) :
print(" - ",ihisto, " - ", ((thsBackground.GetHists().At(ihisto))).GetName(), " = ", ((thsBackground.GetHists().At(ihisto))).Integral() )
if thsData.GetNhists() != 0:
print(" DATA = ", thsData.GetStack().Last().Integral())
# - get axis range
minXused = 0.
maxXused = 1.
minYused = 1.
maxYused = 1.
for sampleName, plotdef in plot.items():
if 'samples' in variable and sampleName not in variable['samples']:
continue
if plotdef['isData'] == 1 :
histos[sampleName].Draw("p")
minXused = histos[sampleName].GetXaxis().GetBinLowEdge(1)
maxXused = histos[sampleName].GetXaxis().GetBinUpEdge(histos[sampleName].GetNbinsX())
maxY = self.GetMaximumIncludingErrors(histos[sampleName])
histos[sampleName].SetMaximum(self._scaleToPlot * maxY)
maxYused = self._scaleToPlot * maxY
minYused = self.GetMinimum(histos[sampleName])
if thsBackground.GetNhists() != 0:
thsBackground.Draw("hist")
maxY = thsBackground.GetMaximum ()
minXused = thsBackground.GetXaxis().GetBinLowEdge(1)
maxXused = thsBackground.GetXaxis().GetBinUpEdge(thsBackground.GetHistogram().GetNbinsX())
if (self._scaleToPlot * maxY) > maxYused :
maxYused = self._scaleToPlot * maxY
minY = thsBackground.GetMinimum ()
if (minY < minYused) :
minYused = minY
if thsSignal.GetNhists() != 0:
thsSignal.Draw("hist")
maxY = thsSignal.GetMaximum ()
minXused = thsSignal.GetXaxis().GetBinLowEdge(1)
maxXused = thsSignal.GetXaxis().GetBinUpEdge(thsSignal.GetHistogram().GetNbinsX())
if (self._scaleToPlot * maxY) > maxYused :
maxYused = self._scaleToPlot * maxY
minY = thsSignal.GetMinimum ()
if (minY < minYused) :
minYused = minY
#print(" X axis = ", minXused, " - ", maxXused)
frame = ROOT.TH1F
frame = tcanvas.DrawFrame(minXused, 0.0, maxXused, 1.0)
# style from https://ghm.web.cern.ch/ghm/plots/MacroExample/myMacro.py
xAxis = frame.GetXaxis()
xAxis.SetNdivisions(6,5,0)
# setup axis names
# New proposal (following https://twiki.cern.ch/twiki/bin/viewauth/CMS/Internal/PubGuidelines)
# Set xaxis label if needed. Format should be variable name, followed by (units) or [units] if needed
if 'xaxis' in variable.keys() :
frame.GetXaxis().SetTitle(variable['xaxis'])
# If yaxis is set, we want to override normal conventions
if 'yaxis' in variable.keys():
frame.GetYaxis().SetTitle(variable['yaxis'])
else:
# Grab unit from x axis title -- capture part in () or [] brackets
xaxistitle = frame.GetXaxis().GetTitle()
unitpattern = '(?:\[|\()(\w+)(?:\]|\))'
unitsearch = re.search(unitpattern,xaxistitle)
unit = 'unit' if unitsearch is None else unitsearch.group(1)
# If dividing by bin width, yaxis should be "<Events / [unit]>"
if variable["divideByBinWidth"] == 1:
frame.GetYaxis().SetTitle("< Events / %s >"%unit)
#frame.GetYaxis().SetTitle("dN/d"+variable['xaxis'])
else:
# If using fixed bin width, yaxis should be "Events / bin size [unit]"
if len(variable['range']) == 3:
binsize = float(variable['range'][2] - variable['range'][1])/float(variable['range'][0])
frame.GetYaxis().SetTitle("Events / %g %s"%(binsize,unit))
# Otherwise, yaxis should be "Events / bin"
else:
frame.GetYaxis().SetTitle("Events / bin")
#
# - now draw
# - first the MC
# before the background+signal, and then only the signal alone
#
if len(groupPlot.keys()) == 0:
if thsBackground.GetNhists() != 0:
thsBackground.Draw("hist same")
if thsSignal.GetNhists() != 0:
#for ihisto in range(thsSignal.GetNhists()) :
#((thsSignal.GetHists().At(ihisto))).SetFillStyle(0)
#((thsSignal.GetHists().At(ihisto))).Draw("hist same")
thsSignal.Draw("hist same noclear")
else :
if thsBackground_grouped.GetNhists() != 0:
thsBackground_grouped.Draw("hist same")
if thsSignal_grouped.GetNhists() != 0:
thsSignal_grouped.Draw("hist same noclear")
if len(sigSupList_grouped) != 0:
for histo in sigSupList_grouped:
histo.Draw("hist same")
# if there is a systematic band draw it
# if there is "histo_total" there is no need of explicit nuisances
if (not self._removeMCStat) or len(mynuisances.keys()) != 0 or histo_total!= None:
tgrMC.SetLineColor(12)
tgrMC.SetFillColor(12)
tgrMC.SetLineWidth(2)
tgrMC.SetFillStyle(3004)
tgrMCOverMC.SetLineColor(12)
tgrMCOverMC.SetFillColor(12)
tgrMCOverMC.SetLineWidth(2)
tgrMCOverMC.SetFillStyle(3004)
tgrMC.Draw("2")
# - then the superimposed MC
if len(sigSupList) != 0 and groupFlag==False:
for hist in sigSupList:
hist.Draw("hist same")
# - then the DATA
if tgrData.GetN() != 0:
tgrData.Draw("P0")
else : # never happening if at least one data histogram is provided
for sampleName, plotdef in plot.items():
if 'samples' in variable and sampleName not in variable['samples']:
continue
if plotdef['isData'] == 1 :
histos[sampleName].Draw("p same")
#---- the Legend
tlegend = ROOT.TLegend(0.20, 0.65, 0.80, 0.88)
tlegend.SetFillColor(0)
tlegend.SetTextFont(42)
tlegend.SetTextSize(0.035)
tlegend.SetLineColor(0)
tlegend.SetShadowColor(0)
reversedSampleNames = list(self._samples)
reversedSampleNames.reverse()
if len(groupPlot.keys()) == 0:
for sampleName in reversedSampleNames:
try:
plotdef = plot[sampleName]
except KeyError:
continue
if plotdef['isData'] == 0 :
if 'nameHR' in plotdef.keys() :
if plotdef['nameHR'] != '' :
if self._showIntegralLegend == 0 :
tlegend.AddEntry(histos[sampleName], plotdef['nameHR'], "F")
else :
if variable["divideByBinWidth"] == 1:
nevents = histos[sampleName].Integral(1,histos[sampleName].GetNbinsX(),"width")
else:
nevents = histos[sampleName].Integral(1,histos[sampleName].GetNbinsX())
tlegend.AddEntry(histos[sampleName], plotdef['nameHR'] + " [" + str(round(nevents,1)) + "]", "F")
else :
if self._showIntegralLegend == 0 :
tlegend.AddEntry(histos[sampleName], sampleName, "F")
else :
if variable["divideByBinWidth"] == 1:
nevents = histos[sampleName].Integral(1,histos[sampleName].GetNbinsX(),"width")
else:
nevents = histos[sampleName].Integral(1,histos[sampleName].GetNbinsX())
tlegend.AddEntry(histos[sampleName], sampleName + " [" + str(round(nevents,1)) + "]", "F")
for sampleName in reversedSampleNames:
try:
plotdef = plot[sampleName]
except KeyError:
continue
if plotdef['isData'] == 1 :
if 'nameHR' in plotdef.keys() :
if self._showIntegralLegend == 0 :
tlegend.AddEntry(histos[sampleName], plotdef['nameHR'], "EPL")
else :
if variable["divideByBinWidth"] == 1:
nevents = histos[sampleName].Integral(1,histos[sampleName].GetNbinsX(),"width")
else:
nevents = histos[sampleName].Integral(1,histos[sampleName].GetNbinsX())
tlegend.AddEntry(histos[sampleName], plotdef['nameHR'] + " [" + str(round(nevents,1)) + "]", "EPL")
else :
if self._showIntegralLegend == 0 :
tlegend.AddEntry(histos[sampleName], sampleName, "EPL")
else :
if variable["divideByBinWidth"] == 1:
nevents = histos[sampleName].Integral(1,histos[sampleName].GetNbinsX(),"width")
else:
nevents = histos[sampleName].Integral(1,histos[sampleName].GetNbinsX())
tlegend.AddEntry(histos[sampleName], sampleName + " [" + str(round(nevents,1)) + "]", "EPL")
else :
for sampleNameGroup, sampleConfiguration in groupPlot.items():
if 'samples' in variable and len(set(sampleConfiguration['samples']) & set(variable['samples'])) == 0:
continue
if self._showIntegralLegend == 0 :
tlegend.AddEntry(histos_grouped[sampleNameGroup], sampleConfiguration['nameHR'], "F")
else :
if variable["divideByBinWidth"] == 1:
nevents = histos_grouped[sampleNameGroup].Integral(1,histos_grouped[sampleNameGroup].GetNbinsX(),"width")
else:
nevents = histos_grouped[sampleNameGroup].Integral(1,histos_grouped[sampleNameGroup].GetNbinsX())
tlegend.AddEntry(histos_grouped[sampleNameGroup], sampleConfiguration['nameHR'] + " [" + str(round(nevents,1)) + "]" , "F")
for sampleName in reversedSampleNames:
if 'samples' in variable and sampleName not in variable['samples']:
continue
try:
plotdef = plot[sampleName]
except KeyError:
continue
if plotdef['isData'] == 1 :
if 'nameHR' in plotdef.keys() :
if self._showIntegralLegend == 0 :
tlegend.AddEntry(histos[sampleName], plotdef['nameHR'], "EPL")
else :
if variable["divideByBinWidth"] == 1:
nevents = histos[sampleName].Integral(1,histos[sampleName].GetNbinsX(),"width")
else:
nevents = histos[sampleName].Integral(1,histos[sampleName].GetNbinsX())
print(" nevents [", sampleName, "] = ", nevents)
tlegend.AddEntry(histos[sampleName], plotdef['nameHR'] + " [" + str(round(nevents,1)) + "]", "EPL")
else :
if self._showIntegralLegend == 0 :
tlegend.AddEntry(histos[sampleName], sampleName , "EPL")
else :
if variable["divideByBinWidth"] == 1:
nevents = histos[sampleName].Integral(1,histos[sampleName].GetNbinsX(),"width")
else:
nevents = histos[sampleName].Integral(1,histos[sampleName].GetNbinsX())
print(" nevents [", sampleName, "] = ", nevents)
tlegend.AddEntry(histos[sampleName], sampleName + " [" + str(round(nevents,1)) + "]", "EPL")
# add "All MC" in the legend
if not self._removeAllMC :
# if there is "histo_total" there is no need of explicit nuisances
if len(mynuisances.keys()) != 0 or histo_total!= None:
if self._showIntegralLegend == 0 :
tlegend.AddEntry(tgrMC, "Syst.", "F")
else :
print(" nexpected = ", nexpected)
tlegend.AddEntry(tgrMC, "Syst. [" + str(round(nexpected,1)) + "]", "F")
tlegend.SetNColumns(2)
tlegend.Draw()
#change the CMS_lumi variables (see CMS_lumi.py)
import mkShapesRDF.shapeAnalysis.CMS_lumi as CMS_lumi
CMS_lumi.lumi_7TeV = "4.8 fb^{-1}"
CMS_lumi.lumi_8TeV = "18.3 fb^{-1}"
CMS_lumi.lumi_13TeV = "100 fb^{-1}"
CMS_lumi.writeExtraText = 1
CMS_lumi.extraText = "Preliminary"
if not self._preliminary :
CMS_lumi.extraText = ""
CMS_lumi.relPosX = 0.14
CMS_lumi.lumi_sqrtS = "13 TeV" # used with iPeriod = 0, e.g. for simulation-only plots (default is an empty string)
if 'sqrt' in legend.keys() :
CMS_lumi.lumi_sqrtS = legend['sqrt']
if 'lumi' in legend.keys() :
CMS_lumi.lumi_13TeV = legend['lumi']
else:
CMS_lumi.lumi_13TeV = 'L = %.1f fb^{-1}' % self._lumi
# Simple example of macro: plot with CMS name and lumi text
# (this script does not pretend to work in all configurations)
# iPeriod = 1*(0/1 7 TeV) + 2*(0/1 8 TeV) + 4*(0/1 13 TeV)
# For instance:
# iPeriod = 3 means: 7 TeV + 8 TeV
# iPeriod = 7 means: 7 TeV + 8 TeV + 13 TeV
# iPeriod = 0 means: free form (uses lumi_sqrtS)
iPeriod = 4
iPos = 0
CMS_lumi.CMS_lumi(tcanvas, iPeriod, iPos)
print("- draw tlegend")
#---- the Legend (end)
tlegend.Draw()
frame.GetYaxis().SetRangeUser( 0, maxYused )
# draw back all the axes
#frame.Draw("AXIS")
tcanvas.RedrawAxis()
if 'c' in self._plotsToWrite:
if self._plotLinear:
self._saveCanvas(tcanvas, self._outputDirPlots + "/" + canvasNameTemplate + self._FigNamePF)
if self._plotLog:
# log Y axis
frame.GetYaxis().SetRangeUser( max(self._minLogC, minYused), self._maxLogC * maxYused ) # Jonatan
#frame.GetYaxis().SetRangeUser( min(self._minLogC, minYused), self._maxLogC * maxYused ) # Jonatan
tcanvas.SetLogy(True)
# if plotLinear is true, we have already saved root and C (if in the list of formats)
self._saveCanvas(tcanvas, self._outputDirPlots + "/log_" + canvasNameTemplate + self._FigNamePF, imageOnly=self._plotLinear)
tcanvas.SetLogy(False)
if 'root' in self._fileFormats:
text_file_html.write(canvasNameTemplate + self._FigNamePF + ".root;\n")
# ~~~~~~~~~~~~~~~~~~~~
# plot with ratio plot
print("- draw with ratio")
canvasRatioNameTemplate = 'cratio_' + cutName + "_" + variableName
tcanvasRatio.cd()
canvasPad1Name = 'pad1_' + cutName + "_" + variableName
pad1 = ROOT.TPad(canvasPad1Name,canvasPad1Name, 0, 1-0.72, 1, 1)
pad1.SetTopMargin(0.098)
pad1.SetBottomMargin(0.020)
pad1.Draw()
#pad1.cd().SetGrid()
pad1.cd()
#print(" pad1 = ", pad1)
canvasFrameDistroName = 'frame_distro_' + cutName + "_" + variableName
frameDistro = pad1.DrawFrame(minXused, 0.0, maxXused, 1.0, canvasFrameDistroName)
#print(" pad1 = ", pad1)
# style from https://ghm.web.cern.ch/ghm/plots/MacroExample/myMacro.py
xAxisDistro = frameDistro.GetXaxis()
xAxisDistro.SetNdivisions(6,5,0)
xAxisDistro.SetLabelSize(0)
# setup axis names
# New proposal (following https://twiki.cern.ch/twiki/bin/viewauth/CMS/Internal/PubGuidelines)
# Set xaxis label if needed. Format should be variable name, followed by (units) or [units] if needed
if 'xaxis' in variable.keys() :
frameDistro.GetXaxis().SetTitle(variable['xaxis'])
# If yaxis is set, we want to override normal conventions
if 'yaxis' in variable.keys():
frameDistro.GetYaxis().SetTitle(variable['yaxis'])
else:
# Grab unit from x axis title -- capture part in () or [] brackets
xaxistitle = frameDistro.GetXaxis().GetTitle()
unitpattern = '(?:\[|\()(\w+)(?:\]|\))'
unitsearch = re.search(unitpattern,xaxistitle)
unit = 'unit' if unitsearch is None else unitsearch.group(1)
# If dividing by bin width, yaxis should be "<Events / [unit]>"
if variable["divideByBinWidth"] == 1:
frameDistro.GetYaxis().SetTitle("< Events / %s >"%unit)
#frameDistro.GetYaxis().SetTitle("dN/d"+variable['xaxis'])
else:
# If using fixed bin width, yaxis should be "Events / bin size [unit]"
if len(variable['range']) == 3:
binsize = float(variable['range'][2] - variable['range'][1])/float(variable['range'][0])
frameDistro.GetYaxis().SetTitle("Events / %g %s"%(binsize,unit))
# Otherwise, yaxis should be "Events / bin"
else:
frameDistro.GetYaxis().SetTitle("Events / bin")
#frameDistro.GetYaxis().SetRangeUser( 0, maxYused )
frameDistro.GetYaxis().SetRangeUser( min(0.001, minYused), maxYused )
if len(groupPlot.keys()) == 0:
if thsBackground.GetNhists() != 0:
thsBackground.Draw("hist same")
if thsSignal.GetNhists() != 0:
#for ihisto in range(thsSignal.GetNhists()) :
#((thsSignal.GetHists().At(ihisto))).SetFillStyle(0)
#((thsSignal.GetHists().At(ihisto))).Draw("hist same")
thsSignal.Draw("hist same noclear")
else :
if thsBackground_grouped.GetNhists() != 0:
thsBackground_grouped.Draw("hist same")
if thsSignal_grouped.GetNhists() != 0:
thsSignal_grouped.Draw("hist same noclear")
if len(sigSupList_grouped) != 0:
for histo in sigSupList_grouped:
histo.Draw("hist same")
# if there is "histo_total" there is no need of explicit nuisances
if (not self._removeMCStat) or len(mynuisances.keys()) != 0 or histo_total!= None:
tgrMC.Draw("2")
# - then the superimposed MC
if len(sigSupList) != 0 and groupFlag==False:
for hist in sigSupList:
hist.Draw("hist same")
# - then the DATA
if tgrData.GetN() != 0:
tgrData.Draw("P0")
tlegend.Draw()
#if 'lumi' in legend.keys() and 'sqrt' not in legend.keys():
#flag_lumi = ROOT.TLatex (minXused + (maxXused-minXused)*3./4., 0 + (maxYused-0)*3.9/4., legend['lumi'])
#flag_lumi.Draw()
#if 'sqrt' in legend.keys() and 'lumi' not in legend.keys():
#flag_sqrt = ROOT.TLatex (minXused + (maxXused-minXused)*3./4., 0 + (maxYused-0)*3.9/4., legend['sqrt'])
#flag_sqrt.Draw()
#if 'sqrt' in legend.keys() and 'lumi' in legend.keys():
#flag_lumi_sqrt = ROOT.TLatex (minXused + (maxXused-minXused)*2.5/4., 0 + (maxYused-0)*3.9/4., "#splitline{CMS preliminary}{#splitline{" + legend['lumi'] + "}{" + legend['sqrt'] + "} }")
#flag_lumi_sqrt.Draw()
if self._extraLegend is not None:
legExtra = ROOT.TLatex()
legExtra.SetTextSize(0.045)
legExtra.SetTextAngle(0)
legExtra.SetTextAlign(22)
legExtra.SetTextFont(62)
legExtra.DrawLatexNDC(0.85,0.8,self._extraLegend)
CMS_lumi.CMS_lumi(tcanvasRatio, iPeriod, iPos)
# draw back all the axes
#frameDistro.Draw("AXIS")
pad1.RedrawAxis()
tcanvasRatio.cd()
canvasPad2Name = 'pad2_' + cutName + "_" + variableName
pad2 = ROOT.TPad(canvasPad2Name,canvasPad2Name,0,0,1,1-0.72)
pad2.SetTopMargin(0.06)
pad2.SetBottomMargin(0.392)
pad2.Draw()
#pad2.cd().SetGrid()
pad2.cd()
#print(" pad1 = ", pad1)
#print(" pad2 = ", pad2, " minXused = ", minXused, " maxXused = ", maxXused)
canvasFrameRatioName = 'frame_ratio_' + cutName + "_" + variableName
#print(" canvasFrameRatioName = ", canvasFrameRatioName)
frameRatio = pad2.DrawFrame(minXused, 0.0, maxXused, 2.0, canvasFrameRatioName)
#print(" pad2 = ", pad2)
# style from https://ghm.web.cern.ch/ghm/plots/MacroExample/myMacro.py
xAxisDistro = frameRatio.GetXaxis()
xAxisDistro.SetNdivisions(6,5,0)
if 'xaxis' in variable.keys() :
frameRatio.GetXaxis().SetTitle(variable['xaxis'])
else :
frameRatio.GetXaxis().SetTitle(variableName)
frameRatio.GetYaxis().SetTitle("Data/Expected")
#frameRatio.GetYaxis().SetTitle("Data/MC")
#frameRatio.GetYaxis().SetRangeUser( 0.0, 2.0 )
#frameRatio.GetYaxis().SetRangeUser( 0.8, 1.2 )
# changed to 0.5 1.5!
frameRatio.GetYaxis().SetRangeUser( 0.5, 1.5 )
# frameRatio.GetYaxis().SetRangeUser( 0.5, 1.5 )
self.Pad2TAxis(frameRatio)
# if there is "histo_total" there is no need of explicit nuisances
if (not self._removeMCStat) or len(mynuisances.keys()) != 0 or histo_total!= None:
tgrMCOverMC.Draw("2")
tgrDataOverMC.Draw("P0")
if self._postFit == 'p' or self._postFit == 's' or self._postFit == 'b':
#---- Ratio Legend
tlegendRatio = ROOT.TLegend(0.20, 0.40, 0.60, 0.55)
tlegendRatio.SetFillColor(0)
tlegendRatio.SetTextFont(42)
##tlegendRatio.SetTextSize(0.035)
tlegendRatio.SetLineColor(0)
tlegendRatio.SetShadowColor(0)
if self._postFit == 'p':
tlegendRatio.AddEntry(tgrDataOverMC, "post-fit", "PL")
tlegendRatio.AddEntry(tgrDataOverPF, "pre-fit", "PL")
if self._postFit == 's' or self._postFit == 'b':
tlegendRatio.AddEntry(tgrDataOverMC, "pre-fit", "PL")
tlegendRatio.AddEntry(tgrDataOverPF, "post-fit", "PL")
for sampleName, sample in self._samples.items():
##if sampleName.find('total') == 1:
## or sampleName == 'total_background_prefit' or sampleName == 'total_background_postfit_s' or sampleName == 'total_background_postfit_b':
if 'total' in sampleName:
tgrDataOverPF.SetMarkerColor(plot[sampleName]['color'])
tgrDataOverPF.SetLineColor(plot[sampleName]['color'])
# tgrDataOverPF.SetMarkerColor(2)
# tgrDataOverPF.SetLineColor(2)
tgrDataOverPF.Draw("PE,same")
tlegendRatio.Draw("same")
for samplesToRatioGrName, samplesGrToRatio in tgrRatioList.items() :
samplesGrToRatio.Draw("P")
oneLine2 = ROOT.TLine(frameRatio.GetXaxis().GetXmin(), 1, frameRatio.GetXaxis().GetXmax(), 1);
oneLine2.SetLineStyle(3)
oneLine2.SetLineWidth(3)
oneLine2.Draw("same")
# draw back all the axes
#frameRatio.Draw("AXIS")
pad2.RedrawAxis()
pad2.SetGrid()
if 'cratio' in self._plotsToWrite:
if self._plotLinear:
self._saveCanvas(tcanvasRatio, self._outputDirPlots + "/" + canvasRatioNameTemplate + self._FigNamePF)
if self._plotLog:
# log Y axis
#frameDistro.GetYaxis().SetRangeUser( max(self._minLogCratio, maxYused/1000), self._maxLogCratio * maxYused )
#frameDistro.GetYaxis().SetRangeUser( min(self._minLogCratio, maxYused/1000), self._maxLogCratio * maxYused )
if maxYused>0:
frameDistro.GetYaxis().SetRangeUser( min(self._minLogCratio, maxYused/1000), math.pow(10, math.log10(maxYused) + (math.log10(maxYused)-math.log10(self._minLogCratio)) * (self._maxLinearScale-1)) )
else:
frameDistro.GetYaxis().SetRangeUser( min(self._minLogCratio, maxYused/1000), self._maxLogCratio * maxYused )
pad1.SetLogy(True)
self._saveCanvas(tcanvasRatio, self._outputDirPlots + "/log_" + canvasRatioNameTemplate + self._FigNamePF, imageOnly=self._plotLinear)
pad1.SetLogy(False)
if 'root' in self._fileFormats:
text_file_html.write(canvasRatioNameTemplate + ".root;\n")
# ~~~~~~~~~~~~~~~~~~~~
# plot with difference plot
print("- draw with difference")
if self._showRelativeRatio :
canvasDifferenceNameTemplate = 'cdifference_relative_' + cutName + "_" + variableName
else :
canvasDifferenceNameTemplate = 'cdifference_' + cutName + "_" + variableName
tcanvasDifference.cd()
canvasPad1differenceName = 'pad1difference_' + cutName + "_" + variableName
pad1difference = ROOT.TPad(canvasPad1differenceName,canvasPad1differenceName, 0, 1-0.72, 1, 1)
pad1difference.SetTopMargin(0.098)
pad1difference.SetBottomMargin(0.000)
pad1difference.Draw()
#pad1difference.cd().SetGrid()
pad1difference.cd()
#print(" pad1difference = ", pad1difference)
canvasFrameDistroName = 'frame_distro_' + cutName + "_" + variableName
frameDistro = pad1difference.DrawFrame(minXused, 0.0, maxXused, 1.0, canvasFrameDistroName)
#print(" pad1difference = ", pad1difference)
# style from https://ghm.web.cern.ch/ghm/plots/MacroExample/myMacro.py
xAxisDistro = frameDistro.GetXaxis()
xAxisDistro.SetNdivisions(6,5,0)
# setup axis names
# New proposal (following https://twiki.cern.ch/twiki/bin/viewauth/CMS/Internal/PubGuidelines)
# Set xaxis label if needed. Format should be variable name, followed by (units) or [units] if needed
if 'xaxis' in variable.keys() :
frameDistro.GetXaxis().SetTitle(variable['xaxis'])
# If yaxis is set, we want to override normal conventions
if 'yaxis' in variable.keys():
frameDistro.GetYaxis().SetTitle(variable['yaxis'])
else:
# Grab unit from x axis title -- capture part in () or [] brackets
xaxistitle = frameDistro.GetXaxis().GetTitle()
unitpattern = '(?:\[|\()(\w+)(?:\]|\))'
unitsearch = re.search(unitpattern,xaxistitle)
unit = 'unit' if unitsearch is None else unitsearch.group(1)
# If dividing by bin width, yaxis should be "<Events / [unit]>"
if variable["divideByBinWidth"] == 1:
frameDistro.GetYaxis().SetTitle("< Events / %s >"%unit)
#frameDistro.GetYaxis().SetTitle("dN/d"+variable['xaxis'])
else:
# If using fixed bin width, yaxis should be "Events / bin size [unit]"
if len(variable['range']) == 3:
binsize = float(variable['range'][2] - variable['range'][1])/float(variable['range'][0])
frameDistro.GetYaxis().SetTitle("Events / %g %s"%(binsize,unit))
# Otherwise, yaxis should be "Events / bin"
else:
frameDistro.GetYaxis().SetTitle("Events / bin")
#frameDistro.GetYaxis().SetRangeUser( 0, maxYused )
frameDistro.GetYaxis().SetRangeUser( min(0.001, minYused), maxYused )
if len(groupPlot.keys()) == 0:
if thsBackground.GetNhists() != 0:
thsBackground.Draw("hist same")
if thsSignal.GetNhists() != 0:
#for ihisto in range(thsSignal.GetNhists()) :
#((thsSignal.GetHists().At(ihisto))).SetFillStyle(0)
#((thsSignal.GetHists().At(ihisto))).Draw("hist same")
thsSignal.Draw("hist same noclear")
else :
if thsBackground_grouped.GetNhists() != 0:
thsBackground_grouped.Draw("hist same")
if thsSignal_grouped.GetNhists() != 0:
thsSignal_grouped.Draw("hist same noclear")
if len(sigSupList_grouped) != 0:
for histo in sigSupList_grouped:
histo.Draw("hist same")
# if there is "histo_total" there is no need of explicit nuisances
if (not self._removeMCStat) or len(mynuisances.keys()) != 0 or histo_total!= None:
tgrMC.Draw("2")
# - then the superimposed MC
if len(sigSupList) != 0 and groupFlag==False:
for hist in sigSupList:
hist.Draw("hist same")
# - then the DATA
if tgrData.GetN() != 0:
tgrData.Draw("P0")
tlegend.Draw()
#if 'lumi' in legend.keys() and 'sqrt' not in legend.keys():
#flag_lumi = ROOT.TLatex (minXused + (maxXused-minXused)*3./4., 0 + (maxYused-0)*3.9/4., legend['lumi'])
#flag_lumi.Draw()
#if 'sqrt' in legend.keys() and 'lumi' not in legend.keys():
#flag_sqrt = ROOT.TLatex (minXused + (maxXused-minXused)*3./4., 0 + (maxYused-0)*3.9/4., legend['sqrt'])
#flag_sqrt.Draw()
#if 'sqrt' in legend.keys() and 'lumi' in legend.keys():
#flag_lumi_sqrt = ROOT.TLatex (minXused + (maxXused-minXused)*2.5/4., 0 + (maxYused-0)*3.9/4., "#splitline{CMS preliminary}{#splitline{" + legend['lumi'] + "}{" + legend['sqrt'] + "} }")
#flag_lumi_sqrt.Draw()
CMS_lumi.CMS_lumi(tcanvasDifference, iPeriod, iPos)
# draw back all the axes
#frameDistro.Draw("AXIS")
pad1difference.RedrawAxis()
tcanvasDifference.cd()
canvasPad2differenceName = 'pad2difference_' + cutName + "_" + variableName
pad2difference = ROOT.TPad(canvasPad2differenceName,canvasPad2differenceName,0,0,1,1-0.72)
pad2difference.SetTopMargin(0.000)
pad2difference.SetBottomMargin(0.392)
pad2difference.Draw()
#pad2difference.cd().SetGrid()
pad2difference.cd()
canvasFrameDifferenceName = 'frame_difference_' + cutName + "_" + variableName
if self._showRelativeRatio :
frameDifference = pad2difference.DrawFrame(minXused, -1.0 , maxXused, 1.0 , canvasFrameDifferenceName)
else :
frameDifference = pad2difference.DrawFrame(minXused, int ( ROOT.TMath.MinElement(tgrDataMinusMC.GetN(),tgrDataMinusMC.GetY()) - 2 ), maxXused, int ( ROOT.TMath.MaxElement(tgrDataMinusMC.GetN(),tgrDataMinusMC.GetY()) + 2 ), canvasFrameDifferenceName)
# style from https://ghm.web.cern.ch/ghm/plots/MacroExample/myMacro.py
xAxisDistro = frameDifference.GetXaxis()
xAxisDistro.SetNdivisions(6,5,0)
if 'xaxis' in variable.keys() :
frameDifference.GetXaxis().SetTitle(variable['xaxis'])
else :
frameDifference.GetXaxis().SetTitle(variableName)
if self._showRelativeRatio :
frameDifference.GetYaxis().SetTitle("#frac{Data - Expected}{Expected}")
frameDifference.GetYaxis().SetRangeUser( -1.0, 1.0 )
else :
frameDifference.GetYaxis().SetTitle("Data - Expected")
frameDifference.GetYaxis().SetRangeUser( int (ROOT.TMath.MinElement(tgrDataMinusMC.GetN(),tgrDataMinusMC.GetY()) - 2 ), int (ROOT.TMath.MaxElement(tgrDataMinusMC.GetN(),tgrDataMinusMC.GetY()) + 2 ) )
self.Pad2TAxis(frameDifference)
# if there is "histo_total" there is no need of explicit nuisances
if (not self._removeMCStat) or len(mynuisances.keys()) != 0 or histo_total!= None:
tgrMCMinusMC.SetLineColor(12)
tgrMCMinusMC.SetFillColor(12)
tgrMCMinusMC.SetLineWidth(2)
tgrMCMinusMC.SetFillStyle(3004)
tgrMCMinusMC.Draw("2")
tgrDataMinusMC.Draw("P0")
#print(" tgrDataMinusMC.GetMinimum()/Max = " , tgrDataMinusMC.GetMinimum(), " / " , tgrDataMinusMC.GetMaximum())
for samplesToDifferenceGrName, samplesGrToDifference in tgrDifferenceList.items() :
samplesGrToDifference.Draw("P")
oneLine2 = ROOT.TLine(frameDifference.GetXaxis().GetXmin(), 0, frameDifference.GetXaxis().GetXmax(), 0);
oneLine2.SetLineStyle(3)
oneLine2.SetLineWidth(3)
oneLine2.Draw("same")
# draw back all the axes
#frameDifference.Draw("AXIS")
pad2difference.RedrawAxis()
pad2difference.SetGrid()
if 'cdifference' in self._plotsToWrite:
if self._plotLinear:
self._saveCanvas(tcanvasDifference, self._outputDirPlots + "/" + canvasDifferenceNameTemplate + self._FigNamePF)
if self._plotLog:
# log Y axis
#frameDistro.GetYaxis().SetRangeUser( max(self._minLogCdifference, maxYused/1000), self._maxLogCdifference * maxYused )
frameDistro.GetYaxis().SetRangeUser( min(self._minLogC, maxYused/1000), self._maxLogCdifference * maxYused )
pad1difference.SetLogy(True)
self._saveCanvas(tcanvasDifference, self._outputDirPlots + "/log_" + canvasDifferenceNameTemplate + self._FigNamePF, imageOnly=self._plotLinear)
pad1difference.SetLogy(False)
if 'root' in self._fileFormats:
text_file_html.write(canvasDifferenceNameTemplate + ".root;\n")
# ~~~~~~~~~~~~~~~~~~~~
# plot with difference plot Fancy : data - bkg subtracted
#
# only IF we have the difference and not the ratio --> _showRelativeRatio
#
if self._plotFancy and not self._showRelativeRatio:
print("- draw with difference Fancy")
#blind data
if 'blind' in variable:
blind_range = variable['blind'][cutName]
b0 = histos[sampleName].FindBin(blind_range[0])
b1 = histos[sampleName].FindBin(blind_range[1])
for ip in range(tgrDataMinusMC.GetN(), tgrDataMinusMC.GetN()-(b1-b0+1),-1):
tgrDataMinusMC.RemovePoint(ip)
canvasDifferenceNameTemplate = 'cdifference_' + cutName + "_" + variableName + "_Fancy"
tcanvasDifference_Fancy.cd()
canvasPad1differenceName = 'pad1difference_' + cutName + "_" + variableName + "_Fancy"
pad1difference = ROOT.TPad(canvasPad1differenceName,canvasPad1differenceName, 0., 0., 1., 1.)
pad1difference.Draw()
pad1difference.cd()
canvasFrameDistroName = 'frame_fancy_' + cutName + "_" + variableName
frameDistro_Fancy = pad1difference.DrawFrame(minXused,
int (ROOT.TMath.MinElement(tgrDataMinusMC.GetN(),tgrDataMinusMC.GetY()) - int ( ROOT.TMath.MaxElement(tgrDataMinusMC.GetN(),tgrDataMinusMC.GetEYlow ()) ) - 20 ),
maxXused,
int (ROOT.TMath.MaxElement(tgrDataMinusMC.GetN(),tgrDataMinusMC.GetY()) + int ( ROOT.TMath.MaxElement(tgrDataMinusMC.GetN(),tgrDataMinusMC.GetEYhigh()) ) + 20 ),
canvasFrameDistroName)
# style from https://ghm.web.cern.ch/ghm/plots/MacroExample/myMacro.py
xAxisDistro = frameDistro_Fancy.GetXaxis()
xAxisDistro.SetNdivisions(6,5,0)
frameDistro_Fancy.GetYaxis().SetMaxDigits(2)
xaxis = frameDistro_Fancy.GetXaxis()
xaxis.SetLabelFont ( 42)
xaxis.SetLabelOffset( 0.015)
xaxis.SetLabelSize ( 0.035)
xaxis.SetNdivisions ( 505)
xaxis.SetTitleFont ( 42)
xaxis.SetTitleOffset( 1.35)
xaxis.SetTitleSize ( 0.035)
yaxis = frameDistro_Fancy.GetYaxis()
yaxis.SetLabelFont ( 42)
yaxis.SetLabelOffset( 0.01)
yaxis.SetLabelSize ( 0.035)
yaxis.SetNdivisions ( 505)
yaxis.SetTitleFont ( 42)
yaxis.SetTitleOffset( 1.55)
yaxis.SetTitleSize ( 0.045)
if 'xaxis' in variable.keys() :
frameDistro_Fancy.GetXaxis().SetTitle(variable['xaxis'])
if variable["divideByBinWidth"] == 1:
if 'yaxis' in variable.keys() :
frameDistro_Fancy.GetYaxis().SetTitle("Data - Bkg " + variable['yaxis'])
else:
if "GeV" in variable['xaxis']:
### FIXME: it's maybe better to add a "yaxis" field in the variable to let the user choose the y axis name
frameDistro_Fancy.GetYaxis().SetTitle("Data - Bkg dN/d"+variable['xaxis'].replace("GeV","GeV^{-1}"))
else:
frameDistro_Fancy.GetYaxis().SetTitle("Data - Bkg dN/d"+variable['xaxis'])
else:
if 'yaxis' in variable.keys() :
frameDistro_Fancy.GetYaxis().SetTitle("Data - Bkg " + variable['yaxis'])
else :
frameDistro_Fancy.GetYaxis().SetTitle("Data - Bkg Events")
else :
frameDistro_Fancy.GetXaxis().SetTitle(variableName)
if variable["divideByBinWidth"] == 1:
frameDistro_Fancy.GetYaxis().SetTitle("Data - Bkg dN/d"+variableName)
else:
if 'yaxis' in variable.keys() :
frameDistro_Fancy.GetYaxis().SetTitle("Data - Expected " + variable['yaxis'])
else :
frameDistro_Fancy.GetYaxis().SetTitle("Data - Expected Events")
frameDistro_Fancy.GetYaxis().SetRangeUser( int (ROOT.TMath.MinElement(tgrDataMinusMC.GetN(),tgrDataMinusMC.GetY()) - int ( ROOT.TMath.MaxElement(tgrDataMinusMC.GetN(),tgrDataMinusMC.GetEYlow ()) ) - 20 ),
int (ROOT.TMath.MaxElement(tgrDataMinusMC.GetN(),tgrDataMinusMC.GetY()) + int ( ROOT.TMath.MaxElement(tgrDataMinusMC.GetN(),tgrDataMinusMC.GetEYhigh()) ) + 20 ) )
# if there is "histo_total" there is no need of explicit nuisances
if (not self._removeMCStat) or len(mynuisances.keys()) != 0 or histo_total!= None:
tgrMCMinusMC.SetLineColor(12)
tgrMCMinusMC.SetFillColor(12)
tgrMCMinusMC.SetLineWidth(2)
tgrMCMinusMC.SetFillStyle(3004)
tgrMCMinusMC.Draw("2")
#---- the Legend
special_tlegend = ROOT.TLegend(0.40, 0.75, 0.90, 0.90)
special_tlegend.SetFillColor(0)
special_tlegend.SetTextFont(42)
special_tlegend.SetTextSize(0.035)
special_tlegend.SetLineColor(0)
special_tlegend.SetShadowColor(0)
special_tlegend.AddEntry( tgrDataMinusMC , 'Data - Bkg', "PL")
special_tlegend.AddEntry( tgrMCMinusMC, "Tot uncertainty", "F")
if self._showDataMinusBkgOnly :
tgrMCSigMinusMCBkg.SetLineWidth(3)
tgrMCSigMinusMCBkg.SetLineColor(2) # red
tgrMCSigMinusMCBkg.SetMarkerColor(2) # red
tgrMCSigMinusMCBkg.SetMarkerSize(0)
tgrMCSigMinusMCBkg.Draw("P")
special_tlegend.AddEntry( tgrMCSigMinusMCBkg , 'Signal', "PL")
# draw the data - MC
# if blind remove the last points
# maxip = 0
# for ip in range(0, tgrDataMinusMC.GetN()):
# x = tgrDataMinusMC.GetPointX(ip)
# if x > 0.6:
# maxip = ip
tgrDataMinusMC.Draw("P")
oneLine2 = ROOT.TLine(frameDistro_Fancy.GetXaxis().GetXmin(), 0, frameDistro_Fancy.GetXaxis().GetXmax(), 0);
oneLine2.SetLineStyle(3)
oneLine2.SetLineWidth(3)
oneLine2.Draw("same")
special_tlegend.Draw()
CMS_lumi.CMS_lumi(tcanvasDifference_Fancy, iPeriod, iPos)
# draw back all the a xes
pad1difference.RedrawAxis()
pad1difference.SetGrid()
if 'cdifference' in self._plotsToWrite:
self._saveCanvas(tcanvasDifference_Fancy, self._outputDirPlots + "/" + canvasDifferenceNameTemplate + self._FigNamePF)
if 'root' in self._fileFormats:
text_file_html.write(canvasDifferenceNameTemplate + ".root;\n")
#
# draw weighted plot
#
if 'doWeight' in variable.keys() :
if variable['doWeight'] == 1 :
if 'binX' in variable.keys() and 'binY' in variable.keys() :
nbinX = variable['binX']
nbinY = variable['binY']
#
# Add weight 1D
# - sample the 1D histogram in 'nbinX' slices long 'nbinY'.
# - calculate the integral of S and B for each of them
# - add a weight on them S/B, for signal, background and data
# - add the weighted histogram to the final histogram, made of 'nbinY' bins
#
# trick to exchange X <-> Y
if self._invertXY :
temp_nbinX = nbinX
nbinX = nbinY
nbinY = temp_nbinX
#
# check if I have to remove the signal on the ackground stack here
# --> ok, it works and it is correct
#
if thsBackground.GetNhists() != 0 and thsSignal.GetNhists() != 0 :
weight_X_thsData = ROOT.THStack ("weight_X_thsData", "weight_X_thsData")
weight_X_thsSignal = ROOT.THStack ("weight_X_thsSignal", "weight_X_thsSignal")
weight_X_thsBackground = ROOT.THStack ("weight_X_thsBackground","weight_X_thsBackground")
#
# the final histogram should be scaled such that the integral
# is the expected signal + background yield
# --> or only background ?
#
if len(groupPlot.keys()) == 0:
totalBkg = thsBackground.GetStack().Last().Integral()
totalSig = thsSignal.GetStack().Last().Integral()
else :
totalBkg = thsBackground_grouped.GetStack().Last().Integral()
totalSig = thsSignal_grouped.GetStack().Last().Integral()
totalBkgSig = totalBkg + totalSig
totalWeightedIntegralBkg = 0.
totalWeightedIntegralSig = 0.
weight_X_list_Data = []
weight_X_list_Signal = []
weight_X_list_Background = []
weight_X_list_weights = []
for sliceX in range(nbinX) :
integral_bkg = 0.
#for ibin in range( thsBackground.GetStack().Last().GetNbinsX() )
for ibin in range( nbinY ) :
if len(groupPlot.keys()) == 0:
if self._invertXY :
integral_bkg += thsBackground.GetStack().Last().GetBinContent(ibin*nbinX +1 + sliceX)
else :
integral_bkg += thsBackground.GetStack().Last().GetBinContent(ibin+1 + sliceX * nbinY)
else :
if self._invertXY :
integral_bkg += thsBackground_grouped.GetStack().Last().GetBinContent(ibin*nbinX +1 + sliceX)
else :
integral_bkg += thsBackground_grouped.GetStack().Last().GetBinContent(ibin+1 + sliceX * nbinY)
integral_sig = 0.
if len(groupPlot.keys()) == 0:
for ibin in range( thsSignal.GetStack().Last().GetNbinsX() ) :
if self._invertXY :
integral_sig += thsSignal.GetStack().Last().GetBinContent(ibin*nbinX +1 + sliceX)
else :
integral_sig += thsSignal.GetStack().Last().GetBinContent(ibin+1 + sliceX * nbinY)
else :
for ibin in range( thsSignal_grouped.GetStack().Last().GetNbinsX() ) :
if self._invertXY :
integral_sig += thsSignal_grouped.GetStack().Last().GetBinContent(ibin*nbinX +1 + sliceX)
else :
integral_sig += thsSignal_grouped.GetStack().Last().GetBinContent(ibin+1 + sliceX * nbinY)
# this is because the signal was added into the background stack before
integral_bkg = integral_bkg - integral_sig
weight = 1
if integral_bkg != 0 :
weight = integral_sig / integral_bkg
else :
weight = 1
#
# remove weight: use just 1 for each line,
# meaning we are just adding the bins together
#
if self._removeWeight == True :
weight = 1
weight_X_list_weights.append(weight)
if len(groupPlot.keys()) == 0:
for ihisto in range(thsBackground.GetNhists()):
hentry = thsBackground.GetHists().At(ihisto)
histo = ROOT.TH1F('h_weigth_X_' + cutName + '_' + variableName + '_' + hentry.GetName() + '_slice_'+ str(sliceX), '-' , nbinY, 0, nbinY)
histo = self.FixBins (histo, tgrData_vx, tgrData_evx)
histo.SetFillColor( hentry.GetFillColor())
histo.SetFillStyle( hentry.GetFillStyle())
histo.SetLineColor( hentry.GetLineColor())
histo.SetLineWidth( hentry.GetLineWidth())
for ibin in range( nbinY ) :
if self._invertXY :
histo.SetBinContent(ibin+1, weight * ( hentry.GetBinContent(ibin*nbinX +1 + sliceX) ) )
else :
histo.SetBinContent(ibin+1, weight * ( hentry.GetBinContent(ibin+1 + sliceX * nbinY) ) )
if sliceX != 0:
weight_X_list_Background[ihisto].Add(histo)
else :
weight_X_list_Background.append(histo)
# the minus signal is because the signal was added into the background stack before
if ihisto < (thsBackground.GetNhists() - thsSignal.GetNhists()) :
totalWeightedIntegralBkg += histo.Integral()
for ihisto in range(thsSignal.GetNhists()):
hentry = thsSignal.GetHists().At(ihisto)
histo = ROOT.TH1F('h_weigth_X_' + cutName + '_' + variableName + '_' + (()).GetName() + '_slice_'+ str(sliceX), "-", nbinY, 0, nbinY)
histo = self.FixBins (histo, tgrData_vx, tgrData_evx)
histo.SetFillColor( hentry.GetFillColor())
histo.SetFillStyle( hentry.GetFillStyle())
histo.SetLineColor( hentry.GetLineColor())
histo.SetLineWidth( hentry.GetLineWidth())
for ibin in range( nbinY ) :
if self._invertXY :
histo.SetBinContent(ibin+1, weight * ( hentry.GetBinContent(ibin*nbinX +1 + sliceX) ) )
else :
histo.SetBinContent(ibin+1, weight * ( hentry.GetBinContent(ibin+1 + sliceX * nbinY) ) )
if sliceX != 0:
weight_X_list_Signal[ihisto].Add(histo)
else :
weight_X_list_Signal.append(histo)
totalWeightedIntegralSig += histo.Integral()
for ihisto in range(thsData.GetNhists()) :
hentry = thsData.GetHists().At(ihisto)
histo = ROOT.TH1F('h_weigth_X_' + cutName + '_' + variableName + '_' + hentry.GetName() + '_slice_'+ str(sliceX), "-", nbinY, 0, nbinY)
histo = self.FixBins (histo, tgrData_vx, tgrData_evx)
for ibin in range( nbinY ) :
if self._invertXY :
histo.SetBinContent(ibin+1, weight * ( hentry.GetBinContent(ibin*nbinX +1 + sliceX ) ) )
else :
histo.SetBinContent(ibin+1, weight * ( hentry.GetBinContent(ibin+1 + sliceX * nbinY) ) )
if sliceX != 0:
weight_X_list_Data[ihisto].Add(histo)
else :
weight_X_list_Data.append(histo)
#weight_X_list_Data.append(histo) ## aaaargh!
else :
for ihisto in range(thsBackground_grouped.GetNhists()):
hentry = thsBackground_grouped.GetHists().At(ihisto)
histo = ROOT.TH1F('h_weigth_X_' + cutName + '_' + variableName + '_' + hentry.GetName() + '_slice_'+ str(sliceX), '-' , nbinY, 0, nbinY)
histo = self.FixBins (histo, tgrData_vx, tgrData_evx)
histo.SetFillColor( hentry.GetFillColor())
histo.SetFillStyle( hentry.GetFillStyle())
histo.SetLineColor( hentry.GetLineColor())
histo.SetLineWidth( hentry.GetLineWidth())
for ibin in range( nbinY ) :
if self._invertXY :
histo.SetBinContent(ibin+1, weight * ( hentry.GetBinContent(ibin*nbinX +1 + sliceX) ) )
else :
histo.SetBinContent(ibin+1, weight * ( hentry.GetBinContent(ibin+1 + sliceX * nbinY) ) )
if sliceX != 0:
weight_X_list_Background[ihisto].Add(histo)
else :
weight_X_list_Background.append(histo)
# the minus signal is because the signal was added into the background stack before
if ihisto < (thsBackground_grouped.GetNhists() - thsSignal_grouped.GetNhists()) :
totalWeightedIntegralBkg += histo.Integral()
for ihisto in range(thsSignal_grouped.GetNhists()):
hentry = thsSignal_grouped.GetHists().At(ihisto)
histo = ROOT.TH1F('h_weigth_X_' + cutName + '_' + variableName + '_' + hentry.GetName() + '_slice_'+ str(sliceX), "-", nbinY, 0, nbinY)
histo = self.FixBins (histo, tgrData_vx, tgrData_evx)
histo.SetFillColor( hentry.GetFillColor())
histo.SetFillStyle( hentry.GetFillStyle())
histo.SetLineColor( hentry.GetLineColor())
histo.SetLineWidth( hentry.GetLineWidth())
for ibin in range( nbinY ) :
if self._invertXY :
histo.SetBinContent(ibin+1, weight * ( hentry.GetBinContent(ibin*nbinX +1 + sliceX) ) )
else :
histo.SetBinContent(ibin+1, weight * ( hentry.GetBinContent(ibin+1 + sliceX * nbinY) ) )
if sliceX != 0:
weight_X_list_Signal[ihisto].Add(histo)
else :
weight_X_list_Signal.append(histo)
totalWeightedIntegralSig += histo.Integral()
for ihisto in range(thsData.GetNhists()):
hentry = thsData.GetHists().At(ihisto)
histo = ROOT.TH1F('h_weigth_X_' + cutName + '_' + variableName + '_' + hentry.GetName() + '_slice_'+ str(sliceX), "-", nbinY, 0, nbinY)
histo = self.FixBins (histo, tgrData_vx, tgrData_evx)
for ibin in range( nbinY ) :
if self._invertXY :
histo.SetBinContent(ibin+1, weight * ( hentry.GetBinContent(ibin*nbinX +1 + sliceX) ) )
else :
histo.SetBinContent(ibin+1, weight * ( hentry.GetBinContent(ibin+1 + sliceX * nbinY) ) )
if sliceX != 0:
weight_X_list_Data[ihisto].Add(histo)
else :
weight_X_list_Data.append(histo)
#weight_X_list_Data.append(histo) ## aaaargh!
#
# gloabal scale factor so that the total number of events is such that
# the expected signal events is unchanged
#
#global_normalization = totalBkg / totalWeightedIntegralBkg
global_normalization = totalSig / totalWeightedIntegralSig if totalWeightedIntegralSig != 0 else 1.0
for histo in weight_X_list_Data:
histo.Scale(global_normalization)
for histo in weight_X_list_Background:
histo.Scale(global_normalization)
for histo in weight_X_list_Signal:
histo.Scale(global_normalization)
for histo in weight_X_list_Data:
weight_X_thsData.Add(histo)
for histo in weight_X_list_Background:
weight_X_thsBackground.Add(histo)
for histo in weight_X_list_Signal:
weight_X_thsSignal.Add(histo)
#print(" weight_X_list_weights = ", weight_X_list_weights)
# create the weighted data distribution
weight_X_tgrData = ROOT.TGraphAsymmErrors()
for sliceX in range( nbinX ) :
for ibin in range( nbinY ) :
x = tgrData_vx[ibin]
#print(" sliceX, ibin = ", sliceX, " , ", ibin, " -> ", tgrData_vx[ibin])
number_of_bin = ibin + sliceX * nbinY
if self._invertXY :
number_of_bin = ibin*nbinX + sliceX
y = weight_X_list_weights[sliceX] * global_normalization * tgrData_vy[number_of_bin]
exlow = tgrData_evx[number_of_bin]
exhigh = tgrData_evx[number_of_bin]
eylow = weight_X_list_weights[sliceX] * global_normalization * tgrData_evy_do[number_of_bin]
eyhigh = weight_X_list_weights[sliceX] * global_normalization * tgrData_evy_up[number_of_bin]
if sliceX != 0:
y += weight_X_tgrData.GetY()[ibin]
eylow = self.SumQ ( eylow, weight_X_tgrData.GetErrorYlow(ibin) )
eyhigh = self.SumQ ( eyhigh, weight_X_tgrData.GetErrorYhigh(ibin) )
#print(" eylow = ", eylow, " eyhigh = ", eyhigh, " y = ", y, " x = ", x )
weight_X_tgrData.SetPoint (ibin, x, y)
weight_X_tgrData.SetPointError (ibin, exlow, exhigh, eylow, eyhigh)
#if sliceX == ( nbinX - 1) :
#print(" ibin,x,y = ", ibin, ", ", x, ", ", y)
# create the weighted data distribution
weight_X_tgrMC = ROOT.TGraphAsymmErrors()
for sliceX in range(nbinX) :
for ibin in range( nbinY ) :
x = tgrData_vx[ibin]
number_of_bin = ibin + sliceX * nbinY
if self._invertXY :
number_of_bin = ibin*nbinX + sliceX
y = weight_X_list_weights[sliceX] * global_normalization * tgrMC_vy[number_of_bin]
exlow = tgrData_evx[number_of_bin]
exhigh = tgrData_evx[number_of_bin]
eylow = 0
eyhigh = 0
if len(nuisances_err_do) != 0:
if histo_total:
eylow = weight_X_list_weights[sliceX] * global_normalization * histo_total.GetBinError(number_of_bin + 1)
eyhigh = weight_X_list_weights[sliceX] * global_normalization * histo_total.GetBinError(number_of_bin + 1)
else :
eylow = weight_X_list_weights[sliceX] * global_normalization * nuisances_err_do[number_of_bin]
eyhigh = weight_X_list_weights[sliceX] * global_normalization * nuisances_err_up[number_of_bin]
if sliceX != 0 :
y += weight_X_tgrMC.GetY()[ibin]
eylow = self.SumQ ( eylow, weight_X_tgrMC.GetErrorYlow(ibin) )
eyhigh = self.SumQ ( eyhigh, weight_X_tgrMC.GetErrorYhigh(ibin) )
#print(" eylow = ", eylow, " eyhigh = ", eyhigh, " y = ", y, " x = ", x )
weight_X_tgrMC.SetPoint (ibin, x, y)
weight_X_tgrMC.SetPointError (ibin, exlow, exhigh, eylow, eyhigh)
#
# create the weighted data over MC distribution
#
weight_X_tgrDataOverMC = weight_X_tgrData.Clone("tgrDataOverMCweighted")
last = weight_X_thsBackground.GetStack().Last()
for ibin in range( nbinY ) :
x = weight_X_tgrDataOverMC.GetX()[ibin]
y = self.Ratio(weight_X_tgrData.GetY()[ibin] , last.GetBinContent(ibin+1) )
number_of_bin = ibin + sliceX * nbinY
if self._invertXY :
number_of_bin = ibin*nbinX + sliceX
exlow = tgrData_evx[number_of_bin]
exhigh = tgrData_evx[number_of_bin]
eylow = self.Ratio(weight_X_tgrData.GetErrorYlow(ibin), last.GetBinContent(ibin+1) )
eyhigh = self.Ratio(weight_X_tgrData.GetErrorYhigh(ibin), last.GetBinContent(ibin+1) )
weight_X_tgrDataOverMC.SetPoint (ibin, x, y)
weight_X_tgrDataOverMC.SetPointError (ibin, exlow, exhigh, eylow, eyhigh)
#print(" Ratio:: ibin,x,y = ", ibin, ", ", x, ", ", y, ", ", eylow, ", ", eyhigh, " <-- ", weight_X_tgrData.GetY()[ibin], " / ", last.GetBinContent(ibin+1) )
#
# create the weighted data minus MC distribution
#
weight_X_tgrDataMinusMC = weight_X_tgrData.Clone("tgrDataMinusMCweighted")
last = weight_X_thsBackground.GetStack().Last()
for ibin in range( nbinY ) :
x = weight_X_tgrDataMinusMC.GetX()[ibin]
if self._showRelativeRatio :
y = self.Ratio ( self.Difference(weight_X_tgrData.GetY()[ibin] , last.GetBinContent(ibin+1) ) , last.GetBinContent(ibin+1) )
else :
# if show only "data - bkg", subtract "data - (bkg+sig) + sig "
if self._showDataMinusBkgOnly :
y = self.Difference(weight_X_tgrData.GetY()[ibin] , last.GetBinContent(ibin+1) ) + weight_X_thsSignal.GetStack().Last().GetBinContent(ibin+1)
else :
y = self.Difference(weight_X_tgrData.GetY()[ibin] , last.GetBinContent(ibin+1) )
number_of_bin = ibin + sliceX * nbinY
if self._invertXY :
number_of_bin = ibin*nbinX + sliceX
exlow = tgrData_evx[number_of_bin]
exhigh = tgrData_evx[number_of_bin]
if self._showRelativeRatio :
eylow = self.Ratio(weight_X_tgrData.GetErrorYlow(ibin), last.GetBinContent(ibin+1) )
eyhigh = self.Ratio(weight_X_tgrData.GetErrorYhigh(ibin), last.GetBinContent(ibin+1) )
else :
eylow = weight_X_tgrData.GetErrorYlow(ibin)
eyhigh = weight_X_tgrData.GetErrorYhigh(ibin)
weight_X_tgrDataMinusMC.SetPoint (ibin, x, y)
weight_X_tgrDataMinusMC.SetPointError (ibin, exlow, exhigh, eylow, eyhigh)
#
# create the weighted MC over MC distribution
#
weight_X_tgrMCOverMC = weight_X_tgrData.Clone("tgrMCOverMCweighted")
last = weight_X_thsBackground.GetStack().Last()
for ibin in range( nbinY ) :
x = weight_X_tgrMCOverMC.GetX()[ibin]
y = 1
number_of_bin = ibin + sliceX * nbinY
if self._invertXY :
number_of_bin = ibin*nbinX + sliceX
exlow = tgrData_evx[number_of_bin]
exhigh = tgrData_evx[number_of_bin]
eylow = self.Ratio(weight_X_tgrMC.GetErrorYlow(ibin), last.GetBinContent(ibin+1) )
eyhigh = self.Ratio(weight_X_tgrMC.GetErrorYhigh(ibin), last.GetBinContent(ibin+1) )
weight_X_tgrMCOverMC.SetPoint (ibin, x, y)
weight_X_tgrMCOverMC.SetPointError (ibin, exlow, exhigh, eylow, eyhigh)
#
# create the weighted MC over MC distribution
#
weight_X_tgrMCMinusMC = weight_X_tgrData.Clone("tgrMCMinusMCweighted")
last = weight_X_thsBackground.GetStack().Last()
for ibin in range( nbinY ) :
x = weight_X_tgrMCMinusMC.GetX()[ibin]
y = 0
number_of_bin = ibin + sliceX * nbinY
if self._invertXY :
number_of_bin = ibin*nbinX + sliceX
exlow = tgrData_evx[number_of_bin]
exhigh = tgrData_evx[number_of_bin]
if self._showRelativeRatio :
eylow = self.Ratio(weight_X_tgrMC.GetErrorYlow(ibin), last.GetBinContent(ibin+1) )
eyhigh = self.Ratio(weight_X_tgrMC.GetErrorYhigh(ibin), last.GetBinContent(ibin+1) )
else :
eylow = weight_X_tgrMC.GetErrorYlow(ibin)
eyhigh = weight_X_tgrMC.GetErrorYhigh(ibin)
#print(" DIFF:: eylow = ", eylow, " eyhigh = ", eyhigh, " y = ", y, " x = ", x )
weight_X_tgrMCMinusMC.SetPoint (ibin, x, y)
weight_X_tgrMCMinusMC.SetPointError (ibin, exlow, exhigh, eylow, eyhigh)
#
# now plot
#
# - recalculate the maxY
# _maxLinearScale --> 1.45 in the past
maxYused = self._maxLinearScale * self.GetMaximumIncludingErrors(last)
# recalculate min-max X due to weighting rolling
minXused = weight_X_tgrMCMinusMC.GetX()[0] - tgrData_evx[0]
maxXused = weight_X_tgrMCMinusMC.GetX()[nbinY-1] + tgrData_evx[nbinY-1]
weight_X_canvasRatioNameTemplate = 'cratio_weight_X_' + cutName + '_' + variableName
weight_X_tcanvasRatio.cd()
canvasPad1Name = 'weight_X_pad1_' + cutName + "_" + variableName
weight_X_pad1 = ROOT.TPad(canvasPad1Name,canvasPad1Name, 0, 1-0.72, 1, 1)
weight_X_pad1.SetTopMargin(0.098)
weight_X_pad1.SetBottomMargin(0.000)
weight_X_pad1.Draw()
weight_X_pad1.cd()
weight_X_canvasFrameDistroName = 'weight_X_frame_distro_' + cutName + "_" + variableName
weight_X_frameDistro = weight_X_pad1.DrawFrame(minXused, 0.0, maxXused, 1.0, weight_X_canvasFrameDistroName)
#weight_X_frameDistro = weight_X_pad1.DrawFrame(minXused, 0.0, maxXused, 1.0, weight_X_canvasFrameDistroName)
#weight_X_frameDistro = weight_X_pad1.DrawFrame(0.0, 0.0, nbinY, 1.0, weight_X_canvasFrameDistroName)
# style from https://ghm.web.cern.ch/ghm/plots/MacroExample/myMacro.py
xAxisDistro = weight_X_frameDistro.GetXaxis()
xAxisDistro.SetNdivisions(6,5,0)
if 'xaxis' in variable.keys() :
weight_X_frameDistro.GetXaxis().SetTitle(variable['xaxis'])
else :
weight_X_frameDistro.GetXaxis().SetTitle(variableName)
weight_X_frameDistro.GetYaxis().SetTitle("S/B weighted Events")
if self._removeWeight == True :
weight_X_frameDistro.GetYaxis().SetTitle("Events")
weight_X_frameDistro.GetYaxis().SetRangeUser( min(0.001, minYused), maxYused )
if weight_X_thsBackground.GetNhists() != 0:
weight_X_thsBackground.Draw("hist same")
if weight_X_thsSignal.GetNhists() != 0:
weight_X_thsSignal.Draw("hist same noclear")
# if there is "histo_total" there is no need of explicit nuisances
if len(mynuisances.keys()) != 0 or histo_total!= None:
weight_X_tgrMC.SetLineColor(12)
weight_X_tgrMC.SetFillColor(12)
weight_X_tgrMC.SetFillStyle(3004)
weight_X_tgrMC.Draw("2")
#weight_X_tgrMC.Draw("P0")
#print(" -------------------------> here ")
# - then the DATA
if weight_X_tgrData.GetN() != 0:
weight_X_tgrData.Draw("P0")
#print(" -------------------------> here data ")
tlegend.Draw()
CMS_lumi.CMS_lumi(weight_X_tcanvasRatio, iPeriod, iPos)
# draw back all the axes
#weight_X_frameDistro.Draw("AXIS")
weight_X_pad1.RedrawAxis()
weight_X_tcanvasRatio.cd()
canvasPad2Name = 'weight_X_weight_X_pad2_' + cutName + "_" + variableName
weight_X_pad2 = ROOT.TPad(canvasPad2Name,canvasPad2Name,0,0,1,1-0.72)
weight_X_pad2.SetTopMargin(0.000)
weight_X_pad2.SetBottomMargin(0.392)
weight_X_pad2.Draw()
#weight_X_pad2.cd().SetGrid()
weight_X_pad2.cd()
weight_X_canvasFrameRatioName = 'weight_X_frame_ratio_' + cutName + "_" + variableName
#weight_X_frameRatio = weight_X_pad2.DrawFrame(minXused, 0.0, nbinY, 2.0, weight_X_canvasFrameRatioName)
weight_X_frameRatio = weight_X_pad2.DrawFrame(minXused, 0.0, maxXused, 2.0, weight_X_canvasFrameRatioName)
#print(" minXused = " , minXused)
#print(" maxXused = " , maxXused)
#print(" nbinY = " , nbinY)
# style from https://ghm.web.cern.ch/ghm/plots/MacroExample/myMacro.py
xAxisDistro = weight_X_frameRatio.GetXaxis()
xAxisDistro.SetNdivisions(6,5,0)
if 'xaxis' in variable.keys() :
weight_X_frameRatio.GetXaxis().SetTitle(variable['xaxis'])
else :
weight_X_frameRatio.GetXaxis().SetTitle(variableName)
weight_X_frameRatio.GetYaxis().SetTitle("Data/Expected")
weight_X_frameRatio.GetYaxis().SetRangeUser( 0.5, 1.5 )
self.Pad2TAxis(weight_X_frameRatio)
# if there is "histo_total" there is no need of explicit nuisances
if len(mynuisances.keys()) != 0 or histo_total!= None:
weight_X_tgrMCOverMC.SetLineColor(12)
weight_X_tgrMCOverMC.SetFillColor(12)
weight_X_tgrMCOverMC.SetFillStyle(3004)
weight_X_tgrMCOverMC.Draw("2")
weight_X_tgrDataOverMC.Draw("P0")
oneLine2 = ROOT.TLine(weight_X_frameRatio.GetXaxis().GetXmin(), 1, weight_X_frameRatio.GetXaxis().GetXmax(), 1);
oneLine2.SetLineStyle(3)
oneLine2.SetLineWidth(3)
oneLine2.Draw("same")
# draw back all the axes
#weight_X_frameRatio.Draw("AXIS")
weight_X_pad2.RedrawAxis()
self._saveCanvas(weight_X_tcanvasRatio, self._outputDirPlots + "/" + weight_X_canvasRatioNameTemplate + self._FigNamePF)
if 'root' in self._fileFormats:
text_file_html.write(weight_X_canvasRatioNameTemplate + ".root;\n")
# save also all the TH1F separately for later combination
temp_file = ROOT.TFile (self._outputDirPlots + "/" + weight_X_canvasRatioNameTemplate + self._FigNamePF + ".root", "UPDATE")
histo_global_normalization = ROOT.TH1F("histo_global_normalization", "", 1, 0, 1)
histo_global_normalization.Fill(0.5, global_normalization)
histo_global_normalization.Write()
weight_X_tgrMCOverMC.Write()
weight_X_tgrDataOverMC.Write()
# if there is "histo_total" there is no need of explicit nuisances
if len(mynuisances.keys()) != 0 or histo_total!= None:
weight_X_tgrMC.Write("weight_X_tgrMC")
if weight_X_tgrData.GetN() != 0:
weight_X_tgrData.Write("weight_X_tgrData")
if weight_X_thsBackground.GetNhists() != 0:
weight_X_thsBackground.Write()
if weight_X_thsSignal.GetNhists() != 0:
weight_X_thsSignal.Write()
for histo in weight_X_list_Data:
histo.Write()
for histo in weight_X_list_Background:
histo.Write()
for histo in weight_X_list_Signal:
histo.Write()
temp_file.Close()
# log Y axis
if self._plotLog:
weight_X_frameDistro.GetYaxis().SetRangeUser( min(0.001, maxYused/1000), 10 * maxYused )
weight_X_pad1.SetLogy(True)
self._saveCanvas(weight_X_tcanvasRatio, self._outputDirPlots + "/log_" + weight_X_canvasRatioNameTemplate, imageOnly=self._plotLinear)
weight_X_pad1.SetLogy(False)
#
# Now plot difference
#
weight_X_pad2.cd()
if self._showRelativeRatio :
weight_X_frameRatio = weight_X_pad2.DrawFrame(minXused, -1.0, maxXused, 1.0, weight_X_canvasFrameRatioName)
else :
weight_X_frameRatio = weight_X_pad2.DrawFrame(minXused, int( ROOT.TMath.MinElement(weight_X_tgrDataMinusMC.GetN(),weight_X_tgrDataMinusMC.GetY()) - 2 ), maxXused, int ( ROOT.TMath.MaxElement(weight_X_tgrDataMinusMC.GetN(),weight_X_tgrDataMinusMC.GetY()) + 2 ), weight_X_canvasFrameRatioName)
# style from https://ghm.web.cern.ch/ghm/plots/MacroExample/myMacro.py
xAxisDistro = weight_X_frameRatio.GetXaxis()
xAxisDistro.SetNdivisions(6,5,0)
if 'xaxis' in variable.keys() :
weight_X_frameRatio.GetXaxis().SetTitle(variable['xaxis'])
else :
weight_X_frameRatio.GetXaxis().SetTitle(variableName)
if self._showRelativeRatio :
weight_X_frameRatio.GetYaxis().SetRangeUser( -1.0 , 1.0 )
weight_X_frameRatio.GetYaxis().SetTitle("#frac{Data - Expected}{Expected}")
else :
weight_X_frameRatio.GetYaxis().SetRangeUser( int( ROOT.TMath.MinElement(weight_X_tgrDataMinusMC.GetN(),weight_X_tgrDataMinusMC.GetY()) - 2 ), int ( ROOT.TMath.MaxElement(weight_X_tgrDataMinusMC.GetN(),weight_X_tgrDataMinusMC.GetY()) + 2 ) )
weight_X_frameRatio.GetYaxis().SetTitle("Data - Expected")
self.Pad2TAxis(weight_X_frameRatio)
# if there is "histo_total" there is no need of explicit nuisances
if len(mynuisances.keys()) != 0 or histo_total!= None:
weight_X_tgrMCMinusMC.SetLineColor(12)
weight_X_tgrMCMinusMC.SetFillColor(12)
weight_X_tgrMCMinusMC.SetFillStyle(3004)
weight_X_tgrMCMinusMC.Draw("2")
weight_X_tgrDataMinusMC.Draw("P0")
#print(" BINS = " , weight_X_tgrDataMinusMC.GetN())
oneLine2 = ROOT.TLine(weight_X_frameRatio.GetXaxis().GetXmin(), 0, weight_X_frameRatio.GetXaxis().GetXmax(), 0);
oneLine2.SetLineStyle(3)
oneLine2.SetLineWidth(3)
oneLine2.Draw("same")
weight_X_pad2.RedrawAxis()
if self._showRelativeRatio :
weight_X_canvasDifferenceNameTemplate = 'cdifference_relative_weight_X_' + cutName + '_' + variableName
else :
weight_X_canvasDifferenceNameTemplate = 'cdifference_weight_X_' + cutName + '_' + variableName
self._saveCanvas(weight_X_tcanvasRatio, self._outputDirPlots + "/" + weight_X_canvasDifferenceNameTemplate)
if 'root' in self._fileFormats:
text_file_html.write(weight_X_canvasDifferenceNameTemplate + ".root;\n")
#
# This is performed at the end because it will change the "FillStyle" of the histograms
# and you don't want to change it in the previous plots!
# All histograms will become "transparent" as far as fill style is concerned
#
if self._plotNormalizedDistributions :
# ~~~~~~~~~~~~~~~~~~~~
# plot signal vs background normalized
tcanvasSigVsBkg.cd()
frameNorm = ROOT.TH1F
frameNorm = tcanvasSigVsBkg.DrawFrame(minXused, 0.0, maxXused, 1.0)
frameNorm.GetYaxis().SetRangeUser( 0, 1.5 )
# setup axis names
if 'xaxis' in variable.keys() :
frameNorm.GetXaxis().SetTitle(variable['xaxis'])
frameNorm.GetYaxis().SetTitle("a.u.")
tcanvasSigVsBkg.RedrawAxis()
maxY_normalized=0.0
for hentry in thsBackground_grouped.GetHists():
num_bins = hentry.GetNbinsX()
if hentry.Integral() > 0.:
y_normalized = hentry.GetBinContent(hentry.GetMaximumBin())/hentry.Integral()
if y_normalized > maxY_normalized:
maxY_normalized = y_normalized
for ibin in range( num_bins ) :
hentry.SetBinError(ibin+1, 0.000001)
hentry.SetFillStyle(0)
hentry.SetLineWidth(3)
for sampleNameGroup, sampleConfiguration in groupPlot.items():
if ('new_histo_group_' + sampleNameGroup + '_' ) in hentry.GetName() :
if 'line' in sampleConfiguration.keys():
hentry.SetLineStyle(self._getLine(sampleConfiguration['line']))
hentry.DrawNormalized("hist,same")
if thsSignal_grouped.GetNhists() != 0:
for hentry in thsSignal_grouped.GetHists():
num_bins = hentry.GetNbinsX()
if hentry.Integral() > 0.:
y_normalized = hentry.GetBinContent(hentry.GetMaximumBin())/hentry.Integral()
if y_normalized > maxY_normalized:
maxY_normalized = y_normalized
for ibin in range( num_bins ) :
hentry.SetBinError(ibin+1, 0.000001)
hentry.SetFillStyle(0)
hentry.SetLineWidth(3)
for sampleNameGroup, sampleConfiguration in groupPlot.items():
if ('new_histo_group_' + sampleNameGroup + '_' ) in hentry.GetName() :
if 'line' in sampleConfiguration.keys():
hentry.SetLineStyle(self._getLine(sampleConfiguration['line']))
hentry.DrawNormalized("hist,same")
# ~~~~~~~~~~~~~~~~~~~~
# include data only if required
if self._plotNormalizedIncludeData :
for sampleName, plotdef in plot.items():
if plotdef['isData'] == 1 :
if 'line' in plotdef.keys():
histos[sampleName].SetLineStyle(self._getLine(plotdef['line']))
histos[sampleName].DrawNormalized("p, same")
frameNorm.GetYaxis().SetRangeUser(0, 1.8*maxY_normalized)
CMS_lumi.CMS_lumi(tcanvasSigVsBkg, iPeriod, iPos)
tlegend.Draw()
tcanvasSigVsBkg.RedrawAxis()
self._saveCanvas(tcanvasSigVsBkg, self._outputDirPlots + "/" + 'cSigVsBkg_' + cutName + "_" + variableName + self._FigNamePF, imageOnly=True)
if self._plotLog:
# log Y axis
#frameDistro.GetYaxis().SetRangeUser( max(self._minLogCdifference, maxYused/1000), self._maxLogCdifference * maxYused )
frameNorm.GetYaxis().SetRangeUser( min(self._minLogC, maxY_normalized/1000), self._maxLogC * maxY_normalized )
tcanvasSigVsBkg.SetLogy(True)
self._saveCanvas(tcanvasSigVsBkg, self._outputDirPlots + "/log_cSigVsBkg_" + cutName + "_" + variableName + self._FigNamePF, imageOnly=self._plotLinear)
tcanvasSigVsBkg.SetLogy(False)
if self._plotNormalizedDistributionsTHstack :
# ~~~~~~~~~~~~~~~~~~~~
#
# Plot signal vs background normalized
# All the backgrounds or signals will be shown as stacked
# All contributions will be shown as well as in the normal stack distribution
# keeping though the integral of background and signal set to 1
#
tcanvasSigVsBkgTHstack.cd()
frameNormTHstack = ROOT.TH1F
frameNormTHstack = tcanvasSigVsBkgTHstack.DrawFrame(minXused, 0.0, maxXused, 1.0)
frameNormTHstack.GetYaxis().SetRangeUser( 0, 1.5 )
# setup axis names
if 'xaxis' in variable.keys() :
frameNormTHstack.GetXaxis().SetTitle(variable['xaxis'])
tcanvasSigVsBkgTHstack.RedrawAxis()
maxY_normalized=0.0
h_sum_of_backgrounds = thsBackground_grouped.GetStack().Last()
h_sum_of_signals = thsSignal_grouped.GetStack().Last()
normalization_factor_background = 1. / h_sum_of_backgrounds.Integral()
normalization_factor_signal = 1. / h_sum_of_signals.Integral()
if h_sum_of_backgrounds.Integral() > 0.:
maxY_normalized = h_sum_of_backgrounds.GetBinContent(h_sum_of_backgrounds.GetMaximumBin())/h_sum_of_backgrounds.Integral()
if h_sum_of_signals.Integral() > 0.:
temp_maxY_normalized = h_sum_of_signals.GetBinContent(h_sum_of_signals.GetMaximumBin())/h_sum_of_signals.Integral()
if (temp_maxY_normalized > maxY_normalized) :
maxY_normalized = temp_maxY_normalized
for hentry in thsBackground_grouped.GetHists():
if (thsSignal_grouped.GetNhists()==0) or (hentry not in thsSignal_grouped.GetHists()) : # since signal is part of the "background" for plotting reason
num_bins = hentry.GetNbinsX()
for ibin in range( num_bins ) :
hentry.SetBinError(ibin+1, 0.000001)
hentry.SetFillStyle(0)
hentry.SetLineWidth(3)
hentry.Scale(normalization_factor_background)
thsBackground_grouped_normalized.Add(hentry)
if thsSignal_grouped.GetNhists() != 0:
for hentry in thsSignal_grouped.GetHists():
num_bins = hentry.GetNbinsX()
for ibin in range( num_bins ) :
hentry.SetBinError(ibin+1, 0.000001)
hentry.SetFillStyle(0)
hentry.SetLineWidth(3)
hentry.Scale(normalization_factor_signal)
thsSignal_grouped_normalized.Add(hentry)
thsSignal_grouped_normalized.Draw("hist same noclear")
thsBackground_grouped_normalized.Draw("hist same noclear")
frameNormTHstack.GetYaxis().SetRangeUser(0, 1.8*maxY_normalized)
tlegend.Draw()
self._saveCanvas(tcanvasSigVsBkgTHstack, self._outputDirPlots + "/" + 'ccTHstackSigVsBkg_' + cutName + "_" + variableName + self._FigNamePF, imageOnly=True)
# some cleaning
#print(" cleaning ...")
#thsData.Delete()
#print(" cleaning ...")
#thsSignal.Delete()
#print(" cleaning ...")
#thsBackground.Delete()
#print(" cleaning ...")
#thsSignal_grouped.Delete()
#print(" cleaning ...")
#thsBackground_grouped.Delete()
print(" >> end:", variableName)
print(" >> all end")
print(" >> all but really all ")
#
# close plotter.html
#
text_file_html.write(" \"></div> \"\n")
text_file_html.close()
#sys.exit(0)
#quit()
#raise SystemExit()
os._exit(0)
#exit()
# ... or it will remain hanging forever ...
# _____________________________________________________________________________
# --- squared sum
def Pad2TAxis(self, hist):
xaxis = hist.GetXaxis()
xaxis.SetLabelFont ( 42)
xaxis.SetLabelOffset( 0.025)
xaxis.SetLabelSize ( 0.1)
xaxis.SetNdivisions ( 505)
xaxis.SetTitleFont ( 42)
xaxis.SetTitleOffset( 1.35)
xaxis.SetTitleSize ( 0.11)
yaxis = hist.GetYaxis()
yaxis.CenterTitle ( )
yaxis.SetLabelFont ( 42)
yaxis.SetLabelOffset( 0.02)
yaxis.SetLabelSize ( 0.1)
yaxis.SetNdivisions ( 505)
yaxis.SetTitleFont ( 42)
yaxis.SetTitleOffset( .65)
yaxis.SetTitleSize ( 0.11)
# _____________________________________________________________________________
# --- squared sum
def SumQ(self, A, B):
return math.sqrt(A*A + B*B)
# _____________________________________________________________________________
# --- Ratio: if denominator is zero, then put 0!
def Ratio(self, A, B):
if B == 0:
#print("divide by 0")
return 0.
else :
return A / B
# _____________________________________________________________________________
# --- Difference
def Difference(self, A, B):
return A - B
# _____________________________________________________________________________
# --- poissonian error bayesian 1sigma band
# 1/0 1/0
def GetPoissError(self, numberEvents, down, up):
alpha = (1-0.6827)
L = 0
if numberEvents!=0 :
L = ROOT.Math.gamma_quantile (alpha/2,numberEvents,1.)
U = 0
if numberEvents==0 :
#
# Poisson error agreed in the CMS statistics committee
# see: https://hypernews.cern.ch/HyperNews/CMS/get/statistics/263.html
# and https://hypernews.cern.ch/HyperNews/CMS/get/HIG-16-042/32/1/1/1/1/1.html
# and https://twiki.cern.ch/twiki/bin/viewauth/CMS/PoissonErrorBars
# to avoid flip-flop.
# The commented version would have created 1.147 for 0 observed events
# while now we get 1.84 in the case of 0 observed events
#
U = ROOT.Math.gamma_quantile_c (alpha/2,numberEvents+1,1.)
#U = ROOT.Math.gamma_quantile_c (alpha,numberEvents+1,1.)
#print("u = ", U)
else :
U = ROOT.Math.gamma_quantile_c (alpha/2,numberEvents+1,1.)
# the error
L = numberEvents - L
if numberEvents > 0 :
U = U - numberEvents
#else :
#U = 1.14 # --> bayesian interval Poisson with 0 events observed
#1.14790758039 from 10 lines above
if up and not down :
return U
if down and not up :
return L
if up and down :
return (L,U)
# _____________________________________________________________________________
def GetMaximumIncludingErrors(self, histo):
maxWithErrors = 0.
for iBin in range(1, histo.GetNbinsX()+1):
binHeight = histo.GetBinContent (iBin) + histo.GetBinError (iBin)
if binHeight > maxWithErrors :
maxWithErrors = binHeight
return maxWithErrors;
# _____________________________________________________________________________
def GetMinimum(self, histo):
minimum = -1.
for iBin in range(1, histo.GetNbinsX()+1):
binHeight = histo.GetBinContent (iBin)
if binHeight < minimum or minimum<0:
minimum = binHeight
return minimum;
# _____________________________________________________________________________
def defineStyle(self):
print("==================")
import mkShapesRDF.shapeAnalysis.tdrStyle as tdrStyle
tdrStyle.setTDRStyle()
ROOT.TGaxis.SetExponentOffset(-0.08, 0.00,"y")
# _____________________________________________________________________________
# --- fix binning
#
def FixBins(self, histo, reference_x, reference_x_err):
#
# variable bin width
#
nbins = len (reference_x)
#print(" nbins = ", nbins)
binning = [ reference_x[ibin]-reference_x_err[ibin] for ibin in range (0, nbins) ]
#binning = [ reference_histo.GetXaxis().GetBinLowEdge(ibin) for ibin in reference_histo.GetNbinsX()+1 ]
binning.append (reference_x[nbins-1]+reference_x_err[nbins-1])
#print(" >>> histo.GetName() ::", histo.GetName(), " ::> " , binning)
hnew = ROOT.TH1F("new_" + histo.GetName(),"", len(binning)-1, array('d', binning ))
for ibin in range (0, nbins+1) :
y = histo.GetBinContent(ibin)
x = histo.GetXaxis().GetBinCenter(ibin)
hnew.SetBinContent(ibin,y)
hnew.SetFillColor(histo.GetFillColor())
hnew.SetLineColor(histo.GetLineColor())
hnew.SetFillStyle(histo.GetFillStyle())
return hnew
def _saveCanvas(self, tcanvas, nameBase, imageOnly=False):
if 'png' in self._fileFormats:
tcanvas.SaveAs(nameBase + ".png")
if 'pdf' in self._fileFormats:
tcanvas.SaveAs(nameBase + ".pdf")
if 'eps' in self._fileFormats:
tcanvas.SaveAs(nameBase + ".eps")
if not imageOnly:
if 'root' in self._fileFormats:
tcanvas.SaveAs(nameBase + ".root")
if 'C' in self._fileFormats:
tcanvas.SaveAs(nameBase + ".C")
def _getColor(self, color):
if type(color) == int:
return color
elif type(color) == tuple:
# RGB
return ROOT.TColor.GetColor(*color)
elif type(color) == str:
# hex string
return ROOT.TColor.GetColor(color)
def _getLine(self, line):
if type(line) == int:
return line
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment