import ROOT
from . import pyrootHelpers as PH
from .arrow import Arrow
from .commonHelpers.logger import logger
logger = logger.getChild(__name__)
[docs]class AE(ROOT.TGraphAsymmErrors):
def __init__(self, histo=None, relErr=False, customDrawString=None):
if relErr and histo is not None:
super(AE, self).__init__(PH.getRelErrHist(histo))
elif histo is not None:
super(AE, self).__init__(histo)
else:
super(AE, self).__init__()
self.customDrawString = customDrawString
self.zIndex = 5
self.data = False
self.arrows = []
self.xTitle = None
# def firstDraw(self):
# self.draw(drawstring='a')
[docs] def draw(self, drawString='', **kwargs):
if self.customDrawString is not None:
self.Draw(self.customDrawString)
elif self.data:
self.SetMarkerStyle(20)
self.Draw(drawString + "PE0")
else:
self.Draw(drawString + "2")
if self.arrows:
for arrow in self.arrows:
arrow.draw()
[docs] def truncateErrors(self):
for i in range(1, self.GetN()):
self.SetPointError(i, 0, 0, 0, 0)
[docs] def addRatioArrows(self, up, low):
"""Add a list of arrows to a ratio graph. The ratio thresholds
for which an arrow is drawn are given by up, low"""
self.arrows = []
x = ROOT.Double()
y = ROOT.Double()
for i in range(1, self.GetN()):
self.GetPoint(i, x, y)
if y > up:
logger.debug("Point number {} at {} with ratio {} will get an up arrow".format(i, x, y))
self.arrows.append(self.makeArrow(x, up, low, True))
if y < low:
logger.debug("Point number {} at {} with ratio {} will get a down arrow".format(i, x, y))
self.arrows.append(self.makeArrow(x, up, low, False))
[docs] @staticmethod
def makeArrow(bincenter, up, low, pointUp=True):
length = 0.2*(up-low)
size = 0.015
if pointUp:
arrow = Arrow(bincenter, up - length, bincenter, up - 0.2*length, 0.01, "|>")
else:
arrow = Arrow(bincenter, low + length, bincenter, low + 0.2*length, 0.01, "|>")
arrow.SetLineWidth(2)
arrow.SetLineColor(ROOT.kRed)
arrow.SetFillColor(ROOT.kRed)
return arrow
[docs]def getPoissonRatio(num, den, ignoreDenErrors=True, ignoreNumErrors=False):
"""Returns a ratio graph of 2 independend histograms. For the error
calculation the Yield per bin is assumed to be the mean of a
poisson distribution (histogram error is ignored).
Optional keyword arguments:
ignoreDenErrors: Ignore the denominator for error calculation (default True)
ignoreNumErrors: Ignore the numerator for error calculation (default False)
"""
if ignoreNumErrors:
raise NotImplementedError("ignoring the numerator for errors is not implemented yet")
if not ignoreDenErrors:
raise NotImplementedError("Considering the denominator for errros is not implemented yet")
ae = AE()
ae.SetName(next(PH.tempNames))
for i in range(1, num.GetNbinsX()+1):
center = num.GetBinCenter(i)
width = num.GetBinWidth(i)/2.0
yNum = num.GetBinContent(i)
yDen = den.GetBinContent(i)
if yNum <= 0.:
logger.debug("Numerator number {} is 0 or negative ({}) for bin at {} - ignoring this bin".format(i, yNum, center))
continue
try:
ratio = num.GetBinContent(i)/den.GetBinContent(i)
eyHigh = (PH.calcPoissonCLUpper(0.68, yNum))/yDen-ratio
eyLow = ratio - (PH.calcPoissonCLLower(0.68, yNum))/yDen
pointIndex = ae.GetN()
ae.SetPoint(pointIndex, center, ratio)
ae.SetPointError(pointIndex, width, width, eyLow, eyHigh)
except ZeroDivisionError:
logger.debug("Denominator number {} is 0 for bin at {} - ignoring this bin (Numerator: {})".format(i, center, yNum))
pass
logger.debug("Created ratio graph with {} points".format(ae.GetN()))
return ae