Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: plot functions accept multiple axes and layout kw #7736

Merged
merged 1 commit into from
Aug 19, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/source/v0.15.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,9 @@ Enhancements
~~~~~~~~~~~~
- Added support for bool, uint8, uint16 and uint32 datatypes in ``to_stata`` (:issue:`7097`, :issue:`7365`)

- Added ``layout`` keyword to ``DataFrame.plot`` (:issue:`6667`)
- Allow to pass multiple axes to ``DataFrame.plot``, ``hist`` and ``boxplot`` (:issue:`5353`, :issue:`6970`, :issue:`7069`)


- ``PeriodIndex`` supports ``resolution`` as the same as ``DatetimeIndex`` (:issue:`7708`)
- ``pandas.tseries.holiday`` has added support for additional holidays and ways to observe holidays (:issue:`7070`)
Expand Down
45 changes: 38 additions & 7 deletions doc/source/visualization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -946,10 +946,41 @@ with the ``subplots`` keyword:
@savefig frame_plot_subplots.png
df.plot(subplots=True, figsize=(6, 6));

Targeting Different Subplots
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using Layout and Targetting Multiple Axes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you take this heading out on purpose?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This section described to plotting DataFrame as subplot splitting by column. I thought it is categorized in subplot section.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this in a separate sub-section, maybe: Using layout? (or something like that)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Because there are 2 methods (layout and multiple axes) to specify the subplots layout, added the section named so.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good thanks

You can pass an ``ax`` argument to :meth:`Series.plot` to plot on a particular axis:
The layout of subplots can be specified by ``layout`` keyword. It can accept
``(rows, columns)``. The ``layout`` keyword can be used in
``hist`` and ``boxplot`` also. If input is invalid, ``ValueError`` will be raised.

The number of axes which can be contained by rows x columns specified by ``layout`` must be
larger than the number of required subplots. If layout can contain more axes than required,
blank axes are not drawn.

.. ipython:: python

@savefig frame_plot_subplots_layout.png
df.plot(subplots=True, layout=(2, 3), figsize=(6, 6));

Also, you can pass multiple axes created beforehand as list-like via ``ax`` keyword.
This allows to use more complicated layout.
The passed axes must be the same number as the subplots being drawn.

When multiple axes are passed via ``ax`` keyword, ``layout``, ``sharex`` and ``sharey`` keywords are ignored.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we error catching when the keywords are passed (e.g. sharex,sharey,etc) and ax is specified? or is it matplotlib? tested?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No errors, but show warnings to be ignored (Because default of sharex and sharey are True).
https://github.com/sinhrks/pandas/blob/multi_ax/pandas/tools/plotting.py#L2952

Will add a test for this.

These must be configured when creating axes.

.. ipython:: python

fig, axes = plt.subplots(4, 4, figsize=(6, 6));
plt.adjust_subplots(wspace=0.5, hspace=0.5);
target1 = [axes[0][0], axes[1][1], axes[2][2], axes[3][3]]
target2 = [axes[3][0], axes[2][1], axes[1][2], axes[0][3]]

df.plot(subplots=True, ax=target1, legend=False);
@savefig frame_plot_subplots_multi_ax.png
(-df).plot(subplots=True, ax=target2, legend=False);

Another option is passing an ``ax`` argument to :meth:`Series.plot` to plot on a particular axis:

.. ipython:: python
:suppress:
Expand All @@ -964,12 +995,12 @@ You can pass an ``ax`` argument to :meth:`Series.plot` to plot on a particular a
.. ipython:: python

fig, axes = plt.subplots(nrows=2, ncols=2)
df['A'].plot(ax=axes[0,0]); axes[0,0].set_title('A')
df['B'].plot(ax=axes[0,1]); axes[0,1].set_title('B')
df['C'].plot(ax=axes[1,0]); axes[1,0].set_title('C')
df['A'].plot(ax=axes[0,0]); axes[0,0].set_title('A');
df['B'].plot(ax=axes[0,1]); axes[0,1].set_title('B');
df['C'].plot(ax=axes[1,0]); axes[1,0].set_title('C');

@savefig series_plot_multi.png
df['D'].plot(ax=axes[1,1]); axes[1,1].set_title('D')
df['D'].plot(ax=axes[1,1]); axes[1,1].set_title('D');

.. ipython:: python
:suppress:
Expand Down
142 changes: 134 additions & 8 deletions pandas/tests/test_graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ def test_hist_layout_with_by(self):
axes = _check_plot_works(df.height.hist, by=df.classroom, layout=(2, 2))
self._check_axes_shape(axes, axes_num=3, layout=(2, 2))

axes = _check_plot_works(df.height.hist, by=df.category, layout=(4, 2), figsize=(12, 7))
axes = df.height.hist(by=df.category, layout=(4, 2), figsize=(12, 7))
self._check_axes_shape(axes, axes_num=4, layout=(4, 2), figsize=(12, 7))

