Skip to content

Commit 45e66eb

Browse files
author
Tom Augspurger
committed
Merge pull request #6834 from sinhrks/errorbar
ENH: Scatter plot now supports errorbar
2 parents 0961cab + 880a3d0 commit 45e66eb

File tree

5 files changed

+198
-98
lines changed

5 files changed

+198
-98
lines changed

doc/source/release.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ New features
6363
Date is used primarily in astronomy and represents the number of days from
6464
noon, January 1, 4713 BC. Because nanoseconds are used to define the time
6565
in pandas the actual range of dates that you can use is 1678 AD to 2262 AD. (:issue:`4041`)
66-
- Added error bar support to the ``.plot`` method of ``DataFrame`` and ``Series`` (:issue:`3796`)
66+
- Added error bar support to the ``.plot`` method of ``DataFrame`` and ``Series`` (:issue:`3796`, :issue:`6834`)
6767
- Implemented ``Panel.pct_change`` (:issue:`6904`)
6868

6969
API Changes

doc/source/v0.14.0.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ Plotting
365365

366366
- Hexagonal bin plots from ``DataFrame.plot`` with ``kind='hexbin'`` (:issue:`5478`), See :ref:`the docs<visualization.hexbin>`.
367367
- ``DataFrame.plot`` and ``Series.plot`` now supports area plot with specifying ``kind='area'`` (:issue:`6656`)
368-
- Plotting with Error Bars is now supported in the ``.plot`` method of ``DataFrame`` and ``Series`` objects (:issue:`3796`), See :ref:`the docs<visualization.errorbars>`.
368+
- Plotting with Error Bars is now supported in the ``.plot`` method of ``DataFrame`` and ``Series`` objects (:issue:`3796`, :issue:`6834`), See :ref:`the docs<visualization.errorbars>`.
369369
- ``DataFrame.plot`` and ``Series.plot`` now support a ``table`` keyword for plotting ``matplotlib.Table``, See :ref:`the docs<visualization.table>`.
370370
- ``plot(legend='reverse')`` will now reverse the order of legend labels for
371371
most plot kinds. (:issue:`6014`)

doc/source/visualization.rst

+4-1
Original file line numberDiff line numberDiff line change
@@ -394,10 +394,13 @@ x and y errorbars are supported and be supplied using the ``xerr`` and ``yerr``
394394

395395
- As a ``DataFrame`` or ``dict`` of errors with column names matching the ``columns`` attribute of the plotting ``DataFrame`` or matching the ``name`` attribute of the ``Series``
396396
- As a ``str`` indicating which of the columns of plotting ``DataFrame`` contain the error values
397-
- As raw values (``list``, ``tuple``, or ``np.ndarray``). Must be the same length as the plotting ``DataFrame``/``Series``
397+
- As list-like raw values (``list``, ``tuple``, or ``np.ndarray``). Must be the same length as the plotting ``DataFrame``/``Series``
398+
- As float. The error value will be applied to all data.
398399

399400
Asymmetrical error bars are also supported, however raw error values must be provided in this case. For a ``M`` length ``Series``, a ``Mx2`` array should be provided indicating lower and upper (or left and right) errors. For a ``MxN`` ``DataFrame``, asymmetrical errors should be in a ``Mx2xN`` array.
400401

402+
**Note**: Plotting ``xerr`` is not supported in time series.
403+
401404
Here is an example of one way to easily plot group means with standard deviations from the raw data.
402405

403406
.. ipython:: python

pandas/tests/test_graphics.py

+121-34
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#!/usr/bin/env python
2+
# coding: utf-8
3+
14
import nose
25
import os
36
import string
@@ -27,7 +30,6 @@ def _skip_if_no_scipy():
2730
except ImportError:
2831
raise nose.SkipTest("no scipy")
2932

30-
3133
@tm.mplskip
3234
class TestSeriesPlots(tm.TestCase):
3335
def setUp(self):
@@ -315,24 +317,36 @@ def test_dup_datetime_index_plot(self):
315317
@slow
316318
def test_errorbar_plot(self):
317319

