Source code for rootpy.plotting.hist

import ROOT
from ..core import Object, isbasictype, snake_case_methods
from .core import Plottable, dim
from ..context import invisible_canvas
from ..objectproxy import ObjectProxy
from ..registry import register
from .graph import Graph
from array import array


[docs]class DomainError(Exception): pass
class _HistBase(Plottable, Object): TYPES = { 'C': [ROOT.TH1C, ROOT.TH2C, ROOT.TH3C], 'S': [ROOT.TH1S, ROOT.TH2S, ROOT.TH3S], 'I': [ROOT.TH1I, ROOT.TH2I, ROOT.TH3I], 'F': [ROOT.TH1F, ROOT.TH2F, ROOT.TH3F], 'D': [ROOT.TH1D, ROOT.TH2D, ROOT.TH3D] } def __init__(self): Plottable.__init__(self) def _parse_args(self, *args): params = [{'bins': None, 'nbins': None, 'low': None, 'high': None} for _ in xrange(dim(self))] for param in params: if len(args) == 0: raise TypeError("Did not receive expected number of arguments") if type(args[0]) in [tuple, list]: if list(sorted(args[0])) != list(args[0]): raise ValueError( "Bin edges must be sorted in ascending order") if len(set(args[0])) != len(args[0]): raise ValueError("Bin edges must not be repeated") param['bins'] = args[0] param['nbins'] = len(args[0]) - 1 args = args[1:] elif len(args) >= 3: nbins = args[0] if type(nbins) is not int: raise TypeError( "Type of first argument (got %s %s) must be an int" % (type(nbins), nbins)) low = args[1] if not isbasictype(low): raise TypeError( "Type of second argument must be int, float, or long") high = args[2] if not isbasictype(high): raise TypeError( "Type of third argument must be int, float, or long") param['nbins'] = nbins param['low'] = low param['high'] = high if low >= high: raise ValueError( "Upper bound (you gave %f) must be greater than lower " "bound (you gave %f)" % (float(low), float(high))) args = args[3:] else: raise TypeError( "Did not receive expected number of arguments") if len(args) != 0: raise TypeError( "Did not receive expected number of arguments") return params @classmethod def divide(cls, h1, h2, c1=1., c2=1., option=''): ratio = h1.Clone() rootbase = h1.__class__.__bases__[-1] rootbase.Divide(ratio, h1, h2, c1, c2, option) return ratio def Fill(self, *args): bin = self.__class__.__bases__[-1].Fill(self, *args) if bin > 0: return bin - 1 return bin def nbins(self, axis=1): if axis == 1: return self.GetNbinsX() elif axis == 2: return self.GetNbinsY() elif axis == 3: return self.GetNbinsZ() else: raise ValueError("%s is not a valid axis index!" % axis) def axis(self, axis=1): if axis == 1: return self.GetXaxis() elif axis == 2: return self.GetYaxis() elif axis == 3: return self.GetZaxis() else: raise ValueError("%s is not a valid axis index!" % axis) @property def xaxis(self): return self.GetXaxis() @property def yaxis(self): return self.GetYaxis() @property def zaxis(self): return self.GetZaxis() def underflow(self, axis=1): """ Return the underflow for the given axis. Depending on the dimension of the histogram, may return an array. """ if axis not in [1, 2, 3]: raise ValueError("%s is not a valid axis index!" % axis) if self.DIM == 1: return self.GetBinContent(0) elif self.DIM == 2: return [self.GetBinContent(*[i].insert(axis - 1, 0)) for i in xrange(self.nbins((axis + 1) % 2))] elif self.DIM == 3: axis2, axis3 = [1, 2, 3].remove(axis) return [[self.GetBinContent(*[i, j].insert(axis - 1, 0)) for i in xrange(self.nbins(axis2))] for j in xrange(self.nbins(axis3))] def overflow(self, axis=1): """ Return the overflow for the given axis. Depending on the dimension of the histogram, may return an array. """ if axis not in [1, 2, 3]: raise ValueError("%s is not a valid axis index!" % axis) if self.DIM == 1: return self.GetBinContent(self.nbins(1) + 1) elif self.DIM == 2: axis2 = [1, 2].remove(axis) return [self.GetBinContent(*[i].insert(axis - 1, self.nbins(axis))) for i in xrange(self.nbins(axis2))] elif self.DIM == 3: axis2, axis3 = [1, 2, 3].remove(axis) return [[self.GetBinContent( *[i, j].insert(axis - 1, self.nbins(axis))) for i in xrange(self.nbins(axis2))] for j in xrange(self.nbins(axis3))] def lowerbound(self, axis=1): if axis == 1: return self.xedges(0) if axis == 2: return self.yedges(0) if axis == 3: return self.zedges(0) return ValueError("axis must be 1, 2, or 3") def upperbound(self, axis=1): if axis == 1: return self.xedges(-1) if axis == 2: return self.yedges(-1) if axis == 3: return self.zedges(-1) return ValueError("axis must be 1, 2, or 3") def _centers(self, axis, index=None): if index is None: return (self._centers(axis, i) for i in xrange(self.nbins(axis))) index = index % self.nbins(axis) return (self._edgesl(axis, index) + self._edgesh(axis, index)) / 2 def _edgesl(self, axis, index=None): if index is None: return (self._edgesl(axis, i) for i in xrange(self.nbins(axis))) index = index % self.nbins(axis) return self.axis(axis).GetBinLowEdge(index + 1) def _edgesh(self, axis, index=None): if index is None: return (self._edgesh(axis, i) for i in xrange(self.nbins(axis))) index = index % self.nbins(axis) return self.axis(axis).GetBinUpEdge(index + 1) def _edges(self, axis, index=None): nbins = self.nbins(axis) if index is None: def temp_generator(): for index in xrange(nbins): yield self._edgesl(axis, index) yield self._edgesh(axis, index) return temp_generator() index = index % (nbins + 1) if index == nbins: return self._edgesh(axis, -1) return self._edgesl(axis, index) def _width(self, axis, index=None): if index is None: return (self._width(axis, i) for i in xrange(self.nbins(axis))) index = index % self.nbins(axis) return self._edgesh(axis, index) - self._edgesl(axis, index) def _erravg(self, axis, index=None): if index is None: return (self._erravg(axis, i) for i in xrange(self.nbins(axis))) index = index % self.nbins(axis) return self._width(axis, index) / 2 def _err(self, axis, index=None): if index is None: return ((self._erravg(axis, i), self._erravg(axis, i)) for i in xrange(self.nbins(axis))) index = index % self.nbins(axis) return (self._erravg(axis, index), self._erravg(axis, index)) def __add__(self, other): copy = self.Clone() copy += other return copy def __iadd__(self, other): if isbasictype(other): if not isinstance(self, _Hist): raise ValueError( "A multidimensional histogram must be filled with a tuple") self.Fill(other) elif type(other) in [list, tuple]: if dim(self) not in [len(other), len(other) - 1]: raise ValueError( "Dimension of %s does not match dimension " "of histogram (with optional weight as last element)" % str(other)) self.Fill(*other) else: self.Add(other) return self def __sub__(self, other): copy = self.Clone() copy -= other return copy def __isub__(self, other): if isbasictype(other): if not isinstance(self, _Hist): raise ValueError( "A multidimensional histogram must be filled with a tuple") self.Fill(other, -1) elif type(other) in [list, tuple]: if len(other) == dim(self): self.Fill(*(other + (-1, ))) elif len(other) == dim(self) + 1: # negate last element self.Fill(*(other[:-1] + (-1 * other[-1], ))) else: raise ValueError( "Dimension of %s does not match dimension " "of histogram (with optional weight as last element)" % str(other)) else: self.Add(other, -1.) return self def __mul__(self, other): copy = self.Clone() copy *= other return copy def __imul__(self, other): if isbasictype(other): self.Scale(other) return self self.Multiply(other) return self def __div__(self, other): copy = self.Clone() copy /= other return copy def __idiv__(self, other): if isbasictype(other): if other == 0: raise ZeroDivisionError() self.Scale(1. / other) return self self.Divide(other) return self def __radd__(self, other): if other == 0: return self.Clone() raise TypeError("unsupported operand type(s) for +: '%s' and '%s'" % (other.__class__.__name__, self.__class__.__name__)) def __rsub__(self, other): if other == 0: return self.Clone() raise TypeError("unsupported operand type(s) for -: '%s' and '%s'" % (other.__class__.__name__, self.__class__.__name__)) def __len__(self): return self.GetNbinsX() def __getitem__(self, index): # TODO: Perhaps this should return a Hist object of dimension (DIM - 1) if index not in range(-1, len(self) + 1): raise IndexError("bin index %i out of range" % index) def __setitem__(self, index): if index not in range(-1, len(self) + 1): raise IndexError("bin index %i out of range" % index) def __iter__(self): return iter(self._content()) def __cmp__(self, other): diff = self.maximum() - other.maximum() if diff > 0: return 1 if diff < 0: return -1 return 0 def errors(self): return iter(self._error_content()) def asarray(self, typecode='f'): return array(typecode, self._content()) def fill_array(self, array, weights=None): """ Fill this histogram with a NumPy array """ try: from . import _libnumpyhist hist = ROOT.AsCObject(self) if weights is not None: _libnumpyhist.fill_hist_with_ndarray( hist, self.DIM, array, weights) else: _libnumpyhist.fill_hist_with_ndarray( hist, self.DIM, array) except ImportError: raise ImportError('``fill_array`` requires NumPy') class _Hist(_HistBase): DIM = 1 def __init__(self, *args, **kwargs): name = kwargs.get('name', None) title = kwargs.get('title', None) params = self._parse_args(*args) if params[0]['bins'] is None: Object.__init__(self, name, title, params[0]['nbins'], params[0]['low'], params[0]['high']) else: Object.__init__(self, name, title, params[0]['nbins'], array('d', params[0]['bins'])) self._post_init(**kwargs) def _post_init(self, **kwargs): _HistBase.__init__(self) self.decorate(**kwargs) def x(self, index=None): return self._centers(1, index) def xerravg(self, index=None): return self._erravg(1, index) def xerrl(self, index=None): return self._erravg(1, index) def xerrh(self, index=None): return self._erravg(1, index) def xerr(self, index=None): return self._err(1, index) def xwidth(self, index=None): return self._width(1, index) def xedgesl(self, index=None): return self._edgesl(1, index) def xedgesh(self, index=None): return self._edgesh(1, index) def xedges(self, index=None): return self._edges(1, index) def yerrh(self, index=None): return self.yerravg(index) def yerrl(self, index=None): return self.yerravg(index) def y(self, index=None): if index is None: return (self.y(i) for i in xrange(self.nbins(1))) index = index % len(self) return self.GetBinContent(index + 1) def yerravg(self, index=None): if index is None: return (self.yerravg(i) for i in xrange(self.nbins(1))) index = index % len(self) return self.GetBinError(index + 1) def yerr(self, index=None): if index is None: return ((self.yerrl(i), self.yerrh(i)) for i in xrange(self.nbins(1))) index = index % len(self) return (self.yerrl(index), self.yerrh(index)) def GetMaximum(self, **kwargs): return self.maximum(**kwargs) def maximum(self, include_error=False): if not include_error: return self.__class__.__bases__[-1].GetMaximum(self) clone = self.Clone() for i in xrange(clone.GetNbinsX()): clone.SetBinContent( i + 1, clone.GetBinContent(i + 1) + clone.GetBinError(i + 1)) return clone.maximum() def GetMinimum(self, **kwargs): return self.minimum(**kwargs) def minimum(self, include_error=False): if not include_error: return self.__class__.__bases__[-1].GetMinimum(self) clone = self.Clone() for i in xrange(clone.GetNbinsX()): clone.SetBinContent( i + 1, clone.GetBinContent(i + 1) - clone.GetBinError(i + 1)) return clone.minimum() def expectation(self, startbin=0, endbin=None): if endbin is not None and endbin < startbin: raise DomainError("endbin should be greated than startbin") if endbin is None: endbin = len(self) - 1 expect = 0. norm = 0. for index in xrange(startbin, endbin + 1): val = self[index] expect += val * self.x(index) norm += val if norm > 0: return expect / norm else: return (self.xedges(endbin + 1) + self.xedges(startbin)) / 2 def _content(self): return self.y() def _error_content(self): return self.yerravg() def __getitem__(self, index): """ if type(index) is slice: return self._content()[index] """ _HistBase.__getitem__(self, index) return self.y(index) def __getslice__(self, i, j): # TODO: getslice is deprecated. getitem should accept slice objects. return list(self)[i:j] def __setitem__(self, index, value): _HistBase.__setitem__(self, index) self.SetBinContent(index + 1, value) class _Hist2D(_HistBase): DIM = 2 def __init__(self, *args, **kwargs): name = kwargs.get('name', None) title = kwargs.get('title', None) params = self._parse_args(*args) if params[0]['bins'] is None and params[1]['bins'] is None: Object.__init__(self, name, title, params[0]['nbins'], params[0]['low'], params[0]['high'], params[1]['nbins'], params[1]['low'], params[1]['high']) elif params[0]['bins'] is None and params[1]['bins'] is not None: Object.__init__(self, name, title, params[0]['nbins'], params[0]['low'], params[0]['high'], params[1]['nbins'], array('d', params[1]['bins'])) elif params[0]['bins'] is not None and params[1]['bins'] is None: Object.__init__(self, name, title, params[0]['nbins'], array('d', params[0]['bins']), params[1]['nbins'], params[1]['low'], params[1]['high']) else: Object.__init__(self, name, title, params[0]['nbins'], array('d', params[0]['bins']), params[1]['nbins'], array('d', params[1]['bins'])) self._post_init(**kwargs) def _post_init(self, **kwargs): _HistBase.__init__(self) self.decorate(**kwargs) def x(self, index=None): return self._centers(1, index) def xerravg(self, index=None): return self._erravg(1, index) def xerrl(self, index=None): return self._erravg(1, index) def xerrh(self, index=None): return self._erravg(1, index) def xerr(self, index=None): return self._err(1, index) def xwidth(self, index=None): return self._width(1, index) def xedgesl(self, index=None): return self._edgesl(1, index) def xedgesh(self, index=None): return self._edgesh(1, index) def xedges(self, index=None): return self._edges(1, index) def y(self, index=None): return self._centers(2, index) def yerravg(self, index=None): return self._erravg(2, index) def yerrl(self, index=None): return self._erravg(2, index) def yerrh(self, index=None): return self._erravg(2, index) def yerr(self, index=None): return self._err(2, index) def ywidth(self, index=None): return self._width(2, index) def yedgesl(self, index=None): return self._edgesl(2, index) def yedgesh(self, index=None): return self._edgesh(2, index) def yedges(self, index=None): return self._edges(2, index) def zerrh(self, index=None): return self.zerravg(index) def zerrl(self, index=None): return self.zerravg(index) def z(self, ix=None, iy=None): if ix is None and iy is None: return [[self.z(ix, iy) for iy in xrange(self.nbins(2))] for ix in xrange(self.nbins(1))] ix = ix % self.nbins(1) iy = iy % self.nbins(2) return self.GetBinContent(ix + 1, iy + 1) def zerravg(self, ix=None, iy=None): if ix is None and iy is None: return [[self.zerravg(ix, iy) for iy in xrange(self.nbins(2))] for ix in xrange(self.nbins(1))] ix = ix % self.nbins(1) iy = iy % self.nbins(2) return self.GetBinError(ix + 1, iy + 1) def zerr(self, ix=None, iy=None): if ix is None and iy is None: return [[(self.zerravg(ix, iy), self.zerravg(ix, iy)) for iy in xrange(self.nbins(2))] for ix in xrange(self.nbins(1))] ix = ix % self.nbins(1) iy = iy % self.nbins(2) return (self.GetBinError(ix + 1, iy + 1), self.GetBinError(ix + 1, iy + 1)) def _content(self): return self.z() def _error_content(self): return self.zerravg() def __getitem__(self, index): if isinstance(index, tuple): # support indexing like h[1,2] return self.z(*index) _HistBase.__getitem__(self, index) a = ObjectProxy([ self.GetBinContent(index + 1, j) for j in xrange(1, self.GetNbinsY() + 1)]) a.__setposthook__('__setitem__', self._setitem(index)) return a def _setitem(self, i): def __setitem(j, value): self.SetBinContent(i + 1, j + 1, value) return __setitem def ravel(self): """ Convert 2D histogram into 1D histogram with the y-axis repeated along the x-axis, similar to NumPy's ravel(). """ nbinsx = self.nbins(1) nbinsy = self.nbins(2) out = Hist(self.nbins(1) * nbinsy, self.xedgesl(0), self.xedgesh(-1) * nbinsy, type=self.TYPE, title=self.title, **self.decorators) for i in range(nbinsx): for j in range(nbinsy): out[i + nbinsy * j] = self[i, j] out.SetBinError(i + nbinsy * j + 1, self.GetBinError(i + 1, j + 1)) return out class _Hist3D(_HistBase): DIM = 3 def __init__(self, *args, **kwargs): name = kwargs.get('name', None) title = kwargs.get('title', None) params = self._parse_args(*args) # ROOT is missing constructors for TH3F... if params[0]['bins'] is None and \ params[1]['bins'] is None and \ params[2]['bins'] is None: Object.__init__(self, name, title, params[0]['nbins'], params[0]['low'], params[0]['high'], params[1]['nbins'], params[1]['low'], params[1]['high'], params[2]['nbins'], params[2]['low'], params[2]['high']) else: if params[0]['bins'] is None: step = (params[0]['high'] - params[0]['low'])\ / float(params[0]['nbins']) params[0]['bins'] = [ params[0]['low'] + n * step for n in xrange(params[0]['nbins'] + 1)] if params[1]['bins'] is None: step = (params[1]['high'] - params[1]['low'])\ / float(params[1]['nbins']) params[1]['bins'] = [ params[1]['low'] + n * step for n in xrange(params[1]['nbins'] + 1)] if params[2]['bins'] is None: step = (params[2]['high'] - params[2]['low'])\ / float(params[2]['nbins']) params[2]['bins'] = [ params[2]['low'] + n * step for n in xrange(params[2]['nbins'] + 1)] Object.__init__(self, name, title, params[0]['nbins'], array('d', params[0]['bins']), params[1]['nbins'], array('d', params[1]['bins']), params[2]['nbins'], array('d', params[2]['bins'])) self._post_init(**kwargs) def _post_init(self, **kwargs): _HistBase.__init__(self) self.decorate(**kwargs) def x(self, index=None): return self._centers(1, index) def xerravg(self, index=None): return self._erravg(1, index) def xerrl(self, index=None): return self._erravg(1, index) def xerrh(self, index=None): return self._erravg(1, index) def xerr(self, index=None): return self._err(1, index) def xwidth(self, index=None): return self._width(1, index) def xedgesl(self, index=None): return self._edgesl(1, index) def xedgesh(self, index=None): return self._edgesh(1, index) def xedges(self, index=None): return self._edges(1, index) def y(self, index=None): return self._centers(2, index) def yerravg(self, index=None): return self._erravg(2, index) def yerrl(self, index=None): return self._erravg(2, index) def yerrh(self, index=None): return self._erravg(2, index) def yerr(self, index=None): return self._err(2, index) def ywidth(self, index=None): return self._width(2, index) def yedgesl(self, index=None): return self._edgesl(2, index) def yedgesh(self, index=None): return self._edgesh(2, index) def yedges(self, index=None): return self._edges(2, index) def z(self, index=None): return self._centers(3, index) def zerravg(self, index=None): return self._erravg(3, index) def zerrl(self, index=None): return self._erravg(3, index) def zerrh(self, index=None): return self._erravg(3, index) def zerr(self, index=None): return self._err(3, index) def zwidth(self, index=None): return self._width(3, index) def zedgesl(self, index=None): return self._edgesl(3, index) def zedgesh(self, index=None): return self._edgesh(3, index) def zedges(self, index=None): return self._edges(3, index) def werrh(self, index=None): return self.werravg(index) def werrl(self, index=None): return self.werravg(index) def w(self, ix=None, iy=None, iz=None): if ix is None and iy is None and iz is None: return [[[self.w(ix, iy, iz) for iz in xrange(self.nbins(3))] for iy in xrange(self.nbins(2))] for ix in xrange(self.nbins(1))] ix = ix % self.nbins(1) iy = iy % self.nbins(2) iz = iz % self.nbins(3) return self.GetBinContent(ix + 1, iy + 1, iz + 1) def werravg(self, ix=None, iy=None, iz=None): if ix is None and iy is None and iz is None: return [[[self.werravg(ix, iy, iz) for iz in xrange(self.nbins(3))] for iy in xrange(self.nbins(2))] for ix in xrange(self.nbins(1))] ix = ix % self.nbins(1) iy = iy % self.nbins(2) iz = iz % self.nbins(3) return self.GetBinError(ix + 1, iy + 1, iz + 1) def werr(self, ix=None, iy=None, iz=None): if ix is None and iy is None and iz is None: return [[[(self.werravg(ix, iy, iz), self.werravg(ix, iy, iz)) for iz in xrange(self.nbins(3))] for iy in xrange(self.nbins(2))] for ix in xrange(self.nbins(1))] ix = ix % self.nbins(1) iy = iy % self.nbins(2) iz = iz % self.nbins(3) return (self.GetBinError(ix + 1, iy + 1, iz + 1), self.GetBinError(ix + 1, iy + 1, iz + 1)) def _content(self): return [[[ self.GetBinContent(i, j, k) for i in xrange(1, self.GetNbinsX() + 1)] for j in xrange(1, self.GetNbinsY() + 1)] for k in xrange(1, self.GetNbinsZ() + 1)] def _error_content(self): return [[[ self.GetBinError(i, j, k) for i in xrange(1, self.GetNbinsX() + 1)] for j in xrange(1, self.GetNbinsY() + 1)] for k in xrange(1, self.GetNbinsZ() + 1)] def __getitem__(self, index): if isinstance(index, tuple): # support indexing like h[1,2,1] return self.w(*index) _HistBase.__getitem__(self, index) out = [] for j in xrange(1, self.GetNbinsY() + 1): a = ObjectProxy([ self.GetBinContent(index + 1, j, k) for k in xrange(1, self.GetNbinsZ() + 1)]) a.__setposthook__('__setitem__', self._setitem(index, j - 1)) out.append(a) return out def _setitem(self, i, j): def __setitem(k, value): self.SetBinContent(i + 1, j + 1, k + 1, value) return __setitem def _Hist_class(type='F'): type = type.upper() if type not in _HistBase.TYPES: raise TypeError("No histogram available with bin type %s" % type) rootclass = _HistBase.TYPES[type][0] class Hist(_Hist, rootclass): TYPE = type return Hist def _Hist2D_class(type='F'): type = type.upper() if type not in _HistBase.TYPES: raise TypeError("No histogram available with bin type %s" % type) rootclass = _HistBase.TYPES[type][1] class Hist2D(_Hist2D, rootclass): TYPE = type return Hist2D def _Hist3D_class(type='F'): type = type.upper() if type not in _HistBase.TYPES: raise TypeError("No histogram available with bin type %s" % type) rootclass = _HistBase.TYPES[type][2] class Hist3D(_Hist3D, rootclass): TYPE = type return Hist3D _HIST_CLASSES_1D = {} _HIST_CLASSES_2D = {} _HIST_CLASSES_3D = {} # register the classes for bintype in _HistBase.TYPES.keys(): cls = _Hist_class(type=bintype) register()(cls) snake_case_methods(cls) _HIST_CLASSES_1D[bintype] = cls cls = _Hist2D_class(type=bintype) register()(cls) snake_case_methods(cls) _HIST_CLASSES_2D[bintype] = cls cls = _Hist3D_class(type=bintype) register()(cls) snake_case_methods(cls) _HIST_CLASSES_3D[bintype] = cls
[docs]class Hist(_Hist): """ Returns a 1-dimensional Hist object which inherits from the associated ROOT.TH1* class (where * is C, S, I, F, or D depending on the type keyword argument) """ def __new__(cls, *args, **kwargs): return _HIST_CLASSES_1D[kwargs.get('type', 'F').upper()]( *args, **kwargs)
[docs]class Hist2D(_Hist2D): """ Returns a 2-dimensional Hist object which inherits from the associated ROOT.TH1* class (where * is C, S, I, F, or D depending on the type keyword argument) """ def __new__(cls, *args, **kwargs): return _HIST_CLASSES_2D[kwargs.get('type', 'F').upper()]( *args, **kwargs)
[docs]class Hist3D(_Hist3D): """ Returns a 3-dimensional Hist object which inherits from the associated ROOT.TH1* class (where * is C, S, I, F, or D depending on the type keyword argument) """ def __new__(cls, *args, **kwargs): return _HIST_CLASSES_3D[kwargs.get('type', 'F').upper()]( *args, **kwargs)
if ROOT.gROOT.GetVersionInt() >= 52800: @snake_case_methods @register()
[docs] class Efficiency(Plottable, Object, ROOT.TEfficiency): def __init__(self, passed, total, name=None, title=None, **kwargs): if dim(passed) != 1 or dim(total) != 1: raise TypeError( "histograms must be 1 dimensional") if len(passed) != len(total): raise ValueError( "histograms must have the same number of bins") if list(passed.xedges()) != list(total.xedges()): raise ValueError( "histograms do not have the same bin boundaries") Object.__init__( self, name, title, len(total), total.xedgesl(0), total.xedgesh(-1)) self.passed = passed.Clone() self.total = total.Clone() self.SetPassedHistogram(self.passed, 'f') self.SetTotalHistogram(self.total, 'f') Plottable.__init__(self) self.decorate(**kwargs) def __len__(self): return len(self.total) def __getitem__(self, bin): return self.GetEfficiency(bin + 1) def __add__(self, other): copy = self.Clone() copy.Add(other) return copy def __iadd__(self, other): ROOT.TEfficiency.Add(self, other) return self def __iter__(self): for bin in xrange(len(self)): yield self[bin]
[docs] def errors(self): for bin in xrange(len(self)): yield (self.GetEfficiencyErrorLow(bin + 1), self.GetEfficiencyErrorUp(bin + 1))
[docs] def GetGraph(self): graph = Graph(len(self)) for index, (bin, effic, (low, up)) in enumerate( zip(xrange(len(self)), iter(self), self.errors())): graph.SetPoint(index, self.total.x(bin), effic) xerror = self.total.xwidth(bin) / 2. graph.SetPointError(index, xerror, xerror, low, up) return graph
@property
[docs] def painted_graph(self): """ Returns the painted graph for a TEfficiency, or if it isn't available, generates one on an `invisible_canvas`. """ if not self.GetPaintedGraph(): with invisible_canvas(): self.Draw() assert self.GetPaintedGraph(), ( "Failed to create TEfficiency::GetPaintedGraph") return self.GetPaintedGraph()
@property
[docs] def xaxis(self): return self.painted_graph.GetXaxis()
@property
[docs] def yaxis(self): return self.painted_graph.GetYaxis()
@register()
[docs]class HistStack(Plottable, Object, ROOT.THStack): def __init__(self, name=None, title=None, **kwargs): Object.__init__(self, name, title) self.hists = [] self.sum = None Plottable.__init__(self) self.dim = 1 def _post_init(self, **kwargs): self.decorate(**kwargs) def __dim__(self): return self.dim
[docs] def GetHists(self): return [hist for hist in self.hists]
[docs] def Add(self, hist): if isinstance(hist, _Hist) or isinstance(hist, _Hist2D): if not self: self.dim = dim(hist) self.sum = hist.Clone() elif dim(self) != dim(hist): raise TypeError( "Dimension of histogram does not match dimension " "of already contained histograms") else: self.sum += hist self.hists.append(hist) ROOT.THStack.Add(self, hist, hist.drawstyle) else: raise TypeError( "Only 1D and 2D histograms are supported")
def __add__(self, other): if not isinstance(other, HistStack): raise TypeError( "Addition not supported for HistStack and %s" % other.__class__.__name__) clone = HistStack() for hist in self: clone.Add(hist) for hist in other: clone.Add(hist) return clone def __iadd__(self, other): if not isinstance(other, HistStack): raise TypeError( "Addition not supported for HistStack and %s" % other.__class__.__name__) for hist in other: self.Add(hist) return self def __len__(self): return len(self.GetHists()) def __getitem__(self, index): return self.GetHists()[index] def __iter__(self): for hist in self.hists: yield hist def __nonzero__(self): return len(self) != 0 def __cmp__(self, other): diff = self.maximum() - other.maximum() if diff > 0: return 1 if diff < 0: return -1 return 0
[docs] def Scale(self, value): for hist in self: hist.Scale(value)
[docs] def Integral(self, start=None, end=None): integral = 0 if start != None and end != None: for hist in self: integral += hist.Integral(start, end) else: for hist in self: integral += hist.Integral() return integral
[docs] def lowerbound(self, axis=1): if not self: return None # negative infinity return min(hist.lowerbound(axis=axis) for hist in self)
[docs] def upperbound(self, axis=1): if not self: return () # positive infinity return max(hist.upperbound(axis=axis) for hist in self)
[docs] def GetMaximum(self, **kwargs): return self.maximum(**kwargs)
[docs] def maximum(self, **kwargs): if not self: return 0 return self.sum.GetMaximum(**kwargs)
[docs] def GetMinimum(self, **kwargs): return self.minimum(**kwargs)
[docs] def minimum(self, **kwargs): if not self: return 0 return self.sum.GetMinimum(**kwargs)
[docs] def Clone(self, newName=None): clone = HistStack(name=newName, title=self.GetTitle()) clone.decorate(template_object=self) for hist in self: clone.Add(hist.Clone()) return clone
@property
[docs] def xaxis(self): return self.GetXaxis()
@property
[docs] def yaxis(self): return self.GetYaxis()