Skip to content

Commit 614e273

Browse files
committed
Merge pull request pandas-dev#6656 from sinhrks/area_pr2
ENH/VIS: Area plot is now supported by kind='area'.
2 parents cbdd359 + b63a699 commit 614e273

File tree

5 files changed

+380
-66
lines changed

5 files changed

+380
-66
lines changed

doc/source/release.rst

+2
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ API Changes
190190
``data`` argument (:issue:`5357`)
191191
- groupby will now not return the grouped column for non-cython functions (:issue:`5610`, :issue:`5614`),
192192
as its already the index
193+
- ``DataFrame.plot`` and ``Series.plot`` now supports area plot with specifying ``kind='area'`` (:issue:`6656`)
194+
- Line plot can be stacked by ``stacked=True``. (:issue:`6656`)
193195

194196
Deprecations
195197
~~~~~~~~~~~~

doc/source/v0.14.0.txt

+2
Original file line numberDiff line numberDiff line change
@@ -364,10 +364,12 @@ Plotting
364364
~~~~~~~~
365365

366366
- Hexagonal bin plots from ``DataFrame.plot`` with ``kind='hexbin'`` (:issue:`5478`), See :ref:`the docs<visualization.hexbin>`.
367+
- ``DataFrame.plot`` and ``Series.plot`` now supports area plot with specifying ``kind='area'`` (:issue:`6656`)
367368
- Plotting with Error Bars is now supported in the ``.plot`` method of ``DataFrame`` and ``Series`` objects (:issue:`3796`), See :ref:`the docs<visualization.errorbars>`.
368369
- ``DataFrame.plot`` and ``Series.plot`` now support a ``table`` keyword for plotting ``matplotlib.Table``, See :ref:`the docs<visualization.table>`.
369370
- ``plot(legend='reverse')`` will now reverse the order of legend labels for
370371
most plot kinds. (:issue:`6014`)
372+
- Line plot and area plot can be stacked by ``stacked=True`` (:issue:`6656`)
371373

372374
- Following keywords are now acceptable for :meth:`DataFrame.plot(kind='bar')` and :meth:`DataFrame.plot(kind='barh')`.
373375

doc/source/visualization.rst

+34
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,40 @@ Finally, there is a helper function ``pandas.tools.plotting.table`` to create a
461461
462462
**Note**: You can get table instances on the axes using ``axes.tables`` property for further decorations. See the `matplotlib table documenation <http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.table>`__ for more.
463463

464+
.. _visualization.area_plot:
465+
466+
Area plot
467+
~~~~~~~~~~~~~~~~~~~
468+
469+
.. versionadded:: 0.14
470+
471+
You can create area plots with ``Series.plot`` and ``DataFrame.plot`` by passing ``kind='area'``. Area plots are stacked by default. To produce stacked area plot, each column must be either all positive or all negative values.
472+
473+
When input data contains `NaN`, it will be automatically filled by 0. If you want to drop or fill by different values, use :func:`dataframe.dropna` or :func:`dataframe.fillna` before calling `plot`.
474+
475+
.. ipython:: python
476+
:suppress:
477+
478+
plt.figure();
479+
480+
.. ipython:: python
481+
482+
df = DataFrame(rand(10, 4), columns=['a', 'b', 'c', 'd'])
483+
484+
@savefig area_plot_stacked.png
485+
df.plot(kind='area');
486+
487+
To produce an unstacked plot, pass ``stacked=False``. Alpha value is set to 0.5 unless otherwise specified:
488+
489+
.. ipython:: python
490+
:suppress:
491+
492+
plt.figure();
493+
494+
.. ipython:: python
495+
496+
@savefig area_plot_unstacked.png
497+
df.plot(kind='area', stacked=False);
464498
465499
.. _visualization.scatter_matrix:
466500

pandas/tests/test_graphics.py

