diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt index 0bfd755aae40c..aeafc76876bbd 100644 --- a/doc/source/whatsnew/v0.20.0.txt +++ b/doc/source/whatsnew/v0.20.0.txt @@ -53,6 +53,7 @@ Other enhancements - ``pd.cut`` and ``pd.qcut`` now support datetime64 and timedelta64 dtypes (issue:`14714`) - ``Series`` provides a ``to_excel`` method to output Excel files (:issue:`8825`) - The ``usecols`` argument in ``pd.read_csv`` now accepts a callable function as a value (:issue:`14154`) +- ``pd.DataFrame.plot`` now prints a title above each subplot if ``suplots=True`` and ``title`` is a list of strings (:issue:`14753`) .. _whatsnew_0200.api_breaking: diff --git a/pandas/tests/plotting/test_misc.py b/pandas/tests/plotting/test_misc.py index a484217da5969..6c313f5937602 100644 --- a/pandas/tests/plotting/test_misc.py +++ b/pandas/tests/plotting/test_misc.py @@ -16,13 +16,11 @@ from pandas.tests.plotting.common import (TestPlotBase, _check_plot_works, _ok_for_gaussian_kde) - """ Test cases for misc plot functions """ @tm.mplskip class TestSeriesPlots(TestPlotBase): - def setUp(self): TestPlotBase.setUp(self) import matplotlib as mpl @@ -54,7 +52,6 @@ def test_bootstrap_plot(self): @tm.mplskip class TestDataFramePlots(TestPlotBase): - @slow def test_scatter_plot_legacy(self): tm._skip_if_no_scipy() @@ -277,6 +274,32 @@ def test_radviz(self): handles, labels = ax.get_legend_handles_labels() self._check_colors(handles, facecolors=colors) + @slow + def test_subplot_titles(self): + df = self.iris.drop('Name', axis=1).head() + # Use the column names as the subplot titles + title = list(df.columns) + + # Case len(title) == len(df) + plot = df.plot(subplots=True, title=title) + self.assertEqual([p.get_title() for p in plot], title) + + # Case len(title) > len(df) + self.assertRaises(ValueError, df.plot, subplots=True, + title=title + ["kittens > puppies"]) + + # Case len(title) < len(df) + self.assertRaises(ValueError, df.plot, subplots=True, title=title[:2]) + + # Case subplots=False and title is of type list + self.assertRaises(ValueError, df.plot, subplots=False, title=title) + + # Case df with 3 numeric columns but layout of (2,2) + plot = df.drop('SepalWidth', axis=1).plot(subplots=True, layout=(2, 2), + title=title[:-1]) + title_list = [ax.get_title() for sublist in plot for ax in sublist] + self.assertEqual(title_list, title[:3] + ['']) + if __name__ == '__main__': nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'], diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index e4cf896a89f57..21e8b64a3656a 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -1217,8 +1217,25 @@ def _adorn_subplots(self): if self.title: if self.subplots: - self.fig.suptitle(self.title) + if is_list_like(self.title): + if len(self.title) != self.nseries: + msg = ('The length of `title` must equal the number ' + 'of columns if using `title` of type `list` ' + 'and `subplots=True`.\n' + 'length of title = {}\n' + 'number of columns = {}').format( + len(self.title), self.nseries) + raise ValueError(msg) + + for (ax, title) in zip(self.axes, self.title): + ax.set_title(title) + else: + self.fig.suptitle(self.title) else: + if is_list_like(self.title): + msg = ('Using `title` of type `list` is not supported ' + 'unless `subplots=True` is passed') + raise ValueError(msg) self.axes[0].set_title(self.title) def _apply_axis_properties(self, axis, rot=None, fontsize=None): @@ -2555,8 +2572,10 @@ def _plot(data, x=None, y=None, subplots=False, figsize : a tuple (width, height) in inches use_index : boolean, default True Use index as ticks for x axis - title : string - Title to use for the plot + title : string or list + Title to use for the plot. If a string is passed, print the string at + the top of the figure. If a list is passed and `subplots` is True, + print each item in the list above the corresponding subplot. grid : boolean, default None (matlab style default) Axis grid lines legend : False/True/'reverse'