@slow
Expand Down Expand Up @@ -1071,6 +1071,7 @@ def test_subplots(self):
for kind in ['bar', 'barh', 'line', 'area']:
axes = df.plot(kind=kind, subplots=True, sharex=True, legend=True)
self._check_axes_shape(axes, axes_num=3, layout=(3, 1))
self.assertEqual(axes.shape, (3, ))

for ax, column in zip(axes, df.columns):
self._check_legend_labels(ax, labels=[com.pprint_thing(column)])
Expand Down Expand Up @@ -1133,6 +1134,77 @@ def test_subplots_timeseries(self):
self._check_visible(ax.get_yticklabels())
self._check_ticks_props(ax, xlabelsize=7, xrot=45)

def test_subplots_layout(self):
# GH 6667
df = DataFrame(np.random.rand(10, 3),
index=list(string.ascii_letters[:10]))

axes = df.plot(subplots=True, layout=(2, 2))
self._check_axes_shape(axes, axes_num=3, layout=(2, 2))
self.assertEqual(axes.shape, (2, 2))

axes = df.plot(subplots=True, layout=(1, 4))
self._check_axes_shape(axes, axes_num=3, layout=(1, 4))
self.assertEqual(axes.shape, (1, 4))

with tm.assertRaises(ValueError):
axes = df.plot(subplots=True, layout=(1, 1))

# single column
df = DataFrame(np.random.rand(10, 1),
index=list(string.ascii_letters[:10]))
axes = df.plot(subplots=True)
self._check_axes_shape(axes, axes_num=1, layout=(1, 1))
self.assertEqual(axes.shape, (1, ))

axes = df.plot(subplots=True, layout=(3, 3))
self._check_axes_shape(axes, axes_num=1, layout=(3, 3))
self.assertEqual(axes.shape, (3, 3))

@slow
def test_subplots_multiple_axes(self):
# GH 5353, 6970, GH 7069
fig, axes = self.plt.subplots(2, 3)
df = DataFrame(np.random.rand(10, 3),
index=list(string.ascii_letters[:10]))

returned = df.plot(subplots=True, ax=axes[0])
self._check_axes_shape(returned, axes_num=3, layout=(1, 3))
self.assertEqual(returned.shape, (3, ))
self.assertIs(returned[0].figure, fig)
# draw on second row
returned = df.plot(subplots=True, ax=axes[1])
self._check_axes_shape(returned, axes_num=3, layout=(1, 3))
self.assertEqual(returned.shape, (3, ))
self.assertIs(returned[0].figure, fig)
self._check_axes_shape(axes, axes_num=6, layout=(2, 3))
tm.close()

with tm.assertRaises(ValueError):
fig, axes = self.plt.subplots(2, 3)
# pass different number of axes from required
df.plot(subplots=True, ax=axes)

# pass 2-dim axes and invalid layout
# invalid lauout should not affect to input and return value
# (show warning is tested in
# TestDataFrameGroupByPlots.test_grouped_box_multiple_axes
fig, axes = self.plt.subplots(2, 2)
df = DataFrame(np.random.rand(10, 4),
index=list(string.ascii_letters[:10]))

returned = df.plot(subplots=True, ax=axes, layout=(2, 1))
self._check_axes_shape(returned, axes_num=4, layout=(2, 2))
self.assertEqual(returned.shape, (4, ))

# single column
fig, axes = self.plt.subplots(1, 1)
df = DataFrame(np.random.rand(10, 1),
index=list(string.ascii_letters[:10]))
axes = df.plot(subplots=True, ax=[axes])
self._check_axes_shape(axes, axes_num=1, layout=(1, 1))
self.assertEqual(axes.shape, (1, ))

def test_negative_log(self):
df = - DataFrame(rand(6, 4),
index=list(string.ascii_letters[:6]),
Expand Down Expand Up @@ -1718,15 +1790,15 @@ def test_hist_df_coord(self):
normal_df = DataFrame({'A': np.repeat(np.array([1, 2, 3, 4, 5]),
np.array([10, 9, 8, 7, 6])),
'B': np.repeat(np.array([1, 2, 3, 4, 5]),
np.array([8, 8, 8, 8, 8])),
np.array([8, 8, 8, 8, 8])),
'C': np.repeat(np.array([1, 2, 3, 4, 5]),
np.array([6, 7, 8, 9, 10]))},
columns=['A', 'B', 'C'])