318-
s = Series(np.arange(10))
320+
s = Series(np.arange(10), name='x')
319321
s_err = np.random.randn(10)
320-
322+
d_err = DataFrame(randn(10, 2), index=s.index, columns=['x', 'y'])
321323
# test line and bar plots
322324
kinds = ['line', 'bar']
323325
for kind in kinds:
324-
_check_plot_works(s.plot, yerr=Series(s_err), kind=kind)
325-
_check_plot_works(s.plot, yerr=s_err, kind=kind)
326-
_check_plot_works(s.plot, yerr=s_err.tolist(), kind=kind)
327-
328-
_check_plot_works(s.plot, xerr=s_err)
326+
ax = _check_plot_works(s.plot, yerr=Series(s_err), kind=kind)
327+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
328+
ax = _check_plot_works(s.plot, yerr=s_err, kind=kind)
329+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
330+
ax = _check_plot_works(s.plot, yerr=s_err.tolist(), kind=kind)
331+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
332+
ax = _check_plot_works(s.plot, yerr=d_err, kind=kind)
333+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
334+
ax = _check_plot_works(s.plot, xerr=0.2, yerr=0.2, kind=kind)
335+
_check_has_errorbars(self, ax, xerr=1, yerr=1)
336+
337+
ax = _check_plot_works(s.plot, xerr=s_err)
338+
_check_has_errorbars(self, ax, xerr=1, yerr=0)
329339

330340
# test time series plotting
331341
ix = date_range('1/1/2000', '1/1/2001', freq='M')
332-
ts = Series(np.arange(12), index=ix)
342+
ts = Series(np.arange(12), index=ix, name='x')
333343
ts_err = Series(np.random.randn(12), index=ix)
344+
td_err = DataFrame(randn(12, 2), index=ix, columns=['x', 'y'])
334345

335-
_check_plot_works(ts.plot, yerr=ts_err)
346+
ax = _check_plot_works(ts.plot, yerr=ts_err)
347+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
348+
ax = _check_plot_works(ts.plot, yerr=td_err)
349+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
336350

337351
# check incorrect lengths and types
338352
with tm.assertRaises(ValueError):
@@ -1505,27 +1519,51 @@ def test_errorbar_plot(self):
15051519
df_err = DataFrame(d_err)
15061520

15071521
# check line plots
1508-
_check_plot_works(df.plot, yerr=df_err, logy=True)
1509-
_check_plot_works(df.plot, yerr=df_err, logx=True, logy=True)
1510-
_check_plot_works(df.plot, yerr=df_err, loglog=True)
1522+
ax = _check_plot_works(df.plot, yerr=df_err, logy=True)
1523+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
1524+
ax = _check_plot_works(df.plot, yerr=df_err, logx=True, logy=True)
1525+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
1526+
ax = _check_plot_works(df.plot, yerr=df_err, loglog=True)
1527+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
15111528

15121529
kinds = ['line', 'bar', 'barh']
15131530
for kind in kinds:
1514-
_check_plot_works(df.plot, yerr=df_err['x'], kind=kind)
1515-
_check_plot_works(df.plot, yerr=d_err, kind=kind)
1516-
_check_plot_works(df.plot, yerr=df_err, xerr=df_err, kind=kind)
1517-
_check_plot_works(df.plot, yerr=df_err['x'], xerr=df_err['x'], kind=kind)
1518-
_check_plot_works(df.plot, yerr=df_err, xerr=df_err, subplots=True, kind=kind)
1531+
ax = _check_plot_works(df.plot, yerr=df_err['x'], kind=kind)
1532+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
1533+
ax = _check_plot_works(df.plot, yerr=d_err, kind=kind)
1534+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
1535+
ax = _check_plot_works(df.plot, yerr=df_err, xerr=df_err, kind=kind)
1536+
_check_has_errorbars(self, ax, xerr=2, yerr=2)
1537+
ax = _check_plot_works(df.plot, yerr=df_err['x'], xerr=df_err['x'], kind=kind)
1538+
_check_has_errorbars(self, ax, xerr=2, yerr=2)
1539+
ax = _check_plot_works(df.plot, xerr=0.2, yerr=0.2, kind=kind)
1540+
_check_has_errorbars(self, ax, xerr=2, yerr=2)
1541+
axes = _check_plot_works(df.plot, yerr=df_err, xerr=df_err, subplots=True, kind=kind)
1542+
for ax in axes:
1543+
_check_has_errorbars(self, ax, xerr=1, yerr=1)
15191544