+189-28
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import numpy as np
1616
from numpy import random
17-
from numpy.random import randn
17+
from numpy.random import rand, randn
1818

1919
from numpy.testing import assert_array_equal
2020
from numpy.testing.decorators import slow
@@ -54,9 +54,10 @@ def test_plot(self):
5454
_check_plot_works(self.ts.plot, style='.', logx=True)
5555
_check_plot_works(self.ts.plot, style='.', loglog=True)
5656
_check_plot_works(self.ts[:10].plot, kind='bar')
57+
_check_plot_works(self.ts.plot, kind='area', stacked=False)
5758
_check_plot_works(self.iseries.plot)
5859

59-
for kind in plotting._common_kinds:
60+
for kind in ['line', 'bar', 'barh', 'kde']:
6061
_check_plot_works(self.series[:5].plot, kind=kind)
6162

6263
_check_plot_works(self.series[:10].plot, kind='barh')
@@ -75,6 +76,33 @@ def test_plot_figsize_and_title(self):
7576
assert_array_equal(np.round(ax.figure.get_size_inches()),
7677
np.array((16., 8.)))
7778

79+
def test_ts_area_lim(self):
80+
ax = self.ts.plot(kind='area', stacked=False)
81+
xmin, xmax = ax.get_xlim()
82+
lines = ax.get_lines()
83+
self.assertEqual(xmin, lines[0].get_data(orig=False)[0][0])
84+
self.assertEqual(xmax, lines[0].get_data(orig=False)[0][-1])
85+
86+
def test_line_area_nan_series(self):
87+
values = [1, 2, np.nan, 3]
88+
s = Series(values)
89+
ts = Series(values, index=tm.makeDateIndex(k=4))
90+
91+
for d in [s, ts]:
92+
ax = _check_plot_works(d.plot)
93+
masked = ax.lines[0].get_ydata()
94+
# remove nan for comparison purpose
95+
self.assert_numpy_array_equal(np.delete(masked.data, 2), np.array([1, 2, 3]))
96+
self.assert_numpy_array_equal(masked.mask, np.array([False, False, True, False]))
97+
98+
expected = np.array([1, 2, 0, 3])
99+
ax = _check_plot_works(d.plot, stacked=True)
100+
self.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected)
101+
ax = _check_plot_works(d.plot, kind='area')
102+
self.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected)
103+
ax = _check_plot_works(d.plot, kind='area', stacked=False)
104+
self.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected)
105+
78106
@slow
79107
def test_bar_log(self):
80108
expected = np.array([1., 10., 100., 1000.])
@@ -500,7 +528,7 @@ def test_subplots(self):
500528
df = DataFrame(np.random.rand(10, 3),
501529
index=list(string.ascii_letters[:10]))
502530

503-
for kind in ['bar', 'barh', 'line']:
531+
for kind in ['bar', 'barh', 'line', 'area']:
504532
axes = df.plot(kind=kind, subplots=True, sharex=True, legend=True)
505533

506534
for ax, column in zip(axes, df.columns):
@@ -529,6 +557,104 @@ def test_subplots(self):
529557
for ax in axes:
530558
self.assertTrue(ax.get_legend() is None)
531559

