Skip to content

Commit 9faeab7

Browse files
authored
BUG: asymmetric error bars for series (GH9536) (#34514)
1 parent 41022a8 commit 9faeab7

File tree

4 files changed

+37
-2
lines changed

4 files changed

+37
-2
lines changed

doc/source/user_guide/visualization.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1425,7 +1425,7 @@ Horizontal and vertical error bars can be supplied to the ``xerr`` and ``yerr``
14251425
* As a ``str`` indicating which of the columns of plotting :class:`DataFrame` contain the error values.
14261426
* As raw values (``list``, ``tuple``, or ``np.ndarray``). Must be the same length as the plotting :class:`DataFrame`/:class:`Series`.
14271427

1428-
Asymmetrical error bars are also supported, however raw error values must be provided in this case. For a ``M`` length :class:`Series`, a ``Mx2`` array should be provided indicating lower and upper (or left and right) errors. For a ``MxN`` :class:`DataFrame`, asymmetrical errors should be in a ``Mx2xN`` array.
1428+
Asymmetrical error bars are also supported, however raw error values must be provided in this case. For a ``N`` length :class:`Series`, a ``2xN`` array should be provided indicating lower and upper (or left and right) errors. For a ``MxN`` :class:`DataFrame`, asymmetrical errors should be in a ``Mx2xN`` array.
14291429

14301430
Here is an example of one way to easily plot group means with standard deviations from the raw data.
14311431

doc/source/whatsnew/v1.1.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ Other enhancements
340340
- :class:`pandas.core.window.ExponentialMovingWindow` now supports a ``times`` argument that allows ``mean`` to be calculated with observations spaced by the timestamps in ``times`` (:issue:`34839`)
341341
- :meth:`DataFrame.agg` and :meth:`Series.agg` now accept named aggregation for renaming the output columns/indexes. (:issue:`26513`)
342342
- ``compute.use_numba`` now exists as a configuration option that utilizes the numba engine when available (:issue:`33966`)
343+
- :meth:`Series.plot` now supports asymmetric error bars. Previously, if :meth:`Series.plot` received a "2xN" array with error values for `yerr` and/or `xerr`, the left/lower values (first row) were mirrored, while the right/upper values (second row) were ignored. Now, the first row represents the left/lower error values and the second row the right/upper error values. (:issue:`9536`)
343344

344345
.. ---------------------------------------------------------------------------
345346

pandas/plotting/_matplotlib/core.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,12 @@ def _parse_errorbars(self, label, err):
770770
DataFrame/dict: error values are paired with keys matching the
771771
key in the plotted DataFrame
772772
str: the name of the column within the plotted DataFrame
773+
774+
Asymmetrical error bars are also supported, however raw error values
775+
must be provided in this case. For a ``N`` length :class:`Series`, a
776+
``2xN`` array should be provided indicating lower and upper (or left
777+
and right) errors. For a ``MxN`` :class:`DataFrame`, asymmetrical errors
778+
should be in a ``Mx2xN`` array.
773779
"""
774780
if err is None:
775781
return None
@@ -810,7 +816,15 @@ def match_labels(data, e):
810816
err_shape = err.shape
811817

812818
# asymmetrical error bars
813-
if err.ndim == 3:
819+
if isinstance(self.data, ABCSeries) and err_shape[0] == 2:
820+
err = np.expand_dims(err, 0)
821+
err_shape = err.shape
822+
if err_shape[2] != len(self.data):
823+
raise ValueError(
824+
"Asymmetrical error bars should be provided "
825+
f"with the shape (2, {len(self.data)})"
826+
)
827+
elif isinstance(self.data, ABCDataFrame) and err.ndim == 3:
814828
if (
815829
(err_shape[0] != self.nseries)
816830
or (err_shape[1] != 2)

pandas/tests/plotting/test_series.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,26 @@ def test_dup_datetime_index_plot(self):
729729
s = Series(values, index=index)
730730
_check_plot_works(s.plot)
731731

732+
def test_errorbar_asymmetrical(self):
733+
# GH9536
734+
s = Series(np.arange(10), name="x")
735+
err = np.random.rand(2, 10)
736+
737+
ax = s.plot(yerr=err, xerr=err)
738+
739+
result = np.vstack([i.vertices[:, 1] for i in ax.collections[1].get_paths()])
740+
expected = (err.T * np.array([-1, 1])) + s.to_numpy().reshape(-1, 1)
741+
tm.assert_numpy_array_equal(result, expected)
742+
743+
msg = (
744+
"Asymmetrical error bars should be provided "
745+
f"with the shape \\(2, {len(s)}\\)"
746+
)
747+
with pytest.raises(ValueError, match=msg):
748+
s.plot(yerr=np.random.rand(2, 11))
749+
750+
tm.close()
751+
732752
@pytest.mark.slow
733753
def test_errorbar_plot(self):
734754

0 commit comments

Comments
 (0)