diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index dce5a356694f..03bd2bf1c98f 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -47,6 +47,10 @@ original location: - mstream -> `from matplotlib import stream as mstream` - mtable -> `from matplotlib import table as mtable` +* The :func:`~matplotlib.pyplot.errorbar` method has been changed such that + the upper and lower limits (*lolims*, *uplims*, *xlolims*, *xuplims*) now + point in the correct direction. + * The Sphinx extensions `ipython_directive` and `ipython_console_highlighting` have been moved to the IPython project itself. While they remain in matplotlib for this release, diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index 341ac381c617..068b8e34d6bf 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -139,6 +139,11 @@ specify wedgeprops = {'linewidth':3} to specify the width of the borders of the wedges in the pie. For more properties that the user can specify, look at the docs for the wedge and text objects. +Fixed the direction of errorbar upper/lower limits +`````````````````````````````````````````````````` +Larry Bradley fixed the :func:`~matplotlib.pyplot.errorbar` method such +that the upper and lower limits (*lolims*, *uplims*, *xlolims*, +*xuplims*) now point in the correct direction. Date handling ------------- diff --git a/examples/statistics/errorbar_limits.py b/examples/statistics/errorbar_limits.py new file mode 100644 index 000000000000..ae335e9deab0 --- /dev/null +++ b/examples/statistics/errorbar_limits.py @@ -0,0 +1,53 @@ +""" +Demo of the errorbar function, including upper and lower limits +""" +import numpy as np +import matplotlib.pyplot as plt + +# example data +x = np.arange(0.5, 5.5, 0.5) +y = np.exp(-x) +xerr = 0.1 +yerr = 0.2 +ls = 'dotted' + +fig = plt.figure() +ax = fig.add_subplot(1, 1, 1) + +# standard error bars +plt.errorbar(x, y, xerr=xerr, yerr=yerr, ls=ls, color='blue') + +# including upper limits +uplims = np.zeros(x.shape) +uplims[[1, 5, 9]] = True +plt.errorbar(x, y+0.5, xerr=xerr, yerr=yerr, uplims=uplims, ls=ls, + color='green') + +# including lower limits +lolims = np.zeros(x.shape) +lolims[[2, 4, 8]] = True +plt.errorbar(x, y+1.0, xerr=xerr, yerr=yerr, lolims=lolims, ls=ls, + color='red') + +# including upper and lower limits +plt.errorbar(x, y+1.5, marker='o', ms=8, xerr=xerr, yerr=yerr, + lolims=lolims, uplims=uplims, ls=ls, color='magenta') + +# including xlower and xupper limits +xerr = 0.2 +yerr = np.zeros(x.shape) + 0.2 +yerr[[3, 6]] = 0.3 +xlolims = lolims +xuplims = uplims +lolims = np.zeros(x.shape) +uplims = np.zeros(x.shape) +lolims[[6]] = True +uplims[[3]] = True +plt.errorbar(x, y+2.1, marker='o', ms=8, xerr=xerr, yerr=yerr, + xlolims=xlolims, xuplims=xuplims, uplims=uplims, lolims=lolims, + ls='none', mec='blue', capsize=0, color='cyan') + +ax.set_xlim((0, 5.5)) +ax.set_title('Errorbar upper and lower limits') +plt.show() + diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 6b8ad4549ad8..13d3f3f26a2f 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2602,7 +2602,9 @@ def errorbar(self, x, y, yerr=None, xerr=None, These arguments can be used to indicate that a value gives only upper/lower limits. In that case a caret symbol is used to indicate this. lims-arguments may be of the same - type as *xerr* and *yerr*. + type as *xerr* and *yerr*. To use limits with inverted + axes, :meth:`set_xlim` or :meth:`set_ylim` must be called + before :meth:`errorbar`. *errorevery*: positive integer subsamples the errorbars. e.g., if everyerror=5, errorbars for @@ -2682,16 +2684,12 @@ def errorbar(self, x, y, yerr=None, xerr=None, if elinewidth: lines_kw['linewidth'] = elinewidth else: - if 'linewidth' in kwargs: - lines_kw['linewidth'] = kwargs['linewidth'] - if 'lw' in kwargs: - lines_kw['lw'] = kwargs['lw'] - if 'transform' in kwargs: - lines_kw['transform'] = kwargs['transform'] - if 'alpha' in kwargs: - lines_kw['alpha'] = kwargs['alpha'] - if 'zorder' in kwargs: - lines_kw['zorder'] = kwargs['zorder'] + for key in ('linewidth', 'lw'): + if key in kwargs: + lines_kw[key] = kwargs[key] + for key in ('transform', 'alpha', 'zorder'): + if key in kwargs: + lines_kw[key] = kwargs[key] # arrays fine here, they are booleans and hence not units if not iterable(lolims): @@ -2727,29 +2725,21 @@ def xywhere(xs, ys, mask): ys = [thisy for thisy, b in zip(ys, mask) if b] return xs, ys + plot_kw = {'label': '_nolegend_'} if capsize > 0: - plot_kw = { - 'ms': 2 * capsize, - 'label': '_nolegend_'} - if capthick is not None: - # 'mew' has higher priority, I believe, - # if both 'mew' and 'markeredgewidth' exists. - # So, save capthick to markeredgewidth so that - # explicitly setting mew or markeredgewidth will - # over-write capthick. - plot_kw['markeredgewidth'] = capthick - # For backwards-compat, allow explicit setting of - # 'mew' or 'markeredgewidth' to over-ride capthick. - if 'markeredgewidth' in kwargs: - plot_kw['markeredgewidth'] = kwargs['markeredgewidth'] - if 'mew' in kwargs: - plot_kw['mew'] = kwargs['mew'] - if 'transform' in kwargs: - plot_kw['transform'] = kwargs['transform'] - if 'alpha' in kwargs: - plot_kw['alpha'] = kwargs['alpha'] - if 'zorder' in kwargs: - plot_kw['zorder'] = kwargs['zorder'] + plot_kw['ms'] = 2. * capsize + if capthick is not None: + # 'mew' has higher priority, I believe, + # if both 'mew' and 'markeredgewidth' exists. + # So, save capthick to markeredgewidth so that + # explicitly setting mew or markeredgewidth will + # over-write capthick. + plot_kw['markeredgewidth'] = capthick + # For backwards-compat, allow explicit setting of + # 'mew' or 'markeredgewidth' to over-ride capthick. + for key in ('markeredgewidth', 'mew', 'transform', 'alpha', 'zorder'): + if key in kwargs: + plot_kw[key] = kwargs[key] if xerr is not None: if (iterable(xerr) and len(xerr) == 2 and @@ -2766,38 +2756,48 @@ def xywhere(xs, ys, mask): right = [thisx + thiserr for (thisx, thiserr) in cbook.safezip(x, xerr)] - yo, _ = xywhere(y, right, everymask) - lo, ro = xywhere(left, right, everymask) - barcols.append(self.hlines(yo, lo, ro, **lines_kw)) - if capsize > 0: - if xlolims.any(): - # can't use numpy logical indexing since left and - # y are lists - leftlo, ylo = xywhere(left, y, xlolims & everymask) - - caplines.extend( - self.plot(leftlo, ylo, ls='None', - marker=mlines.CARETLEFT, **plot_kw)) - xlolims = ~xlolims - leftlo, ylo = xywhere(left, y, xlolims & everymask) - caplines.extend(self.plot(leftlo, ylo, 'k|', **plot_kw)) + # select points without upper/lower limits in x and + # draw normal errorbars for these points + noxlims = ~(xlolims | xuplims) + if noxlims.any(): + yo, _ = xywhere(y, right, noxlims & everymask) + lo, ro = xywhere(left, right, noxlims & everymask) + barcols.append(self.hlines(yo, lo, ro, **lines_kw)) + if capsize > 0: + caplines.extend(self.plot(lo, yo, 'k|', **plot_kw)) + caplines.extend(self.plot(ro, yo, 'k|', **plot_kw)) + + if xlolims.any(): + yo, _ = xywhere(y, right, xlolims & everymask) + lo, ro = xywhere(x, right, xlolims & everymask) + barcols.append(self.hlines(yo, lo, ro, **lines_kw)) + rightup, yup = xywhere(right, y, xlolims & everymask) + if self.xaxis_inverted(): + marker = mlines.CARETLEFT else: - - leftlo, ylo = xywhere(left, y, everymask) - caplines.extend(self.plot(leftlo, ylo, 'k|', **plot_kw)) - - if xuplims.any(): - - rightup, yup = xywhere(right, y, xuplims & everymask) - caplines.extend( - self.plot(rightup, yup, ls='None', - marker=mlines.CARETRIGHT, **plot_kw)) - xuplims = ~xuplims - rightup, yup = xywhere(right, y, xuplims & everymask) - caplines.extend(self.plot(rightup, yup, 'k|', **plot_kw)) + marker = mlines.CARETRIGHT + caplines.extend( + self.plot(rightup, yup, ls='None', marker=marker, + **plot_kw)) + if capsize > 0: + xlo, ylo = xywhere(x, y, xlolims & everymask) + caplines.extend(self.plot(xlo, ylo, 'k|', **plot_kw)) + + if xuplims.any(): + yo, _ = xywhere(y, right, xuplims & everymask) + lo, ro = xywhere(left, x, xuplims & everymask) + barcols.append(self.hlines(yo, lo, ro, **lines_kw)) + leftlo, ylo = xywhere(left, y, xuplims & everymask) + if self.xaxis_inverted(): + marker = mlines.CARETRIGHT else: - rightup, yup = xywhere(right, y, everymask) - caplines.extend(self.plot(rightup, yup, 'k|', **plot_kw)) + marker = mlines.CARETLEFT + caplines.extend( + self.plot(leftlo, ylo, ls='None', marker=marker, + **plot_kw)) + if capsize > 0: + xup, yup = xywhere(x, y, xuplims & everymask) + caplines.extend(self.plot(xup, yup, 'k|', **plot_kw)) if yerr is not None: if (iterable(yerr) and len(yerr) == 2 and @@ -2814,35 +2814,48 @@ def xywhere(xs, ys, mask): upper = [thisy + thiserr for (thisy, thiserr) in cbook.safezip(y, yerr)] - xo, _ = xywhere(x, lower, everymask) - lo, uo = xywhere(lower, upper, everymask) - barcols.append(self.vlines(xo, lo, uo, **lines_kw)) - if capsize > 0: - - if lolims.any(): - xlo, lowerlo = xywhere(x, lower, lolims & everymask) - caplines.extend( - self.plot(xlo, lowerlo, ls='None', - marker=mlines.CARETDOWN, **plot_kw)) - lolims = ~lolims - xlo, lowerlo = xywhere(x, lower, lolims & everymask) - caplines.extend(self.plot(xlo, lowerlo, 'k_', **plot_kw)) + # select points without upper/lower limits in y and + # draw normal errorbars for these points + noylims = ~(lolims | uplims) + if noylims.any(): + xo, _ = xywhere(x, lower, noylims & everymask) + lo, uo = xywhere(lower, upper, noylims & everymask) + barcols.append(self.vlines(xo, lo, uo, **lines_kw)) + if capsize > 0: + caplines.extend(self.plot(xo, lo, 'k_', **plot_kw)) + caplines.extend(self.plot(xo, uo, 'k_', **plot_kw)) + + if lolims.any(): + xo, _ = xywhere(x, lower, lolims & everymask) + lo, uo = xywhere(y, upper, lolims & everymask) + barcols.append(self.vlines(xo, lo, uo, **lines_kw)) + xup, upperup = xywhere(x, upper, lolims & everymask) + if self.yaxis_inverted(): + marker = mlines.CARETDOWN else: - xlo, lowerlo = xywhere(x, lower, everymask) - caplines.extend(self.plot(xlo, lowerlo, 'k_', **plot_kw)) - - if uplims.any(): - xup, upperup = xywhere(x, upper, uplims & everymask) - - caplines.extend( - self.plot(xup, upperup, ls='None', - marker=mlines.CARETUP, **plot_kw)) - uplims = ~uplims - xup, upperup = xywhere(x, upper, uplims & everymask) - caplines.extend(self.plot(xup, upperup, 'k_', **plot_kw)) + marker = mlines.CARETUP + caplines.extend( + self.plot(xup, upperup, ls='None', marker=marker, + **plot_kw)) + if capsize > 0: + xlo, ylo = xywhere(x, y, lolims & everymask) + caplines.extend(self.plot(xlo, ylo, 'k_', **plot_kw)) + + if uplims.any(): + xo, _ = xywhere(x, lower, uplims & everymask) + lo, uo = xywhere(lower, y, uplims & everymask) + barcols.append(self.vlines(xo, lo, uo, **lines_kw)) + xlo, lowerlo = xywhere(x, lower, uplims & everymask) + if self.yaxis_inverted(): + marker = mlines.CARETUP else: - xup, upperup = xywhere(x, upper, everymask) - caplines.extend(self.plot(xup, upperup, 'k_', **plot_kw)) + marker = mlines.CARETDOWN + caplines.extend( + self.plot(xlo, lowerlo, ls='None', marker=marker, + **plot_kw)) + if capsize > 0: + xup, yup = xywhere(x, y, uplims & everymask) + caplines.extend(self.plot(xup, yup, 'k_', **plot_kw)) if not barsabove and fmt is not None: l0, = self.plot(x, y, fmt, **kwargs) diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.pdf b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.pdf new file mode 100644 index 000000000000..9393432690ff Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.png b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.png new file mode 100644 index 000000000000..4029304663d3 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.svg b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.svg new file mode 100644 index 000000000000..3524cf5cbd94 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.svg @@ -0,0 +1,2027 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index aca809fa742b..2d52aaa22548 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1504,6 +1504,54 @@ def test_errorbar(): fig.suptitle('Variable errorbars') +@image_comparison(baseline_images=['errorbar_limits']) +def test_errorbar_limits(): + x = np.arange(0.5, 5.5, 0.5) + y = np.exp(-x) + xerr = 0.1 + yerr = 0.2 + ls = 'dotted' + + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1) + + # standard error bars + plt.errorbar(x, y, xerr=xerr, yerr=yerr, ls=ls, color='blue') + + # including upper limits + uplims = np.zeros(x.shape) + uplims[[1, 5, 9]] = True + plt.errorbar(x, y+0.5, xerr=xerr, yerr=yerr, uplims=uplims, ls=ls, + color='green') + + # including lower limits + lolims = np.zeros(x.shape) + lolims[[2, 4, 8]] = True + plt.errorbar(x, y+1.0, xerr=xerr, yerr=yerr, lolims=lolims, ls=ls, + color='red') + + # including upper and lower limits + plt.errorbar(x, y+1.5, marker='o', ms=8, xerr=xerr, yerr=yerr, + lolims=lolims, uplims=uplims, ls=ls, color='magenta') + + # including xlower and xupper limits + xerr = 0.2 + yerr = np.zeros(x.shape) + 0.2 + yerr[[3, 6]] = 0.3 + xlolims = lolims + xuplims = uplims + lolims = np.zeros(x.shape) + uplims = np.zeros(x.shape) + lolims[[6]] = True + uplims[[3]] = True + plt.errorbar(x, y+2.1, marker='o', ms=8, xerr=xerr, yerr=yerr, + xlolims=xlolims, xuplims=xuplims, uplims=uplims, + lolims=lolims, ls='none', mec='blue', capsize=0, + color='cyan') + ax.set_xlim((0, 5.5)) + ax.set_title('Errorbar upper and lower limits') + + @image_comparison(baseline_images=['hist_stacked_stepfilled']) def test_hist_stacked_stepfilled(): # make some data