560+
def test_negative_log(self):
561+
df = - DataFrame(rand(6, 4),
562+
index=list(string.ascii_letters[:6]),
563+
columns=['x', 'y', 'z', 'four'])
564+
565+
with tm.assertRaises(ValueError):
566+
df.plot(kind='area', logy=True)
567+
with tm.assertRaises(ValueError):
568+
df.plot(kind='area', loglog=True)
569+
570+
def _compare_stacked_y_cood(self, normal_lines, stacked_lines):
571+
base = np.zeros(len(normal_lines[0].get_data()[1]))
572+
for nl, sl in zip(normal_lines, stacked_lines):
573+
base += nl.get_data()[1] # get y coodinates
574+
sy = sl.get_data()[1]
575+
self.assert_numpy_array_equal(base, sy)
576+
577+
def test_line_area_stacked(self):
578+
with tm.RNGContext(42):
579+
df = DataFrame(rand(6, 4),
580+
columns=['w', 'x', 'y', 'z'])
581+
neg_df = - df
582+
# each column has either positive or negative value
583+
sep_df = DataFrame({'w': rand(6), 'x': rand(6),
584+
'y': - rand(6), 'z': - rand(6)})
585+
# each column has positive-negative mixed value
586+
mixed_df = DataFrame(randn(6, 4), index=list(string.ascii_letters[:6]),
587+
columns=['w', 'x', 'y', 'z'])
588+
589+
for kind in ['line', 'area']:
590+
ax1 = _check_plot_works(df.plot, kind=kind, stacked=False)
591+
ax2 = _check_plot_works(df.plot, kind=kind, stacked=True)
592+
self._compare_stacked_y_cood(ax1.lines, ax2.lines)
593+
594+
ax1 = _check_plot_works(neg_df.plot, kind=kind, stacked=False)
595+
ax2 = _check_plot_works(neg_df.plot, kind=kind, stacked=True)
596+
self._compare_stacked_y_cood(ax1.lines, ax2.lines)
597+
598+
ax1 = _check_plot_works(sep_df.plot, kind=kind, stacked=False)
599+
ax2 = _check_plot_works(sep_df.plot, kind=kind, stacked=True)
600+
self._compare_stacked_y_cood(ax1.lines[:2], ax2.lines[:2])
601+
self._compare_stacked_y_cood(ax1.lines[2:], ax2.lines[2:])
602+
603+
_check_plot_works(mixed_df.plot, stacked=False)
604+
with tm.assertRaises(ValueError):
605+
mixed_df.plot(stacked=True)
606+
607+
_check_plot_works(df.plot, kind=kind, logx=True, stacked=True)
608+
609+
def test_line_area_nan_df(self):
610+
values1 = [1, 2, np.nan, 3]
611+
values2 = [3, np.nan, 2, 1]
612+
df = DataFrame({'a': values1, 'b': values2})
613+
tdf = DataFrame({'a': values1, 'b': values2}, index=tm.makeDateIndex(k=4))
614+
615+
for d in [df, tdf]:
616+
ax = _check_plot_works(d.plot)
617+
masked1 = ax.lines[0].get_ydata()
618+
masked2 = ax.lines[1].get_ydata()
619+
# remove nan for comparison purpose
620+
self.assert_numpy_array_equal(np.delete(masked1.data, 2), np.array([1, 2, 3]))
621+
self.assert_numpy_array_equal(np.delete(masked2.data, 1), np.array([3, 2, 1]))
622+
self.assert_numpy_array_equal(masked1.mask, np.array([False, False, True, False]))
623+
self.assert_numpy_array_equal(masked2.mask, np.array([False, True, False, False]))
624+
625+
expected1 = np.array([1, 2, 0, 3])
626+
expected2 = np.array([3, 0, 2, 1])
627+
628+
ax = _check_plot_works(d.plot, stacked=True)
629+
self.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected1)
630+
self.assert_numpy_array_equal(ax.lines[1].get_ydata(), expected1 + expected2)
631+
632+
ax = _check_plot_works(d.plot, kind='area')
633+
self.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected1)
634+
self.assert_numpy_array_equal(ax.lines[1].get_ydata(), expected1 + expected2)
635+
636+
ax = _check_plot_works(d.plot, kind='area', stacked=False)
637+
self.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected1)
638+
self.assert_numpy_array_equal(ax.lines[1].get_ydata(), expected2)
639+
640+
def test_area_lim(self):
641+
df = DataFrame(rand(6, 4),
642+
columns=['x', 'y', 'z', 'four'])
643+
644+
neg_df = - df
645+
for stacked in [True, False]:
646+
ax = _check_plot_works(df.plot, kind='area', stacked=stacked)
647+
xmin, xmax = ax.get_xlim()
648+
ymin, ymax = ax.get_ylim()
649+
lines = ax.get_lines()
650+
self.assertEqual(xmin, lines[0].get_data()[0][0])
651+
self.assertEqual(xmax, lines[0].get_data()[0][-1])
652+
self.assertEqual(ymin, 0)
653+
654+
ax = _check_plot_works(neg_df.plot, kind='area', stacked=stacked)
655+
ymin, ymax = ax.get_ylim()
656+
self.assertEqual(ymax, 0)
657+
532658
@slow
533659
def test_bar_colors(self):
534660
import matplotlib.pyplot as plt
@@ -1077,11 +1203,11 @@ def _check_legend_labels(self, ax, labels):
10771203