1520-
_check_plot_works((df+1).plot, yerr=df_err, xerr=df_err, kind='bar', log=True)
1545+
ax = _check_plot_works((df+1).plot, yerr=df_err, xerr=df_err, kind='bar', log=True)
1546+
_check_has_errorbars(self, ax, xerr=2, yerr=2)
15211547

15221548
# yerr is raw error values
1523-
_check_plot_works(df['y'].plot, yerr=np.ones(12)*0.4)
1524-
_check_plot_works(df.plot, yerr=np.ones((2, 12))*0.4)
1549+
ax = _check_plot_works(df['y'].plot, yerr=np.ones(12)*0.4)
1550+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
1551+
ax = _check_plot_works(df.plot, yerr=np.ones((2, 12))*0.4)
1552+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
1553+
1554+
# yerr is iterator
1555+
import itertools
1556+
ax = _check_plot_works(df.plot, yerr=itertools.repeat(0.1, len(df)))
1557+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
15251558

15261559
# yerr is column name
1527-
df['yerr'] = np.ones(12)*0.2
1528-
_check_plot_works(df.plot, y='y', x='x', yerr='yerr')
1560+
for yerr in ['yerr', u('誤差')]:
1561+
s_df = df.copy()
1562+
s_df[yerr] = np.ones(12)*0.2
1563+
ax = _check_plot_works(s_df.plot, yerr=yerr)
1564+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
1565+
ax = _check_plot_works(s_df.plot, y='y', x='x', yerr=yerr)
1566+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
15291567

15301568
with tm.assertRaises(ValueError):
15311569
df.plot(yerr=np.random.randn(11))
@@ -1539,21 +1577,33 @@ def test_errorbar_with_integer_column_names(self):
15391577
# test with integer column names
15401578
df = DataFrame(np.random.randn(10, 2))
15411579
df_err = DataFrame(np.random.randn(10, 2))
1542-
_check_plot_works(df.plot, yerr=df_err)
1543-
_check_plot_works(df.plot, y=0, yerr=1)
1580+
ax = _check_plot_works(df.plot, yerr=df_err)
1581+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
1582+
ax = _check_plot_works(df.plot, y=0, yerr=1)
1583+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
15441584

15451585
@slow
15461586
def test_errorbar_with_partial_columns(self):
15471587
df = DataFrame(np.random.randn(10, 3))
15481588
df_err = DataFrame(np.random.randn(10, 2), columns=[0, 2])
15491589
kinds = ['line', 'bar']
15501590
for kind in kinds:
1551-
_check_plot_works(df.plot, yerr=df_err, kind=kind)
1591+
ax = _check_plot_works(df.plot, yerr=df_err, kind=kind)
1592+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
15521593

15531594
ix = date_range('1/1/2000', periods=10, freq='M')
15541595
df.set_index(ix, inplace=True)
15551596
df_err.set_index(ix, inplace=True)
1556-
_check_plot_works(df.plot, yerr=df_err, kind='line')
1597+
ax = _check_plot_works(df.plot, yerr=df_err, kind='line')
1598+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
1599+
1600+
d = {'x': np.arange(12), 'y': np.arange(12, 0, -1)}
1601+
df = DataFrame(d)
1602+
d_err = {'x': np.ones(12)*0.2, 'z': np.ones(12)*0.4}
1603+
df_err = DataFrame(d_err)
1604+
for err in [d_err, df_err]:
1605+
ax = _check_plot_works(df.plot, yerr=err)
1606+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
15571607

