2 Utilities for rootplot including histogram classes. 4 from __future__
import print_function
7 Copyright (c) 2009-2010 Jeff Klukas <klukas@wisc.edu> 9 Permission is hereby granted, free of charge, to any person obtaining a copy 10 of this software and associated documentation files (the "Software"), to deal 11 in the Software without restriction, including without limitation the rights 12 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 copies of the Software, and to permit persons to whom the Software is 14 furnished to do so, subject to the following conditions: 16 The above copyright notice and this permission notice shall be included in 17 all copies or substantial portions of the Software. 19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 38 from random
import gauss
43 """A container to hold the parameters from a 2D ROOT histogram.""" 44 def __init__(self, hist, label="__nolabel__", title=None,
45 xlabel=
None, ylabel=
None):
47 if not hist.InheritsFrom(
"TH2"):
48 raise TypeError(
"%s does not inherit from TH2" % hist)
50 raise TypeError(
"%s is not a ROOT object" % hist)
56 for i
in range(1, nx + 1)])
58 self.
nbinsx = nx = self.binlabelsx.index(
'')
61 for i
in range(1, ny + 1)])
63 self.
nbinsy = ny = self.binlabelsy.index(
'')
66 self.
content = [[hist.GetBinContent(i, j)
for i
in range(1, nx + 1)]
67 for j
in range(1, ny + 1)]
68 self.
xedges = [hist.GetXaxis().GetBinLowEdge(i)
69 for i
in range(1, nx + 2)]
70 self.
yedges = [hist.GetYaxis().GetBinLowEdge(i)
71 for i
in range(1, ny + 2)]
76 self.
title = title
or hist.GetTitle()
77 self.
xlabel = xlabel
or hist.GetXaxis().GetTitle()
78 self.
ylabel = ylabel
or hist.GetYaxis().GetTitle()
86 """Return contents of indexth bin: x.__getitem__(y) <==> x[y]""" 89 """Return the number of bins: x.__len__() <==> len(x)""" 92 """Iterate through bins: x.__iter__() <==> iter(x)""" 95 """Return a ROOT.TH2F object with contents of this Hist2D.""" 96 th2f = ROOT.TH2F(name,
"",
100 for ix
in range(self.
nbinsx):
101 for iy
in range(self.
nbinsy):
102 th2f.SetBinContent(ix + 1, iy + 1, self.
content[iy][ix])
106 """A container to hold the parameters from a ROOT histogram.""" 107 def __init__(self, hist, label="__nolabel__",
108 name=
None, title=
None, xlabel=
None, ylabel=
None):
112 except AttributeError:
116 except AttributeError:
117 raise TypeError(
"%s is not a 1D histogram or TGraph" % hist)
119 self.
name = name
or hist.GetName()
121 self.
xlabel = xlabel
or hist.GetXaxis().GetTitle()
122 self.
ylabel = ylabel
or hist.GetYaxis().GetTitle()
127 for i
in range(1, n + 1)])
130 self.
nbins = n = self.binlabels.index(
'')
133 self.
xedges = [hist.GetBinLowEdge(i)
for i
in range(1, n + 2)]
138 self.
y = [hist.GetBinContent(i)
for i
in range(1, n + 1)]
139 self.
yerr = [hist.GetBinError( i)
for i
in range(1, n + 1)]
144 self.
nbins = n = hist.GetN()
145 self.
x, self.
y = [], []
146 x, y = ROOT.Double(0), ROOT.Double(0)
148 hist.GetPoint(i, x, y)
149 self.x.append(copy.copy(x))
150 self.y.append(copy.copy(y))
151 lower = [
max(0, hist.GetErrorXlow(i))
for i
in xrange(n)]
152 upper = [
max(0, hist.GetErrorXhigh(i))
for i
in xrange(n)]
153 self.
xerr = [lower[:], upper[:]]
154 lower = [
max(0, hist.GetErrorYlow(i))
for i
in xrange(n)]
155 upper = [
max(0, hist.GetErrorYhigh(i))
for i
in xrange(n)]
156 self.
yerr = [lower[:], upper[:]]
157 self.
xedges = [self.
x[i] - self.
xerr[0][i]
for i
in xrange(n)]
158 self.xedges.append(self.
x[n - 1] + self.
xerr[1][n - 1])
164 """Return the sum of self and b: x.__add__(y) <==> x + y""" 166 for i
in range(len(self)):
168 c.yerr[0][i] += b.yerr[0][i]
169 c.yerr[1][i] += b.yerr[1][i]
170 c.overflow += b.overflow
171 c.underflow += b.underflow
174 """Return the difference of self and b: x.__sub__(y) <==> x - y""" 176 for i
in range(len(self)):
178 c.yerr[0][i] -= b.yerr[0][i]
179 c.yerr[1][i] -= b.yerr[1][i]
180 c.overflow -= b.overflow
181 c.underflow -= b.underflow
184 return self.
divide(denominator)
186 """Return contents of indexth bin: x.__getitem__(y) <==> x[y]""" 189 """Set contents of indexth bin: x.__setitem__(i, y) <==> x[i]=y""" 190 self.
y[index] = value
192 """Return the number of bins: x.__len__() <==> len(x)""" 195 """Iterate through bins: x.__iter__() <==> iter(x)""" 197 def min(self, threshold=None):
198 """Return the y-value of the bottom tip of the lowest errorbar.""" 199 vals = [(yval - yerr)
for yval, yerr
in zip(self.
y, self.
yerr[0])
200 if (yval - yerr) > threshold]
206 """Return average between the upper and lower xerr.""" 207 return [(self.
xerr[0][i] + self.
xerr[1][i]) / 2
208 for i
in range(self.
nbins)]
210 """Return average between the upper and lower yerr.""" 211 return [(self.
yerr[0][i] + self.
yerr[1][i]) / 2
212 for i
in range(self.
nbins)]
215 Scale contents, errors, and over/underflow by the given scale factor. 217 self.
y = [x * factor
for x
in self.
y]
218 self.
yerr[0] = [x * factor
for x
in self.
yerr[0]]
219 self.
yerr[1] = [x * factor
for x
in self.
yerr[1]]
224 Delete a the contents of a bin, sliding all the other data one bin to 225 the left. This can be useful for histograms with labeled bins. 232 self.
xerr[0].pop(index)
233 self.
xerr[1].pop(index)
234 self.
yerr[0].pop(index)
235 self.
yerr[1].pop(index)
237 self.binlabels.pop(index)
239 """Return a ROOT.TH1F object with contents of this Hist""" 240 th1f = ROOT.TH1F(name
or self.
name,
"", self.
nbins,
241 array.array(
'f', self.
xedges))
244 for i
in range(self.
nbins):
245 th1f.SetBinContent(i + 1, self.
y[i])
247 th1f.SetBinError(i + 1, (self.
yerr[0][i] + self.
yerr[1][i]) / 2)
249 th1f.SetBinError(i + 1, self.
yerr[i])
251 th1f.GetXaxis().SetBinLabel(i + 1, self.
binlabels[i])
257 """Return a ROOT.TGraphAsymmErrors object with contents of this Hist""" 258 x = array.array(
'f', self.
x)
259 y = array.array(
'f', self.
y)
260 xl = array.array(
'f', self.
xerr[0])
261 xh = array.array(
'f', self.
xerr[1])
262 yl = array.array(
'f', self.
yerr[0])
263 yh = array.array(
'f', self.
yerr[1])
264 tgraph = ROOT.TGraphAsymmErrors(self.
nbins, x, y, xl, xh, yl, yh)
265 tgraph.SetName(name
or self.
name)
270 Return the simple quotient with errors added in quadrature. 272 This function is called by the division operator: 273 hist3 = hist1.divide_wilson(hist2) <--> hist3 = hist1 / hist2 275 if len(self) != len(denominator):
276 raise TypeError(
"Cannot divide %s with %i bins by " 278 (denominator.name, len(denominator),
279 self.
name, len(self)))
280 quotient = copy.deepcopy(self)
282 den_yerr = denominator.av_yerr()
283 quotient.yerr = [0.
for i
in range(self.
nbins)]
284 for i
in range(self.
nbins):
285 if denominator[i] == 0
or self[i] == 0:
288 quotient.y[i] = self[i] / denominator[i]
289 quotient.yerr[i] = quotient[i]
290 quotient.yerr[i] *= math.sqrt((num_yerr[i] / self[i]) ** 2 +
291 (den_yerr[i] / denominator[i]) ** 2)
292 if quotient.yerr[i] > quotient[i]:
293 quotient.yerr[i] = quotient[i]
294 quotient.yerr = [quotient.yerr, quotient.yerr]
297 """Return an efficiency plot with Wilson score interval errors.""" 298 if len(self) != len(denominator):
299 raise TypeError(
"Cannot divide %s with %i bins by " 301 (denominator.name, len(denominator),
302 self.
name, len(self)))
304 quotient = copy.deepcopy(self)
306 quotient.yerr = [lower_err, upper_err]
311 A container to hold Hist objects for plotting together. 313 When plotting, the title and the x and y labels of the last Hist added 314 will be used unless specified otherwise in the constructor. 316 def __init__(self, hists=None, title=None, xlabel=None, ylabel=None):
326 """Return indexth hist: x.__getitem__(y) <==> x[y]""" 327 return self.
hists[index]
329 """Replace indexth hist with value: x.__setitem__(i, y) <==> x[i]=y""" 330 self.
hists[index] = value
332 """Return the number of hists in the stack: x.__len__() <==> len(x)""" 333 return len(self.
hists)
335 """Iterate through hists in the stack: x.__iter__() <==> iter(x)""" 336 return iter(self.
hists)
338 """Return the value of the highest bin of all hists in the stack.""" 339 maxes = [
max(x)
for x
in self.
hists]
345 """Return the value of the highest bin in the addition of all hists.""" 347 return max([sum([h[i]
for h
in self.
hists])
348 for i
in range(self.
hists[0].nbins)])
352 """Scale all Hists by factor.""" 353 for hist
in self.
hists:
355 def min(self, threshold=None):
357 Return the value of the lowest bin of all hists in the stack. 359 If threshold is specified, only values above the threshold will be 362 mins = [x.min(threshold)
for x
in self.
hists]
364 def add(self, hist, **kwargs):
366 Add a Hist object to this stack. 368 Any additional keyword arguments will be added to just this Hist 369 when the stack is plotted. 371 if "label" in kwargs:
372 hist.label = kwargs[
'label']
375 if hist.xedges != self.
hists[0].xedges:
376 raise ValueError(
"Cannot add %s to stack; all Hists must " 377 "have the same binning." % hist.name)
378 self.hists.append(hist)
379 self.kwargs.append(kwargs)
385 """A wrapper for TFiles, allowing easier access to methods.""" 388 self.
name = name
or filename[:-5]
389 self.
file = ROOT.TFile(filename,
'read')
390 if self.file.IsZombie():
391 raise ValueError(
"Error opening %s" % filename)
392 def cd(self, directory=''):
393 """Make directory the current directory.""" 394 self.file.cd(directory)
395 def get(self, object_name, path=None, type1D=Hist, type2D=Hist2D):
396 """Return a Hist object from the given path within this file.""" 398 path = os.path.dirname(object_name)
399 object_name = os.path.basename(object_name)
401 roothist = self.file.GetDirectory(path).Get(object_name)
402 except ReferenceError
as e:
403 raise ReferenceError(e)
405 return type2D(roothist)
407 return type1D(roothist)
409 def ls(directory=None):
410 """Return a python list of ROOT object names from the given directory.""" 411 if directory ==
None:
412 keys = ROOT.gDirectory.GetListOfKeys()
414 keys = ROOT.gDirectory.GetDirectory(directory).GetListOfKeys()
419 key = keys.After(key)
420 names.append(obj.GetName())
424 """Return ROOT's present working directory.""" 425 return ROOT.gDirectory.GetPath()
428 """Return a Hist object with the given name.""" 429 return Hist(ROOT.gDirectory.Get(object_name))
437 saved_argv = sys.argv[:]
438 argstring =
' '.
join(sys.argv)
439 sys.argv = [sys.argv[0]]
444 The program was unable to access PyROOT. Usually, this just requires switching 445 to the same major version of python that used when compiling ROOT. To 446 determine which version that is, try the following command: 447 root -config 2>&1 | tr ' ' '\\n' | egrep 'python|PYTHON' 448 If this is different from the python version you are currently using, try 449 changing your PATH to point to the new one.""")
454 ROOT.gROOT.SetBatch()
455 ROOT.gErrorIgnoreLevel = ROOT.kWarning
457 if os.path.exists(
'rootlogon.C'):
458 ROOT.gROOT.Macro(
'rootlogon.C')
459 sys.argv = saved_argv[:]
464 Modify a string based on a list of patterns and substitutions. 466 replacements should be a list of two-entry tuples, the first entry giving 467 a string to search for and the second entry giving the string with which 468 to replace it. If replacements includes a pattern entry containing 469 'use_regexp', then all patterns will be treated as regular expressions 474 if 'use_regexp' in [x
for x,y
in replacements]:
475 for pattern, repl
in [x
for x
in replacements
476 if x[0] !=
'use_regexp']:
477 string = re.sub(pattern, repl, string)
479 for pattern, repl
in replacements:
480 string = string.replace(pattern, repl)
481 if re.match(_all_whitespace_string, string):
487 for binlabel
in binlabels:
496 eff, upper_err, lower_err = [], [], []
497 for n, d
in zip(numerator_array, denominator_array):
500 s = math.sqrt(p * (1 - p) / d + 1 / (4 * d * d))
503 upper_err.append(-p + 1/(1 + 1/d) * (t + s))
504 lower_err.append(+p - 1/(1 + 1/d) * (t - s))
505 except ZeroDivisionError:
509 return eff, upper_err, lower_err
514 num_processors = os.sysconf(
'SC_NPROCESSORS_ONLN')
517 num_processors = os.environ[
'NUMBER_OF_PROCESSORS']
520 return num_processors
523 outfile = ROOT.TFile(
"test.root",
"recreate")
525 d = outfile.mkdir(
"dir%i" % (i + 1))
528 hist = ROOT.TH1F(
"hist%i" % (j + 1),
"A Histogram", 10, 0, 10)
536 glob_magic_check = re.compile(
'[*?[]')
539 return glob_magic_check.search(s)
is not None 547 if not tdirectory.GetDirectory(dirname):
549 names = [key.GetName()
for key
in 550 tdirectory.GetDirectory(dirname).GetListOfKeys()]
551 return fnmatch.filter(names, pattern)
554 if tdirectory.Get(os.path.join(dirname, basename)):
559 """Return a list of paths matching a pathname pattern. 561 The pattern may contain simple shell-style wildcards a la fnmatch. 563 >>> import rootplot.utilities 564 >>> f = rootplot.utilities.testfile() 566 ['dir1', 'dir2', 'dir3', 'dir4'] 567 >>> rootglob(f, 'dir1/*') 568 ['dir1/hist1', 'dir1/hist2', 'dir1/hist3', 'dir1/hist4'] 569 >>> rootglob(f, '*/hist1') 570 ['dir1/hist1', 'dir2/hist1', 'dir3/hist1', 'dir4/hist1'] 571 >>> rootglob(f, 'dir1/hist[1-2]') 572 ['dir1/hist1', 'dir1/hist2'] 577 """Return an iterator which yields the paths matching a pathname pattern. 579 The pattern may contain simple shell-style wildcards a la fnmatch. 583 if tdirectory.Get(pathname):
586 dirname, basename = os.path.split(pathname)
592 glob_in_dir = _rootglob1
594 glob_in_dir = _rootglob0
596 for name
in glob_in_dir(tdirectory, dirname, basename):
597 yield os.path.join(dirname, name)
599 if __name__ ==
'__main__':
def __init__(self, hists=None, title=None, xlabel=None, ylabel=None)
def TH1F(self, name=None)
def loadROOT(batch=True)
Define additional helping functions.
def __init_TH1(self, hist)
def process_bin_labels(binlabels)
def _rootglob0(tdirectory, dirname, basename)
def min(self, threshold=None)
def divide(self, denominator)
def delete_bin(self, index)
def __init__(self, hist, label="__nolabel__", name=None, title=None, xlabel=None, ylabel=None)
S & print(S &os, JobReport::InputFile const &f)
def replace(string, replacements)
def __init__(self, filename, name=None)
def __getitem__(self, index)
def irootglob(tdirectory, pathname)
def __getitem__(self, index)
def __getitem__(self, index)
OutputIterator zip(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result, Compare comp)
def min(self, threshold=None)
def __setitem__(self, index, value)
def _rootglob1(tdirectory, dirname, pattern)
def find_num_processors()
def wilson_interval(numerator_array, denominator_array)
static std::string join(char **cmd)
def __div__(self, denominator)
def add(self, hist, kwargs)
def __setitem__(self, index, value)
def TGraph(self, name=None)
def rootglob(tdirectory, pathname)
def __init__(self, hist, label="__nolabel__", title=None, xlabel=None, ylabel=None)
def __init_TGraph(self, hist)
def cd(self, directory='')
def get(self, object_name, path=None, type1D=Hist, type2D=Hist2D)
How EventSelector::AcceptEvent() decides whether to accept an event for output otherwise it is excluding the probing of A single or multiple positive and the trigger will pass if any such matching triggers are PASS or EXCEPTION[A criterion thatmatches no triggers at all is detected and causes a throw.] A single negative with an expectation of appropriate bit checking in the decision and the trigger will pass if any such matching triggers are FAIL or EXCEPTION A wildcarded negative criterion that matches more than one trigger in the trigger list("!*","!HLTx*"if it matches 2 triggers or more) will accept the event if all the matching triggers are FAIL.It will reject the event if any of the triggers are PASS or EXCEPTION(this matches the behavior of"!*"before the partial wildcard feature was incorporated).Triggers which are in the READY state are completely ignored.(READY should never be returned since the trigger paths have been run
def divide_wilson(self, denominator)