nan_df = DataFrame({'A': np.repeat(np.array([np.nan, 1, 2, 3, 4, 5]),
np.array([3, 10, 9, 8, 7, 6])),
'B': np.repeat(np.array([1, np.nan, 2, 3, 4, 5]),
np.array([8, 3, 8, 8, 8, 8])),
np.array([8, 3, 8, 8, 8, 8])),
'C': np.repeat(np.array([1, 2, 3, np.nan, 4, 5]),
np.array([6, 7, 8, 3, 9, 10]))},
columns=['A', 'B', 'C'])
Expand Down Expand Up @@ -2712,6 +2784,41 @@ def test_grouped_box_layout(self):
return_type='dict')
self._check_axes_shape(self.plt.gcf().axes, axes_num=3, layout=(1, 4))

@slow
def test_grouped_box_multiple_axes(self):
# GH 6970, GH 7069
df = self.hist_df

# check warning to ignore sharex / sharey
# this check should be done in the first function which
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a way to reset the warnings filter: https://docs.python.org/2/library/warnings.html

I've never done it properly, whenever I'm testing I'll just reset the global __warningregistry__ to {}.

# passes multiple axes to plot, hist or boxplot
# location should be changed if other test is added
# which has earlier alphabetical order
with tm.assert_produces_warning(UserWarning):
fig, axes = self.plt.subplots(2, 2)
df.groupby('category').boxplot(column='height', return_type='axes', ax=axes)
self._check_axes_shape(self.plt.gcf().axes, axes_num=4, layout=(2, 2))

fig, axes = self.plt.subplots(2, 3)
returned = df.boxplot(column=['height', 'weight', 'category'], by='gender',
return_type='axes', ax=axes[0])
returned = np.array(returned.values())
self._check_axes_shape(returned, axes_num=3, layout=(1, 3))
self.assert_numpy_array_equal(returned, axes[0])
self.assertIs(returned[0].figure, fig)
# draw on second row
returned = df.groupby('classroom').boxplot(column=['height', 'weight', 'category'],
return_type='axes', ax=axes[1])
returned = np.array(returned.values())
self._check_axes_shape(returned, axes_num=3, layout=(1, 3))
self.assert_numpy_array_equal(returned, axes[1])
self.assertIs(returned[0].figure, fig)

with tm.assertRaises(ValueError):
fig, axes = self.plt.subplots(2, 3)
# pass different number of axes from required
axes = df.groupby('classroom').boxplot(ax=axes)

@slow
def test_grouped_hist_layout(self):

Expand All @@ -2724,12 +2831,12 @@ def test_grouped_hist_layout(self):
axes = _check_plot_works(df.hist, column='height', by=df.gender, layout=(2, 1))
self._check_axes_shape(axes, axes_num=2, layout=(2, 1))

axes = _check_plot_works(df.hist, column='height', by=df.category, layout=(4, 1))
axes = df.hist(column='height', by=df.category, layout=(4, 1))
self._check_axes_shape(axes, axes_num=4, layout=(4, 1))

axes = _check_plot_works(df.hist, column='height', by=df.category,
layout=(4, 2), figsize=(12, 8))
axes = df.hist(column='height', by=df.category, layout=(4, 2), figsize=(12, 8))
self._check_axes_shape(axes, axes_num=4, layout=(4, 2), figsize=(12, 8))
tm.close()

# GH 6769
axes = _check_plot_works(df.hist, column='height', by='classroom', layout=(2, 2))
Expand All @@ -2739,13 +2846,32 @@ def test_grouped_hist_layout(self):
axes = _check_plot_works(df.hist, by='classroom')
self._check_axes_shape(axes, axes_num=3, layout=(2, 2))

axes = _check_plot_works(df.hist, by='gender', layout=(3, 5))
axes = df.hist(by='gender', layout=(3, 5))
self._check_axes_shape(axes, axes_num=2, layout=(3, 5))

axes = _check_plot_works(df.hist, column=['height', 'weight', 'category'])
axes = df.hist(column=['height', 'weight', 'category'])
self._check_axes_shape(axes, axes_num=3, layout=(2, 2))

@slow
def test_grouped_hist_multiple_axes(self):
# GH 6970, GH 7069
df = self.hist_df

fig, axes = self.plt.subplots(2, 3)
returned = df.hist(column=['height', 'weight', 'category'], ax=axes[0])
self._check_axes_shape(returned, axes_num=3, layout=(1, 3))
self.assert_numpy_array_equal(returned, axes[0])
self.assertIs(returned[0].figure, fig)
returned = df.hist(by='classroom', ax=axes[1])
self._check_axes_shape(returned, axes_num=3, layout=(1, 3))
self.assert_numpy_array_equal(returned, axes[1])
self.assertIs(returned[0].figure, fig)

with tm.assertRaises(ValueError):
fig, axes = self.plt.subplots(2, 3)
# pass different number of axes from required
axes = df.hist(column='height', ax=axes)
@slow
def test_axis_share_x(self):
df = self.hist_df
# GH4089
Expand Down
Loading