Repository: rootpy/rootpy

Issue:
Plotting Histstack with rootpy.plotting.root2matplotlib.hist causes IndexError
Dear all,

It seems that they've been some changes which broke my code (how many times did you hear that one before ;)). One of my example scripts:
https://github.com/BristolTopGroup/DailyPythonScripts/blob/master/examples/matplotlib_hist_example.py
is using rootpy.plotting.root2matplotlib.hist to plot a stack and encounters this problem:

``` python
File "matplotlib_hist_example.py", line 76, in <module>
    rplt.hist(stack, stacked=True, axes=axes, yerr=False)
  File "/storage/Workspace/Analysis/DailyPythonScripts/external/rootpy/rootpy/plotting/root2matplotlib.py", line 243, in hist
    proxy = _hist(high, bottom=low, axes=axes, logy=logy, **kwargs)
  File "/storage/Workspace/Analysis/DailyPythonScripts/external/rootpy/rootpy/plotting/root2matplotlib.py", line 290, in _hist
    zorder=zorder)
  File "/storage/Workspace/Analysis/DailyPythonScripts/external/rootpy/rootpy/plotting/root2matplotlib.py", line 692, in fill_between
    x.append(a_xedges[ibin + 1])
IndexError: list index out of range
```

A quick look at the rootpy example:
https://github.com/rootpy/rootpy/blob/master/examples/plotting/plot_matplotlib_hist.py
shows that 'rootpy.plotting.root2matplotlib.bar' is used instead of 'hist'. If I change it in the example it breaks with the same error.

Is this a bug, a problem with my set-up or a feature?



Discussion:

Resolving patch:
diff --git a/rootpy/plotting/hist.py b/rootpy/plotting/hist.py
--- a/rootpy/plotting/hist.py
+++ b/rootpy/plotting/hist.py
@@ -2444,7 +2444,7 @@ class HistStack(Plottable, NamedObject, QROOT.THStack):
 
     _ROOT = QROOT.THStack
 
-    def __init__(self, name=None, title=None, hists=None, **kwargs):
+    def __init__(self, hists=None, name=None, title=None, **kwargs):
 
         super(HistStack, self).__init__(name=name, title=title)
         self._post_init(hists=hists, **kwargs)
@@ -2452,7 +2452,6 @@ def __init__(self, name=None, title=None, hists=None, **kwargs):
     def _post_init(self, hists=None, **kwargs):
 
         super(HistStack, self)._post_init(**kwargs)
-
         self.hists = []
         self.dim = 1
         current_hists = super(HistStack, self).GetHists()
@@ -2465,9 +2464,7 @@ def _post_init(self, hists=None, **kwargs):
                     raise TypeError(
                         "Dimensions of the contained histograms are not equal")
                 self.hists.append(hist)
-
         self.sum = sum(self.hists) if self.hists else None
-
         if hists:
             for h in hists:
                 self.Add(h)
diff --git a/rootpy/plotting/root2matplotlib.py b/rootpy/plotting/root2matplotlib.py
--- a/rootpy/plotting/root2matplotlib.py
+++ b/rootpy/plotting/root2matplotlib.py
@@ -22,6 +22,7 @@
 ROOT.kTRUE
 
 from math import sqrt
+from itertools import izip
 import matplotlib.pyplot as plt
 import numpy as np
 
@@ -43,7 +44,6 @@
 
 
 def _set_defaults(h, kwargs, types=['common']):
-
     defaults = {}
     for key in types:
         if key == 'common':
@@ -90,20 +90,16 @@ def _set_bounds(h,
                 snap=True,
                 logx=None,
                 logy=None):
-
     if axes is None:
         axes = plt.gca()
-
     if prev_xlim is None:
         prev_xlim = plt.xlim()
     if prev_ylim is None:
         prev_ylim = plt.ylim()
-
     if logx is None:
         logx = axes.get_xscale() == 'log'
     if logy is None:
         logy = axes.get_yscale() == 'log'