10781204
@slow
10791205
def test_df_legend_labels(self):
1080-
kinds = 'line', 'bar', 'barh', 'kde', 'density'
1081-
df = DataFrame(randn(3, 3), columns=['a', 'b', 'c'])
1082-
df2 = DataFrame(randn(3, 3), columns=['d', 'e', 'f'])
1083-
df3 = DataFrame(randn(3, 3), columns=['g', 'h', 'i'])
1084-
df4 = DataFrame(randn(3, 3), columns=['j', 'k', 'l'])
1206+
kinds = 'line', 'bar', 'barh', 'kde', 'density', 'area'
1207+
df = DataFrame(rand(3, 3), columns=['a', 'b', 'c'])
1208+
df2 = DataFrame(rand(3, 3), columns=['d', 'e', 'f'])
1209+
df3 = DataFrame(rand(3, 3), columns=['g', 'h', 'i'])
1210+
df4 = DataFrame(rand(3, 3), columns=['j', 'k', 'l'])
10851211

10861212
for kind in kinds:
10871213
ax = df.plot(kind=kind, legend=True)
@@ -1170,31 +1296,41 @@ def test_style_by_column(self):
11701296
for i, l in enumerate(ax.get_lines()[:len(markers)]):
11711297
self.assertEqual(l.get_marker(), markers[i])
11721298

1299+
def check_line_colors(self, colors, lines):
1300+
for i, l in enumerate(lines):
1301+
xp = colors[i]
1302+
rs = l.get_color()
1303+
self.assertEqual(xp, rs)
1304+
1305+
def check_collection_colors(self, colors, cols):
1306+
from matplotlib.colors import ColorConverter
1307+
conv = ColorConverter()
1308+
for i, c in enumerate(cols):
1309+
xp = colors[i]
1310+
xp = conv.to_rgba(xp)
1311+
rs = c.get_facecolor()[0]
1312+
for x, y in zip(xp, rs):
1313+
self.assertEqual(x, y)
1314+
11731315
@slow
11741316
def test_line_colors(self):
11751317
import matplotlib.pyplot as plt
11761318
import sys
11771319
from matplotlib import cm
11781320

11791321
custom_colors = 'rgcby'
1180-
11811322
df = DataFrame(randn(5, 5))
11821323

11831324
ax = df.plot(color=custom_colors)
1184-
1185-
lines = ax.get_lines()
1186-
for i, l in enumerate(lines):
1187-
xp = custom_colors[i]
1188-
rs = l.get_color()
1189-
self.assertEqual(xp, rs)
1325+
self.check_line_colors(custom_colors, ax.get_lines())
11901326

11911327
tmp = sys.stderr
11921328
sys.stderr = StringIO()
11931329
try:
11941330
tm.close()
11951331
ax2 = df.plot(colors=custom_colors)
11961332
lines2 = ax2.get_lines()
1197-
for l1, l2 in zip(lines, lines2):
1333+
for l1, l2 in zip(ax.get_lines(), lines2):
11981334
self.assertEqual(l1.get_color(), l2.get_color())
11991335
finally:
12001336
sys.stderr = tmp
@@ -1204,30 +1340,45 @@ def test_line_colors(self):
12041340
ax = df.plot(colormap='jet')
12051341

