2 Utilities for rootplot including histogram classes.
6 Copyright (c) 2009-2010 Jeff Klukas <klukas@wisc.edu>
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
15 The above copyright notice and this permission notice shall be included in
16 all copies or substantial portions of the Software.
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
37 from random
import gauss
42 """A container to hold the parameters from a 2D ROOT histogram."""
43 def __init__(self, hist, label="__nolabel__", title=None,
44 xlabel=
None, ylabel=
None):
46 if not hist.InheritsFrom(
"TH2"):
47 raise TypeError(
"%s does not inherit from TH2" % hist)
49 raise TypeError(
"%s is not a ROOT object" % hist)
55 for i
in range(1, nx + 1)])
57 self.
nbinsx = nx = self.binlabelsx.index(
'')
60 for i
in range(1, ny + 1)])
62 self.
nbinsy = ny = self.binlabelsy.index(
'')
65 self.
content = [[hist.GetBinContent(i, j)
for i
in range(1, nx + 1)]
66 for j
in range(1, ny + 1)]
67 self.
xedges = [hist.GetXaxis().GetBinLowEdge(i)
68 for i
in range(1, nx + 2)]
69 self.
yedges = [hist.GetYaxis().GetBinLowEdge(i)
70 for i
in range(1, ny + 2)]
75 self.
title = title
or hist.GetTitle()
76 self.
xlabel = xlabel
or hist.GetXaxis().GetTitle()
77 self.
ylabel = ylabel
or hist.GetYaxis().GetTitle()
85 """Return contents of indexth bin: x.__getitem__(y) <==> x[y]"""
88 """Return the number of bins: x.__len__() <==> len(x)"""
91 """Iterate through bins: x.__iter__() <==> iter(x)"""
94 """Return a ROOT.TH2F object with contents of this Hist2D."""
95 th2f = ROOT.TH2F(name,
"",
99 for ix
in range(self.
nbinsx):
100 for iy
in range(self.
nbinsy):
101 th2f.SetBinContent(ix + 1, iy + 1, self.
content[iy][ix])
105 """A container to hold the parameters from a ROOT histogram."""
106 def __init__(self, hist, label="__nolabel__",
107 name=
None, title=
None, xlabel=
None, ylabel=
None):
111 except AttributeError:
115 except AttributeError:
116 raise TypeError(
"%s is not a 1D histogram or TGraph" % hist)
118 self.
name = name
or hist.GetName()
120 self.
xlabel = xlabel
or hist.GetXaxis().GetTitle()
121 self.
ylabel = ylabel
or hist.GetYaxis().GetTitle()
126 for i
in range(1, n + 1)])
129 self.
nbins = n = self.binlabels.index(
'')
132 self.
xedges = [hist.GetBinLowEdge(i)
for i
in range(1, n + 2)]
137 self.
y = [hist.GetBinContent(i)
for i
in range(1, n + 1)]
138 self.
yerr = [hist.GetBinError( i)
for i
in range(1, n + 1)]
143 self.
nbins = n = hist.GetN()
144 self.
x, self.
y = [], []
145 x, y = ROOT.Double(0), ROOT.Double(0)
147 hist.GetPoint(i, x, y)
148 self.x.append(copy.copy(x))
149 self.y.append(copy.copy(y))
150 lower = [
max(0, hist.GetErrorXlow(i))
for i
in xrange(n)]
151 upper = [
max(0, hist.GetErrorXhigh(i))
for i
in xrange(n)]
152 self.
xerr = [lower[:], upper[:]]
153 lower = [
max(0, hist.GetErrorYlow(i))
for i
in xrange(n)]
154 upper = [
max(0, hist.GetErrorYhigh(i))
for i
in xrange(n)]
155 self.
yerr = [lower[:], upper[:]]
156 self.
xedges = [self.
x[i] - self.
xerr[0][i]
for i
in xrange(n)]
157 self.xedges.append(self.
x[n - 1] + self.
xerr[1][n - 1])
163 """Return the sum of self and b: x.__add__(y) <==> x + y"""
165 for i
in range(len(self)):
167 c.yerr[0][i] += b.yerr[0][i]
168 c.yerr[1][i] += b.yerr[1][i]
169 c.overflow += b.overflow
170 c.underflow += b.underflow
173 """Return the difference of self and b: x.__sub__(y) <==> x - y"""
175 for i
in range(len(self)):
177 c.yerr[0][i] -= b.yerr[0][i]
178 c.yerr[1][i] -= b.yerr[1][i]
179 c.overflow -= b.overflow
180 c.underflow -= b.underflow
183 return self.
divide(denominator)
185 """Return contents of indexth bin: x.__getitem__(y) <==> x[y]"""
188 """Set contents of indexth bin: x.__setitem__(i, y) <==> x[i]=y"""
189 self.
y[index] = value
191 """Return the number of bins: x.__len__() <==> len(x)"""
194 """Iterate through bins: x.__iter__() <==> iter(x)"""
196 def min(self, threshold=None):
197 """Return the y-value of the bottom tip of the lowest errorbar."""
198 vals = [(yval - yerr)
for yval, yerr
in zip(self.
y, self.
yerr[0])
199 if (yval - yerr) > threshold]
205 """Return average between the upper and lower xerr."""
206 return [(self.
xerr[0][i] + self.
xerr[1][i]) / 2
207 for i
in range(self.
nbins)]
209 """Return average between the upper and lower yerr."""
210 return [(self.
yerr[0][i] + self.
yerr[1][i]) / 2
211 for i
in range(self.
nbins)]
214 Scale contents, errors, and over/underflow by the given scale factor.
216 self.
y = [x * factor
for x
in self.
y]
217 self.
yerr[0] = [x * factor
for x
in self.
yerr[0]]
218 self.
yerr[1] = [x * factor
for x
in self.
yerr[1]]
223 Delete a the contents of a bin, sliding all the other data one bin to
224 the left. This can be useful for histograms with labeled bins.
236 self.binlabels.pop(index)
238 """Return a ROOT.TH1F object with contents of this Hist"""
239 th1f = ROOT.TH1F(name
or self.
name,
"", self.
nbins,
240 array.array(
'f', self.
xedges))
243 for i
in range(self.
nbins):
244 th1f.SetBinContent(i + 1, self.
y[i])
246 th1f.SetBinError(i + 1, (self.
yerr[0][i] + self.
yerr[1][i]) / 2)
248 th1f.SetBinError(i + 1, self.
yerr[i])
250 th1f.GetXaxis().SetBinLabel(i + 1, self.
binlabels[i])
256 """Return a ROOT.TGraphAsymmErrors object with contents of this Hist"""
257 x = array.array(
'f', self.
x)
258 y = array.array(
'f', self.
y)
259 xl = array.array(
'f', self.
xerr[0])
260 xh = array.array(
'f', self.
xerr[1])
261 yl = array.array(
'f', self.
yerr[0])
262 yh = array.array(
'f', self.
yerr[1])
263 tgraph = ROOT.TGraphAsymmErrors(self.
nbins, x, y, xl, xh, yl, yh)
264 tgraph.SetName(name
or self.
name)
269 Return the simple quotient with errors added in quadrature.
271 This function is called by the division operator:
272 hist3 = hist1.divide_wilson(hist2) <--> hist3 = hist1 / hist2
274 if len(self) != len(denominator):
275 raise TypeError(
"Cannot divide %s with %i bins by "
277 (denominator.name, len(denominator),
278 self.
name, len(self)))
279 quotient = copy.deepcopy(self)
281 den_yerr = denominator.av_yerr()
282 quotient.yerr = [0.
for i
in range(self.
nbins)]
283 for i
in range(self.
nbins):
284 if denominator[i] == 0
or self[i] == 0:
287 quotient.y[i] = self[i] / denominator[i]
288 quotient.yerr[i] = quotient[i]
289 quotient.yerr[i] *= math.sqrt((num_yerr[i] / self[i]) ** 2 +
290 (den_yerr[i] / denominator[i]) ** 2)
291 if quotient.yerr[i] > quotient[i]:
292 quotient.yerr[i] = quotient[i]
293 quotient.yerr = [quotient.yerr, quotient.yerr]
296 """Return an efficiency plot with Wilson score interval errors."""
297 if len(self) != len(denominator):
298 raise TypeError(
"Cannot divide %s with %i bins by "
300 (denominator.name, len(denominator),
301 self.
name, len(self)))
303 quotient = copy.deepcopy(self)
305 quotient.yerr = [lower_err, upper_err]
310 A container to hold Hist objects for plotting together.
312 When plotting, the title and the x and y labels of the last Hist added
313 will be used unless specified otherwise in the constructor.
315 def __init__(self, hists=None, title=None, xlabel=None, ylabel=None):
325 """Return indexth hist: x.__getitem__(y) <==> x[y]"""
326 return self.
hists[index]
328 """Replace indexth hist with value: x.__setitem__(i, y) <==> x[i]=y"""
329 self.
hists[index] = value
331 """Return the number of hists in the stack: x.__len__() <==> len(x)"""
332 return len(self.
hists)
334 """Iterate through hists in the stack: x.__iter__() <==> iter(x)"""
335 return iter(self.
hists)
337 """Return the value of the highest bin of all hists in the stack."""
338 maxes = [
max(x)
for x
in self.
hists]
344 """Return the value of the highest bin in the addition of all hists."""
346 return max([sum([h[i]
for h
in self.
hists])
347 for i
in range(self.
hists[0].nbins)])
349 print [h.nbins
for h
in self.
hists]
351 """Scale all Hists by factor."""
352 for hist
in self.
hists:
354 def min(self, threshold=None):
356 Return the value of the lowest bin of all hists in the stack.
358 If threshold is specified, only values above the threshold will be
361 mins = [x.min(threshold)
for x
in self.
hists]
363 def add(self, hist, **kwargs):
365 Add a Hist object to this stack.
367 Any additional keyword arguments will be added to just this Hist
368 when the stack is plotted.
370 if "label" in kwargs:
371 hist.label = kwargs[
'label']
374 if hist.xedges != self.
hists[0].xedges:
375 raise ValueError(
"Cannot add %s to stack; all Hists must "
376 "have the same binning." % hist.name)
377 self.hists.append(hist)
378 self.kwargs.append(kwargs)
384 """A wrapper for TFiles, allowing easier access to methods."""
387 self.
name = name
or filename[:-5]
388 self.
file = ROOT.TFile(filename,
'read')
389 if self.file.IsZombie():
390 raise ValueError(
"Error opening %s" % filename)
391 def cd(self, directory=''):
392 """Make directory the current directory."""
393 self.file.cd(directory)
394 def get(self, object_name, path=None, type1D=Hist, type2D=Hist2D):
395 """Return a Hist object from the given path within this file."""
397 path = os.path.dirname(object_name)
398 object_name = os.path.basename(object_name)
400 roothist = self.file.GetDirectory(path).Get(object_name)
401 except ReferenceError
as e:
402 raise ReferenceError(e)
404 return type2D(roothist)
406 return type1D(roothist)
408 def ls(directory=None):
409 """Return a python list of ROOT object names from the given directory."""
410 if directory ==
None:
411 keys = ROOT.gDirectory.GetListOfKeys()
413 keys = ROOT.gDirectory.GetDirectory(directory).GetListOfKeys()
418 key = keys.After(key)
419 names.append(obj.GetName())
423 """Return ROOT's present working directory."""
424 return ROOT.gDirectory.GetPath()
427 """Return a Hist object with the given name."""
428 return Hist(ROOT.gDirectory.Get(object_name))
436 saved_argv = sys.argv[:]
437 argstring =
' '.
join(sys.argv)
438 sys.argv = [sys.argv[0]]
443 The program was unable to access PyROOT. Usually, this just requires switching
444 to the same major version of python that used when compiling ROOT. To
445 determine which version that is, try the following command:
446 root -config 2>&1 | tr ' ' '\\n' | egrep 'python|PYTHON'
447 If this is different from the python version you are currently using, try
448 changing your PATH to point to the new one."""
453 ROOT.gROOT.SetBatch()
454 ROOT.gErrorIgnoreLevel = ROOT.kWarning
456 if os.path.exists(
'rootlogon.C'):
457 ROOT.gROOT.Macro(
'rootlogon.C')
458 sys.argv = saved_argv[:]
463 Modify a string based on a list of patterns and substitutions.
465 replacements should be a list of two-entry tuples, the first entry giving
466 a string to search for and the second entry giving the string with which
467 to replace it. If replacements includes a pattern entry containing
468 'use_regexp', then all patterns will be treated as regular expressions
473 if 'use_regexp' in [x
for x,y
in replacements]:
474 for pattern, repl
in [x
for x
in replacements
475 if x[0] !=
'use_regexp']:
476 string = re.sub(pattern, repl, string)
478 for pattern, repl
in replacements:
479 string = string.replace(pattern, repl)
480 if re.match(_all_whitespace_string, string):
486 for binlabel
in binlabels:
495 eff, upper_err, lower_err = [], [], []
496 for n, d
in zip(numerator_array, denominator_array):
499 s = math.sqrt(p * (1 - p) / d + 1 / (4 * d * d))
502 upper_err.append(-p + 1/(1 + 1/d) * (t + s))
503 lower_err.append(+p - 1/(1 + 1/d) * (t - s))
504 except ZeroDivisionError:
508 return eff, upper_err, lower_err
513 num_processors = os.sysconf(
'SC_NPROCESSORS_ONLN')
516 num_processors = os.environ[
'NUMBER_OF_PROCESSORS']
519 return num_processors
522 outfile = ROOT.TFile(
"test.root",
"recreate")
524 d = outfile.mkdir(
"dir%i" % (i + 1))
527 hist = ROOT.TH1F(
"hist%i" % (j + 1),
"A Histogram", 10, 0, 10)
535 glob_magic_check = re.compile(
'[*?[]')
538 return glob_magic_check.search(s)
is not None
546 if not tdirectory.GetDirectory(dirname):
548 names = [key.GetName()
for key
in
549 tdirectory.GetDirectory(dirname).GetListOfKeys()]
550 return fnmatch.filter(names, pattern)
553 if tdirectory.Get(os.path.join(dirname, basename)):
558 """Return a list of paths matching a pathname pattern.
560 The pattern may contain simple shell-style wildcards a la fnmatch.
562 >>> import rootplot.utilities
563 >>> f = rootplot.utilities.testfile()
565 ['dir1', 'dir2', 'dir3', 'dir4']
566 >>> rootglob(f, 'dir1/*')
567 ['dir1/hist1', 'dir1/hist2', 'dir1/hist3', 'dir1/hist4']
568 >>> rootglob(f, '*/hist1')
569 ['dir1/hist1', 'dir2/hist1', 'dir3/hist1', 'dir4/hist1']
570 >>> rootglob(f, 'dir1/hist[1-2]')
571 ['dir1/hist1', 'dir1/hist2']
576 """Return an iterator which yields the paths matching a pathname pattern.
578 The pattern may contain simple shell-style wildcards a la fnmatch.
582 if tdirectory.Get(pathname):
585 dirname, basename = os.path.split(pathname)
591 glob_in_dir = _rootglob1
593 glob_in_dir = _rootglob0
595 for name
in glob_in_dir(tdirectory, dirname, basename):
596 yield os.path.join(dirname, name)
598 if __name__ ==
'__main__':
static void pop(std::vector< T > &vec, unsigned int index)
def loadROOT
Define additional helping functions.
static std::string join(char **cmd)
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