-
     xmin, xmax, ymin, ymax = get_limits(
         h,
         xpadding=xpadding,
@@ -113,7 +109,6 @@ def _set_bounds(h,
         snap=snap,
         logx=logx,
         logy=logy)
-
     if was_empty:
         axes.set_xlim([xmin, xmax])
         axes.set_ylim([ymin, ymax])
@@ -123,7 +118,6 @@ def _set_bounds(h,
             axes.set_xlim([xmin, max(prev_xmax, xmax)])
         else:
             axes.set_xlim([min(prev_xmin, xmin), max(prev_xmax, xmax)])
-
         prev_ymin, prev_ymax = prev_ylim
         if logy and prev_ymin <= 0:
             axes.set_ylim([ymin, max(prev_ymax, ymax)])
@@ -132,12 +126,10 @@ def _set_bounds(h,
 
 
 def _get_highest_zorder(axes):
-
     return max([c.get_zorder() for c in axes.get_children()])
 
 
 def _maybe_reversed(x, reverse=False):
-
     if reverse:
         return reversed(x)
     return x
@@ -269,12 +261,10 @@ def hist(hists,
 
 
 def _hist(h, axes=None, bottom=None, logy=None, zorder=None, **kwargs):
-
     if axes is None:
         axes = plt.gca()
     if zorder is None:
         zorder = _get_highest_zorder(axes) + 1
-
     _set_defaults(h, kwargs, ['common', 'line', 'fill'])
     kwargs_proxy = kwargs.copy()
     fill = kwargs.pop('fill', False) or 'hatch' in kwargs
@@ -461,7 +451,6 @@ def bar(hists,
 
 
 def _bar(h, roffset=0., rwidth=1., xerr=None, yerr=None, axes=None, **kwargs):
-
     if axes is None:
         axes = plt.gca()
     if xerr:
@@ -575,10 +564,11 @@ def errorbar(hists,
     return returns
 
 
-def _errorbar(h, xerr, yerr, axes=None, emptybins=True, **kwargs):
-
+def _errorbar(h, xerr, yerr, axes=None, emptybins=True, zorder=None, **kwargs):
     if axes is None:
         axes = plt.gca()
+    if zorder is None:
+        zorder = _get_highest_zorder(axes) + 1
     _set_defaults(h, kwargs, ['common', 'errors', 'errorbar', 'marker'])
     if xerr:
         xerr = np.array([list(h.xerrl()), list(h.xerrh())])
@@ -594,7 +584,7 @@ def _errorbar(h, xerr, yerr, axes=None, emptybins=True, **kwargs):
             xerr = xerr[:, nonempty]
         if yerr is not False:
             yerr = yerr[:, nonempty]
-    return axes.errorbar(x, y, xerr=xerr, yerr=yerr, **kwargs)
+    return axes.errorbar(x, y, xerr=xerr, yerr=yerr, zorder=zorder, **kwargs)
 
 
 def step(h, logy=None, axes=None, **kwargs):
@@ -676,22 +666,16 @@ def fill_between(a, b, logy=None, axes=None, **kwargs):
     if not isinstance(a, _Hist) or not isinstance(b, _Hist):
         raise TypeError(
             "fill_between only operates on 1D histograms")
-    a_xedges = list(a.xedges())
-    b_xedges = list(b.xedges())
-    if a_xedges != b_xedges:
-        raise ValueError("histogram x edges are incompatible")
+    a.check_compatibility(b, check_edges=True)
     x = []
     top = []
     bottom = []
-    for ibin in xrange(1, a.nbins(0) + 1):
-        up = max(a.y(ibin), b.y(ibin))
-        dn = min(a.y(ibin), b.y(ibin))
-        x.append(a_xedges[ibin])
-        top.append(up)
-        bottom.append(dn)
-        x.append(a_xedges[ibin + 1])
-        top.append(up)
-        bottom.append(dn)
+    for abin, bbin in izip(a.bins(overflow=False), b.bins(overflow=False)):
+        up = max(abin.value, bbin.value)
+        dn = min(abin.value, bbin.value)
+        x.extend([abin.x.low, abin.x.high])
+        top.extend([up, up])
+        bottom.extend([dn, dn])
     x = np.array(x)
     top = np.array(top)
     bottom = np.array(bottom)
@@ -806,7 +790,6 @@ def contour(h, axes=None, zoom=None, **kwargs):
     x = np.array(list(h.x()))
     y = np.array(list(h.y()))
     z = np.array(h.z()).T
-
     if zoom is not None:
         from scipy import ndimage
         if hasattr(zoom, '__iter__'):
@@ -817,5 +800,4 @@ def contour(h, axes=None, zoom=None, **kwargs):
             x = ndimage.zoom(x, zoom)
             y = ndimage.zoom(y, zoom)
         z = ndimage.zoom(z, zoom)
-
     return axes.contour(x, y, z, **kwargs)


Test patch:
diff --git a/rootpy/plotting/tests/test_root2matplotlib.py b/rootpy/plotting/tests/test_root2matplotlib.py
--- a/rootpy/plotting/tests/test_root2matplotlib.py
+++ b/rootpy/plotting/tests/test_root2matplotlib.py
@@ -1,6 +1,6 @@
 # Copyright 2012 the rootpy developers
 # distributed under the terms of the GNU General Public License
-from rootpy.plotting import Hist, Hist2D, Graph
+from rootpy.plotting import Hist, Hist2D, HistStack, Graph
 from nose.plugins.skip import SkipTest
 from nose.tools import with_setup
 
@@ -32,6 +32,12 @@ def test_bar():
     h.FillRandom('gaus')
     rplt.bar(h)
 
+    # stack
+    h1 = h.Clone()
+    stack = HistStack([h, h1])
+    rplt.bar(stack)
+    rplt.bar([h, h1])
+
 
 @with_setup(setup_func)
 def test_hist():
@@ -40,6 +46,12 @@ def test_hist():
     h.FillRandom('gaus')
     rplt.hist(h)
 
+    # stack
+    h1 = h.Clone()
+    stack = HistStack([h, h1])
+    rplt.hist(stack)
+    rplt.hist([h, h1])
+
 
 @with_setup(setup_func)
 def test_hist2d():