12061342
rgba_colors = lmap(cm.jet, np.linspace(0, 1, len(df)))
1207-
1208-
lines = ax.get_lines()
1209-
for i, l in enumerate(lines):
1210-
xp = rgba_colors[i]
1211-
rs = l.get_color()
1212-
self.assertEqual(xp, rs)
1343+
self.check_line_colors(rgba_colors, ax.get_lines())
12131344

12141345
tm.close()
12151346

12161347
ax = df.plot(colormap=cm.jet)
12171348

12181349
rgba_colors = lmap(cm.jet, np.linspace(0, 1, len(df)))
1219-
1220-
lines = ax.get_lines()
1221-
for i, l in enumerate(lines):
1222-
xp = rgba_colors[i]
1223-
rs = l.get_color()
1224-
self.assertEqual(xp, rs)
1350+
self.check_line_colors(rgba_colors, ax.get_lines())
12251351

12261352
# make color a list if plotting one column frame
12271353
# handles cases like df.plot(color='DodgerBlue')
12281354
tm.close()
12291355
df.ix[:, [0]].plot(color='DodgerBlue')
12301356

1357+
@slow
1358+
def test_area_colors(self):
1359+
from matplotlib import cm
1360+
from matplotlib.collections import PolyCollection
1361+
1362+
custom_colors = 'rgcby'
1363+
df = DataFrame(rand(5, 5))
1364+
1365+
ax = df.plot(kind='area', color=custom_colors)
1366+
self.check_line_colors(custom_colors, ax.get_lines())
1367+
poly = [o for o in ax.get_children() if isinstance(o, PolyCollection)]
1368+
self.check_collection_colors(custom_colors, poly)
1369+
1370+
ax = df.plot(kind='area', colormap='jet')
1371+
rgba_colors = lmap(cm.jet, np.linspace(0, 1, len(df)))
1372+
self.check_line_colors(rgba_colors, ax.get_lines())
1373+
poly = [o for o in ax.get_children() if isinstance(o, PolyCollection)]
1374+
self.check_collection_colors(rgba_colors, poly)
1375+
1376+
ax = df.plot(kind='area', colormap=cm.jet)
1377+
rgba_colors = lmap(cm.jet, np.linspace(0, 1, len(df)))
1378+
self.check_line_colors(rgba_colors, ax.get_lines())
1379+
poly = [o for o in ax.get_children() if isinstance(o, PolyCollection)]
1380+
self.check_collection_colors(rgba_colors, poly)
1381+
12311382
def test_default_color_cycle(self):
12321383
import matplotlib.pyplot as plt
12331384
plt.rcParams['axes.color_cycle'] = list('rgbk')
@@ -1268,6 +1419,15 @@ def test_partially_invalid_plot_data(self):
12681419
with tm.assertRaises(TypeError):
12691420
df.plot(kind=kind)
12701421

1422+
with tm.RNGContext(42):
1423+
# area plot doesn't support positive/negative mixed data
1424+
kinds = ['area']
1425+
df = DataFrame(rand(10, 2), dtype=object)
1426+
df[np.random.rand(df.shape[0]) > 0.5] = 'a'
1427+
for kind in kinds:
1428+
with tm.assertRaises(TypeError):
1429+
df.plot(kind=kind)
1430+
12711431
def test_invalid_kind(self):
12721432
df = DataFrame(randn(10, 2))
12731433
with tm.assertRaises(ValueError):
@@ -1671,6 +1831,7 @@ def _check_plot_works(f, *args, **kwargs):
16711831
plt.savefig(path)
16721832
finally:
16731833
tm.close(fig)
1834+
return ret
16741835

16751836

16761837
def curpath():

0 commit comments

Comments
 (0)