CMS 3D CMS Logo

root2matplotlib.py
Go to the documentation of this file.
1 """
2 Utilities for plotting ROOT histograms in matplotlib.
3 """
4 
5 __license__ = '''\
6 Copyright (c) 2009-2010 Jeff Klukas <klukas@wisc.edu>
7 
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:
14 
15 The above copyright notice and this permission notice shall be included in
16 all copies or substantial portions of the Software.
17 
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
24 THE SOFTWARE.
25 '''
26 
27 ################ Import python libraries
28 
29 import math
30 import ROOT
31 import re
32 import copy
33 import array
34 from rootplot import utilities
35 import matplotlib as mpl
36 import matplotlib.pyplot as plt
37 import matplotlib.transforms as transforms
38 import numpy as np
39 
40 ################ Define constants
41 
42 _all_whitespace_string = re.compile(r'\s*$')
43 
44 
45 ################ Define classes
46 
48  """A container to hold the parameters from a 2D ROOT histogram."""
49  def __init__(self, *args, **kwargs):
50  self.replacements = None
51  if 'replacements' in kwargs:
52  self.replacements = kwargs.pop('replacements')
53  utilities.Hist2D.__init__(self, *args, **kwargs)
54  def contour(self, **kwargs):
55  """Draw a contour plot."""
56  cs = plt.contour(self.x, self.y, self.content, **kwargs)
57  plt.clabel(cs, inline=1, fontsize=10)
58  if self.binlabelsx is not None:
59  plt.xticks(np.arange(self.nbinsx), self.binlabelsx)
60  if self.binlabelsy is not None:
61  plt.yticks(np.arange(self.nbinsy), self.binlabelsy)
62  return cs
63  def col(self, **kwargs):
64  """Draw a colored box plot using :func:`matplotlib.pyplot.imshow`."""
65  if 'cmap' in kwargs:
66  kwargs['cmap'] = plt.get_cmap(kwargs['cmap'])
67  plot = plt.imshow(self.content, interpolation='nearest',
68  extent=[self.xedges[0], self.xedges[-1],
69  self.yedges[0], self.yedges[-1]],
70  aspect='auto', origin='lower', **kwargs)
71  return plot
72  def colz(self, **kwargs):
73  """
74  Draw a colored box plot with a colorbar using
75  :func:`matplotlib.pyplot.imshow`.
76  """
77  plot = self.col(**kwargs)
78  plt.colorbar(plot)
79  return plot
80  def box(self, maxsize=40, **kwargs):
81  """
82  Draw a box plot with size indicating content using
83  :func:`matplotlib.pyplot.scatter`.
84 
85  The data will be normalized, with the largest box using a marker of
86  size maxsize (in points).
87  """
88  x = np.hstack([self.x for i in range(self.nbinsy)])
89  y = np.hstack([[yval for i in range(self.nbinsx)] for yval in self.y])
90  maxvalue = np.max(self.content)
91  if maxvalue == 0:
92  maxvalue = 1
93  sizes = np.array(self.content).flatten() / maxvalue * maxsize
94  plot = plt.scatter(x, y, sizes, marker='s', **kwargs)
95  return plot
96  def TH2F(self, name=""):
97  """Return a ROOT.TH2F object with contents of this Hist2D."""
98  th2f = ROOT.TH2F(name, "",
99  self.nbinsx, array.array('f', self.xedges),
100  self.nbinsy, array.array('f', self.yedges))
101  th2f.SetTitle("%s;%s;%s" % (self.title, self.xlabel, self.ylabel))
102  for ix in range(self.nbinsx):
103  for iy in range(self.nbinsy):
104  th2f.SetBinContent(ix + 1, iy + 1, self.content[iy][ix])
105  return th2f
106 
108  """A container to hold the parameters from a ROOT histogram."""
109  def __init__(self, *args, **kwargs):
110  self.replacements = None
111  if 'replacements' in kwargs:
112  self.replacements = kwargs.pop('replacements')
113  utilities.Hist.__init__(self, *args, **kwargs)
114  def _prepare_xaxis(self, rotation=0, alignment='center'):
115  """Apply bounds and text labels on x axis."""
116  if self.binlabels is not None:
117  binwidth = (self.xedges[-1] - self.xedges[0]) / self.nbins
118  plt.xticks(self.x, self.binlabels,
119  rotation=rotation, ha=alignment)
120  plt.xlim(self.xedges[0], self.xedges[-1])
121 
122  def _prepare_yaxis(self, rotation=0, alignment='center'):
123  """Apply bounds and text labels on y axis."""
124  if self.binlabels is not None:
125  binwidth = (self.xedges[-1] - self.xedges[0]) / self.nbins
126  plt.yticks(self.x, self.binlabels,
127  rotation=rotation, va=alignment)
128  plt.ylim(self.xedges[0], self.xedges[-1])
129 
130  def show_titles(self, **kwargs):
131  """Print the title and axis labels to the current figure."""
132  replacements = kwargs.get('replacements', None) or self.replacements
133  plt.title(replace(self.title, replacements))
134  plt.xlabel(replace(self.xlabel, replacements))
135  plt.ylabel(replace(self.ylabel, replacements))
136  def hist(self, label_rotation=0, label_alignment='center', **kwargs):
137  """
138  Generate a matplotlib hist figure.
139 
140  All additional keyword arguments will be passed to
141  :func:`matplotlib.pyplot.hist`.
142  """
143  kwargs.pop('fmt', None)
144  replacements = kwargs.get('replacements', None) or self.replacements
145  weights = self.y
146  # Kludge to avoid mpl bug when plotting all zeros
147  if self.y == [0] * self.nbins:
148  weights = [1.e-10] * self.nbins
149  plot = plt.hist(self.x, weights=weights, bins=self.xedges,
150  label=replace(self.label, replacements), **kwargs)
151  self._prepare_xaxis(label_rotation, label_alignment)
152  return plot
153  def errorbar(self, xerr=False, yerr=False, label_rotation=0,
154  label_alignment='center', **kwargs):
155  """
156  Generate a matplotlib errorbar figure.
157 
158  All additional keyword arguments will be passed to
159  :func:`matplotlib.pyplot.errorbar`.
160  """
161  if xerr:
162  kwargs['xerr'] = self.xerr
163  if yerr:
164  kwargs['yerr'] = self.yerr
165  replacements = kwargs.get('replacements', None) or self.replacements
166  errorbar = plt.errorbar(self.x, self.y,
167  label=replace(self.label, replacements),
168  **kwargs)
169  self._prepare_xaxis(label_rotation, label_alignment)
170  return errorbar
171  def errorbarh(self, xerr=False, yerr=False, label_rotation=0,
172  label_alignment='center', **kwargs):
173  """
174  Generate a horizontal matplotlib errorbar figure.
175 
176  All additional keyword arguments will be passed to
177  :func:`matplotlib.pyplot.errorbar`.
178  """
179  if xerr: kwargs['xerr'] = self.yerr
180  if yerr: kwargs['yerr'] = self.xerr
181  replacements = kwargs.get('replacements', None) or self.replacements
182  errorbar = plt.errorbar(self.y, self.x,
183  label=replace(self.label, replacements),
184  **kwargs)
185  self._prepare_yaxis(label_rotation, label_alignment)
186  return errorbar
187  def bar(self, xerr=False, yerr=False, xoffset=0., width=0.8,
188  label_rotation=0, label_alignment='center', **kwargs):
189  """
190  Generate a matplotlib bar figure.
191 
192  All additional keyword arguments will be passed to
193  :func:`matplotlib.pyplot.bar`.
194  """
195  kwargs.pop('fmt', None)
196  if xerr: kwargs['xerr'] = self.av_xerr()
197  if yerr: kwargs['yerr'] = self.av_yerr()
198  replacements = kwargs.get('replacements', None) or self.replacements
199  ycontent = [self.xedges[i] + self.width[i] * xoffset
200  for i in range(len(self.xedges) - 1)]
201  width = [x * width for x in self.width]
202  bar = plt.bar(ycontent, self.y, width,
203  label=replace(self.label, replacements), **kwargs)
204  self._prepare_xaxis(label_rotation, label_alignment)
205  return bar
206  def barh(self, xerr=False, yerr=False, yoffset=0., width=0.8,
207  label_rotation=0, label_alignment='center', **kwargs):
208  """
209  Generate a horizontal matplotlib bar figure.
210 
211  All additional keyword arguments will be passed to
212  :func:`matplotlib.pyplot.bar`.
213  """
214  kwargs.pop('fmt', None)
215  if xerr: kwargs['xerr'] = self.av_yerr()
216  if yerr: kwargs['yerr'] = self.av_xerr()
217  replacements = kwargs.get('replacements', None) or self.replacements
218  xcontent = [self.xedges[i] + self.width[i] * yoffset
219  for i in range(len(self.xedges) - 1)]
220  width = [x * width for x in self.width]
221  barh = plt.barh(xcontent, self.y, width,
222  label=replace(self.label, replacements),
223  **kwargs)
224  self._prepare_yaxis(label_rotation, label_alignment)
225  return barh
226 
228  """
229  A container to hold Hist objects for plotting together.
230 
231  When plotting, the title and the x and y labels of the last Hist added
232  will be used unless specified otherwise in the constructor.
233  """
234  def __init__(self, *args, **kwargs):
235  if 'replacements' in kwargs:
236  self.replacements = kwargs.pop('replacements')
237  utilities.HistStack.__init__(self, *args, **kwargs)
238  def show_titles(self, **kwargs):
239  self.hists[-1].show_titles()
240  def hist(self, label_rotation=0, **kwargs):
241  """
242  Make a matplotlib hist plot.
243 
244  Any additional keyword arguments will be passed to
245  :func:`matplotlib.pyplot.hist`, which allows a vast array of
246  possibilities. Particlularly, the *histtype* values such as
247  ``'barstacked'`` and ``'stepfilled'`` give substantially different
248  results. You will probably want to include a transparency value
249  (i.e. *alpha* = 0.5).
250  """
251  contents = np.dstack([hist.y for hist in self.hists])
252  xedges = self.hists[0].xedges
253  x = np.dstack([hist.x for hist in self.hists])[0]
254  labels = [hist.label for hist in self.hists]
255  try:
256  clist = [item['color'] for item in self.kwargs]
257  plt.gca().set_color_cycle(clist)
258  ## kwargs['color'] = clist # For newer version of matplotlib
259  except:
260  pass
261  plot = plt.hist(x, weights=contents, bins=xedges,
262  label=labels, **kwargs)
263  def bar3d(self, **kwargs):
264  #### Not yet ready for primetime
265  from mpl_toolkits.mplot3d import Axes3D
266  fig = plt.figure()
267  ax = Axes3D(fig)
268  plots = []
269  labels = []
270  for i, hist in enumerate(self.hists):
271  if self.title is not None: hist.title = self.title
272  if self.xlabel is not None: hist.xlabel = self.xlabel
273  if self.ylabel is not None: hist.ylabel = self.ylabel
274  labels.append(hist.label)
275  all_kwargs = copy.copy(kwargs)
276  all_kwargs.update(self.kwargs[i])
277  bar = ax.bar(hist.x, hist.y, zs=i, zdir='y', width=hist.width,
278  **all_kwargs)
279  plots.append(bar)
280  from matplotlib.ticker import FixedLocator
281  locator = FixedLocator(range(len(labels)))
282  ax.w_yaxis.set_major_locator(locator)
283  ax.w_yaxis.set_ticklabels(labels)
284  ax.set_ylim3d(-1, len(labels))
285  return plots
286  def barstack(self, **kwargs):
287  """
288  Make a matplotlib bar plot, with each Hist stacked upon the last.
289 
290  Any additional keyword arguments will be passed to
291  :func:`matplotlib.pyplot.bar`.
292  """
293  bottom = None # if this is set to zeroes, it fails for log y
294  plots = []
295  for i, hist in enumerate(self.hists):
296  if self.title is not None: hist.title = self.title
297  if self.xlabel is not None: hist.xlabel = self.xlabel
298  if self.ylabel is not None: hist.ylabel = self.ylabel
299  all_kwargs = copy.copy(kwargs)
300  all_kwargs.update(self.kwargs[i])
301  bar = hist.bar(bottom=bottom, **all_kwargs)
302  plots.append(bar)
303  if not bottom: bottom = [0. for i in range(self.hists[0].nbins)]
304  bottom = [sum(pair) for pair in zip(bottom, hist.y)]
305  return plots
306  def histstack(self, **kwargs):
307  """
308  Make a matplotlib hist plot, with each Hist stacked upon the last.
309 
310  Any additional keyword arguments will be passed to
311  :func:`matplotlib.pyplot.hist`.
312  """
313  bottom = None # if this is set to zeroes, it fails for log y
314  plots = []
315  cumhist = None
316  for i, hist in enumerate(self.hists):
317  if cumhist:
318  cumhist = hist + cumhist
319  else:
320  cumhist = copy.copy(hist)
321  if self.title is not None: cumhist.title = self.title
322  if self.xlabel is not None: cumhist.xlabel = self.xlabel
323  if self.ylabel is not None: cumhist.ylabel = self.ylabel
324  all_kwargs = copy.copy(kwargs)
325  all_kwargs.update(self.kwargs[i])
326  zorder = 0 + float(len(self) - i)/len(self) # plot in reverse order
327  plot = cumhist.hist(zorder=zorder, **all_kwargs)
328  plots.append(plot)
329  return plots
330  def barcluster(self, width=0.8, **kwargs):
331  """
332  Make a clustered bar plot.
333 
334  Any additional keyword arguments will be passed to
335  :func:`matplotlib.pyplot.bar`.
336  """
337  plots = []
338  spacer = (1. - width) / 2
339  width = width / len(self.hists)
340  for i, hist in enumerate(self.hists):
341  if self.title is not None: hist.title = self.title
342  if self.xlabel is not None: hist.xlabel = self.xlabel
343  if self.ylabel is not None: hist.ylabel = self.ylabel
344  all_kwargs = copy.copy(kwargs)
345  all_kwargs.update(self.kwargs[i])
346  bar = hist.bar(xoffset=width*i + spacer, width=width, **all_kwargs)
347  plots.append(bar)
348  return plots
349  def barh(self, width=0.8, **kwargs):
350  """
351  Make a horizontal clustered matplotlib bar plot.
352 
353  Any additional keyword arguments will be passed to
354  :func:`matplotlib.pyplot.bar`.
355  """
356  plots = []
357  spacer = (1. - width) / 2
358  width = width / len(self.hists)
359  for i, hist in enumerate(self.hists):
360  if self.title is not None: hist.title = self.title
361  if self.xlabel is not None: hist.ylabel = self.xlabel
362  if self.ylabel is not None: hist.xlabel = self.ylabel
363  all_kwargs = copy.copy(kwargs)
364  all_kwargs.update(self.kwargs[i])
365  bar = hist.barh(yoffset=width*i + spacer, width=width, **all_kwargs)
366  plots.append(bar)
367  return plots
368  def bar(self, **kwargs):
369  """
370  Make a bar plot, with all Hists in the stack overlaid.
371 
372  Any additional keyword arguments will be passed to
373  :func:`matplotlib.pyplot.bar`. You will probably want to set a
374  transparency value (i.e. *alpha* = 0.5).
375  """
376  plots = []
377  for i, hist in enumerate(self.hists):
378  if self.title is not None: hist.title = self.title
379  if self.xlabel is not None: hist.xlabel = self.xlabel
380  if self.ylabel is not None: hist.ylabel = self.ylabel
381  all_kwargs = copy.copy(kwargs)
382  all_kwargs.update(self.kwargs[i])
383  bar = hist.bar(**all_kwargs)
384  plots.append(bar)
385  return plots
386  def errorbar(self, offset=False, **kwargs):
387  """
388  Make a matplotlib errorbar plot, with all Hists in the stack overlaid.
389 
390  Passing 'offset=True' will slightly offset each dataset so overlapping
391  errorbars are still visible. Any additional keyword arguments will
392  be passed to :func:`matplotlib.pyplot.errorbar`.
393  """
394  plots = []
395  for i, hist in enumerate(self.hists):
396  if self.title is not None: hist.title = self.title
397  if self.xlabel is not None: hist.xlabel = self.xlabel
398  if self.ylabel is not None: hist.ylabel = self.ylabel
399  all_kwargs = copy.copy(kwargs)
400  all_kwargs.update(self.kwargs[i])
401  transform = plt.gca().transData
402  if offset:
403  index_offset = (len(self.hists) - 1)/2.
404  pixel_offset = 1./72 * (i - index_offset)
405  transform = transforms.ScaledTranslation(
406  pixel_offset, 0, plt.gcf().dpi_scale_trans)
407  transform = plt.gca().transData + transform
408  errorbar = hist.errorbar(transform=transform, **all_kwargs)
409  plots.append(errorbar)
410  return plots
411  def errorbarh(self, **kwargs):
412  """
413  Make a horizontal matplotlib errorbar plot, with all Hists in the
414  stack overlaid.
415 
416  Any additional keyword arguments will be passed to
417  :func:`matplotlib.pyplot.errorbar`.
418  """
419  plots = []
420  for i, hist in enumerate(self.hists):
421  if self.title is not None: hist.title = self.title
422  if self.xlabel is not None: hist.ylabel = self.xlabel
423  if self.ylabel is not None: hist.xlabel = self.ylabel
424  all_kwargs = copy.copy(kwargs)
425  all_kwargs.update(self.kwargs[i])
426  errorbar = hist.errorbarh(**all_kwargs)
427  plots.append(errorbar)
428  return plots
429 
430 ################ Define functions and classes for navigating within ROOT
431 
433  """A wrapper for TFiles, allowing easier access to methods."""
434  def get(self, object_name, path=None):
435  try:
436  return utilities.RootFile.get(self, object_name, path,
437  Hist, Hist2D)
438  except ReferenceError as e:
439  raise ReferenceError(e)
440 
441 ################ Define additional helping functions
442 
443 def replace(string, replacements):
444  """
445  Modify a string based on a list of patterns and substitutions.
446 
447  replacements should be a list of two-entry tuples, the first entry giving
448  a string to search for and the second entry giving the string with which
449  to replace it. If replacements includes a pattern entry containing
450  'use_regexp', then all patterns will be treated as regular expressions
451  using re.sub.
452  """
453  if not replacements:
454  return string
455  if 'use_regexp' in [x for x,y in replacements]:
456  for pattern, repl in [x for x in replacements
457  if x[0] != 'use_regexp']:
458  string = re.sub(pattern, repl, string)
459  else:
460  for pattern, repl in replacements:
461  string = string.replace(pattern, repl)
462  if re.match(_all_whitespace_string, string):
463  return ""
464  return string
465 
def errorbar(self, offset=False, kwargs)
def box(self, maxsize=40, kwargs)
def replace(string, replacements)
def hist(self, label_rotation=0, kwargs)
def _prepare_yaxis(self, rotation=0, alignment='center')
def _prepare_xaxis(self, rotation=0, alignment='center')
def hist(self, label_rotation=0, label_alignment='center', kwargs)
def get(self, object_name, path=None)
OutputIterator zip(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result, Compare comp)
def flatten(obj)
Definition: CommonUtil.py:4
def errorbar(self, xerr=False, yerr=False, label_rotation=0, label_alignment='center', kwargs)
def barh(self, xerr=False, yerr=False, yoffset=0., width=0.8, label_rotation=0, label_alignment='center', kwargs)
def bar(self, xerr=False, yerr=False, xoffset=0., width=0.8, label_rotation=0, label_alignment='center', kwargs)
def errorbarh(self, xerr=False, yerr=False, label_rotation=0, label_alignment='center', kwargs)
def barcluster(self, width=0.8, kwargs)