15581608
@slow
15591609
def test_errorbar_timeseries(self):
@@ -1568,13 +1618,19 @@ def test_errorbar_timeseries(self):
15681618

15691619
kinds = ['line', 'bar', 'barh']
15701620
for kind in kinds:
1571-
_check_plot_works(tdf.plot, yerr=tdf_err, kind=kind)
1572-
_check_plot_works(tdf.plot, yerr=d_err, kind=kind)
1573-
_check_plot_works(tdf.plot, y='y', kind=kind)
1574-
_check_plot_works(tdf.plot, y='y', yerr='x', kind=kind)
1575-
_check_plot_works(tdf.plot, yerr=tdf_err, kind=kind)
1576-
_check_plot_works(tdf.plot, kind=kind, subplots=True)
1577-
1621+
ax = _check_plot_works(tdf.plot, yerr=tdf_err, kind=kind)
1622+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
1623+
ax = _check_plot_works(tdf.plot, yerr=d_err, kind=kind)
1624+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
1625+
ax = _check_plot_works(tdf.plot, y='y', yerr=tdf_err['x'], kind=kind)
1626+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
1627+
ax = _check_plot_works(tdf.plot, y='y', yerr='x', kind=kind)
1628+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
1629+
ax = _check_plot_works(tdf.plot, yerr=tdf_err, kind=kind)
1630+
_check_has_errorbars(self, ax, xerr=0, yerr=2)
1631+
axes = _check_plot_works(tdf.plot, kind=kind, yerr=tdf_err, subplots=True)
1632+
for ax in axes:
1633+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
15781634

15791635
def test_errorbar_asymmetrical(self):
15801636

@@ -1608,6 +1664,21 @@ def test_table(self):
16081664
plotting.table(ax, df.T)
16091665
self.assert_(len(ax.tables) == 1)
16101666

1667+
def test_errorbar_scatter(self):
1668+
df = DataFrame(np.random.randn(5, 2), index=range(5), columns=['x', 'y'])
1669+
df_err = DataFrame(np.random.randn(5, 2) / 5,
1670+
index=range(5), columns=['x', 'y'])
1671+
1672+
ax = _check_plot_works(df.plot, kind='scatter', x='x', y='y')
1673+
_check_has_errorbars(self, ax, xerr=0, yerr=0)
1674+
ax = _check_plot_works(df.plot, kind='scatter', x='x', y='y', xerr=df_err)
1675+
_check_has_errorbars(self, ax, xerr=1, yerr=0)
1676+
ax = _check_plot_works(df.plot, kind='scatter', x='x', y='y', yerr=df_err)
1677+
_check_has_errorbars(self, ax, xerr=0, yerr=1)
1678+
ax = _check_plot_works(df.plot, kind='scatter', x='x', y='y',
1679+
xerr=df_err, yerr=df_err)
1680+
_check_has_errorbars(self, ax, xerr=1, yerr=1)
1681+
16111682

16121683
@tm.mplskip
16131684
class TestDataFrameGroupByPlots(tm.TestCase):
@@ -1803,8 +1874,24 @@ def assert_is_valid_plot_return_object(objs):
18031874
''.format(objs.__class__.__name__))
18041875

18051876

1877+
def _check_has_errorbars(t, ax, xerr=0, yerr=0):
1878+
containers = ax.containers
1879+
xerr_count = 0
1880+
yerr_count = 0
1881+
for c in containers:
1882+
has_xerr = getattr(c, 'has_xerr', False)
1883+
has_yerr = getattr(c, 'has_yerr', False)
1884+
if has_xerr:
1885+
xerr_count += 1
1886+
if has_yerr:
1887+
yerr_count += 1
1888+
t.assertEqual(xerr, xerr_count)
1889+
t.assertEqual(yerr, yerr_count)
1890+
1891+
18061892
def _check_plot_works(f, *args, **kwargs):
18071893
import matplotlib.pyplot as plt
1894+
ret = None
18081895

18091896
try:
18101897
try:

0 commit comments

Comments
 (0)