-
-
Notifications
You must be signed in to change notification settings - Fork 18.1k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No errors, but show warnings to be ignored (Because default of 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: | ||
|
@@ -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: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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)]) | ||
|
@@ -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]), | ||
|
@@ -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']) | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
# 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): | ||
|
||
|
@@ -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)) | ||
|
@@ -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 | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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)There was a problem hiding this comment.
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.