From 3f133acb922d90053ebc6712d9cbd7c3dbcc4425 Mon Sep 17 00:00:00 2001 From: bmagnusson Date: Fri, 25 Nov 2016 17:27:58 -0800 Subject: [PATCH 01/12] Add logic such that if 'title' is a list and 'subplots' is True, use each item of the list as the title of the individual subplots. --- pandas/tools/plotting.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index d46dc4d355b4c..98abb2c30c373 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -1217,7 +1217,11 @@ def _adorn_subplots(self): if self.title: if self.subplots: - self.fig.suptitle(self.title) + if type(self.title) == list: + for (ax, title) in zip(self.axes, self.title): + ax.set_title(title) + else: + self.fig.suptitle(self.title) else: self.axes[0].set_title(self.title) From 55f466797e83424198b1b48a84ef8c04a077c2ba Mon Sep 17 00:00:00 2001 From: bmagnusson Date: Sat, 26 Nov 2016 12:04:26 -0800 Subject: [PATCH 02/12] ENH: Add the ability to have a separate title for each subplot when plotting. --- pandas/tools/plotting.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 98abb2c30c373..1a31ea31fad0f 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -2559,8 +2559,12 @@ 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 + If a string is passed, print the string at the top of the figure. If a + list of strings is passed and subplots is True, print the the first + string above the first subplot, the second string above the second + subplot, and so forth until the end of the list is reached or there are + no more subplots. grid : boolean, default None (matlab style default) Axis grid lines legend : False/True/'reverse' From ecb9453b85559bf225f37f0dd98b2c57361da706 Mon Sep 17 00:00:00 2001 From: bmagnusson Date: Mon, 28 Nov 2016 15:03:28 -0800 Subject: [PATCH 03/12] switch to using 'is_list_like' --- pandas/tools/plotting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 1a31ea31fad0f..cf828835df2e5 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -1217,7 +1217,7 @@ def _adorn_subplots(self): if self.title: if self.subplots: - if type(self.title) == list: + if is_list_like(self.title): for (ax, title) in zip(self.axes, self.title): ax.set_title(title) else: From d6d1b0ce4155ebbb5eca9ccb129f197432d0f3cf Mon Sep 17 00:00:00 2001 From: bmagnusson Date: Mon, 28 Nov 2016 15:05:17 -0800 Subject: [PATCH 04/12] add line describing the new titles for subplots feature --- doc/source/whatsnew/v0.20.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt index 847583e1871f9..bee47751a614a 100644 --- a/doc/source/whatsnew/v0.20.0.txt +++ b/doc/source/whatsnew/v0.20.0.txt @@ -40,7 +40,7 @@ Other enhancements ^^^^^^^^^^^^^^^^^^ - ``pd.read_excel`` now preserves sheet order when using ``sheetname=None`` (:issue:`9930`) - +- ``pd.DataFrame.plot`` now prints a title above each subplot if ``suplots=True`` and ``title`` is a list of strings .. _whatsnew_0200.api_breaking: From 94ea2d30a57d26488b5b1bdeb46875af5f00fb7c Mon Sep 17 00:00:00 2001 From: bmagnusson Date: Mon, 28 Nov 2016 15:07:39 -0800 Subject: [PATCH 05/12] add tests to check if the titles above subplots are correct if 'subplots' == True and 'title' is a list --- pandas/tests/plotting/test_misc.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/pandas/tests/plotting/test_misc.py b/pandas/tests/plotting/test_misc.py index a484217da5969..15eec4122b23d 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,24 @@ 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.title._text for p in plot], title) + + # Case len(title) > len(df) + plot = df.plot(subplots=True, title=title + ['Ignore me!']) + self.assertEqual([p.title._text for p in plot], title) + + # Case len(title) < len(df) + plot = df.plot(subplots=True, title=title[:2]) + self.assertEqual([p.title._text for p in plot], title[:2] + ['', '']) + if __name__ == '__main__': nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'], From eb43f258f21698ca27577e80ba5c2ac68b4f63c3 Mon Sep 17 00:00:00 2001 From: bmagnusson Date: Mon, 28 Nov 2016 17:47:40 -0800 Subject: [PATCH 06/12] -raise ValueError if len(title) != number of columns. -raise ValueError if subplots=False and title is of type list --- pandas/tests/plotting/test_misc.py | 10 ++++++---- pandas/tools/plotting.py | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/pandas/tests/plotting/test_misc.py b/pandas/tests/plotting/test_misc.py index 15eec4122b23d..df43eb30a822c 100644 --- a/pandas/tests/plotting/test_misc.py +++ b/pandas/tests/plotting/test_misc.py @@ -285,12 +285,14 @@ def test_subplot_titles(self): self.assertEqual([p.title._text for p in plot], title) # Case len(title) > len(df) - plot = df.plot(subplots=True, title=title + ['Ignore me!']) - self.assertEqual([p.title._text for p in plot], title) + self.assertRaises(ValueError, df.plot, subplots=True, + title=title + ["kittens > puppies"]) # Case len(title) < len(df) - plot = df.plot(subplots=True, title=title[:2]) - self.assertEqual([p.title._text for p in plot], title[:2] + ['', '']) + 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) if __name__ == '__main__': diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index cf828835df2e5..6f7ef3d527bb8 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -1218,11 +1218,21 @@ def _adorn_subplots(self): if self.title: if self.subplots: if is_list_like(self.title): + if len(self.title) != len(self.axes): + msg = 'The length of `title` must equal the number ' \ + 'of columns if using `title` of type `list` ' \ + 'and `subplots=True`' + 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): @@ -2561,10 +2571,8 @@ def _plot(data, x=None, y=None, subplots=False, Use index as ticks for x axis title : string or list If a string is passed, print the string at the top of the figure. If a - list of strings is passed and subplots is True, print the the first - string above the first subplot, the second string above the second - subplot, and so forth until the end of the list is reached or there are - no more subplots. + 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' From 5586a966ac7d4b265fe755d15836123be3c946e0 Mon Sep 17 00:00:00 2001 From: bmagnusson Date: Tue, 6 Dec 2016 18:42:51 -0800 Subject: [PATCH 07/12] added :issue:`14753` --- doc/source/whatsnew/v0.20.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt index bee47751a614a..26704b2d77a4e 100644 --- a/doc/source/whatsnew/v0.20.0.txt +++ b/doc/source/whatsnew/v0.20.0.txt @@ -40,7 +40,7 @@ Other enhancements ^^^^^^^^^^^^^^^^^^ - ``pd.read_excel`` now preserves sheet order when using ``sheetname=None`` (:issue:`9930`) -- ``pd.DataFrame.plot`` now prints a title above each subplot if ``suplots=True`` and ``title`` is a list of strings +- ``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: From aa5bb9840a37a4f02db679f00ac856e58347cde2 Mon Sep 17 00:00:00 2001 From: bmagnusson Date: Tue, 6 Dec 2016 18:44:13 -0800 Subject: [PATCH 08/12] -use self.nseries instead of len(self.axes) -print out length of title and number of columns in error message --- pandas/tests/plotting/test_misc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/tests/plotting/test_misc.py b/pandas/tests/plotting/test_misc.py index df43eb30a822c..1f8486229daba 100644 --- a/pandas/tests/plotting/test_misc.py +++ b/pandas/tests/plotting/test_misc.py @@ -294,6 +294,12 @@ def test_subplot_titles(self): # 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.title._text 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'], From 2059339f000f99e0e88acc67e749bf2704957a27 Mon Sep 17 00:00:00 2001 From: bmagnusson Date: Tue, 6 Dec 2016 18:45:43 -0800 Subject: [PATCH 09/12] -use self.nseries instead of len(self.axes) -print out length of title and number of columns in error message -added test case for when layout=(2,2) but number of columns=3 --- pandas/tools/plotting.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 6f7ef3d527bb8..bb6a3e92a7158 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -1218,10 +1218,13 @@ def _adorn_subplots(self): if self.title: if self.subplots: if is_list_like(self.title): - if len(self.title) != len(self.axes): + 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`' + '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): From 301cc7d40d7df0d1caee27c06f124cfb9712bc8e Mon Sep 17 00:00:00 2001 From: bmagnusson Date: Tue, 6 Dec 2016 19:48:57 -0800 Subject: [PATCH 10/12] use .get_title() instead of .title._text --- pandas/tests/plotting/test_misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/plotting/test_misc.py b/pandas/tests/plotting/test_misc.py index 1f8486229daba..6c313f5937602 100644 --- a/pandas/tests/plotting/test_misc.py +++ b/pandas/tests/plotting/test_misc.py @@ -282,7 +282,7 @@ def test_subplot_titles(self): # Case len(title) == len(df) plot = df.plot(subplots=True, title=title) - self.assertEqual([p.title._text for p in plot], title) + self.assertEqual([p.get_title() for p in plot], title) # Case len(title) > len(df) self.assertRaises(ValueError, df.plot, subplots=True, @@ -297,7 +297,7 @@ def test_subplot_titles(self): # 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.title._text for sublist in plot for ax in sublist] + title_list = [ax.get_title() for sublist in plot for ax in sublist] self.assertEqual(title_list, title[:3] + ['']) From 5b8895172251c148b90987e9d3cf27c85a15b145 Mon Sep 17 00:00:00 2001 From: bmagnusson Date: Wed, 7 Dec 2016 17:05:31 -0800 Subject: [PATCH 11/12] -Add 'Title to use for the plot' as the first sentence for the docstring. -A few other stylistic changes for consistency. --- pandas/tools/plotting.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index bb6a3e92a7158..097efe7d8d02d 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -1219,12 +1219,12 @@ def _adorn_subplots(self): if self.subplots: 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) + 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): @@ -1233,8 +1233,8 @@ def _adorn_subplots(self): 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' + msg = ('Using `title` of type `list` is not supported ' + 'unless `subplots=True` is passed') raise ValueError(msg) self.axes[0].set_title(self.title) @@ -2573,9 +2573,9 @@ def _plot(data, x=None, y=None, subplots=False, use_index : boolean, default True Use index as ticks for x axis title : string or list - 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. + 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' From 59ab880d3de2e52eff5711af4f13a1151d40eb9c Mon Sep 17 00:00:00 2001 From: bmagnusson Date: Thu, 8 Dec 2016 11:03:45 -0800 Subject: [PATCH 12/12] Fix 'continuation line under-indented for visual indent' found by linting. --- pandas/tools/plotting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 1a61a199086b4..21e8b64a3656a 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -1234,7 +1234,7 @@ def _adorn_subplots(self): else: if is_list_like(self.title): msg = ('Using `title` of type `list` is not supported ' - 'unless `subplots=True` is passed') + 'unless `subplots=True` is passed') raise ValueError(msg) self.axes[0].set_title(self.title)