Skip to content

Commit 587e410

Browse files
author
Tom Augspurger
committed
Merge pull request #9740 from JanSchulz/plotting_with_ax
Plotting: don't change visibility of xaxis labels and ticklabels if passing in a axis
2 parents a004c59 + 015fc62 commit 587e410

File tree

3 files changed

+183
-44
lines changed

3 files changed

+183
-44
lines changed

doc/source/whatsnew/v0.16.1.txt

+8
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ API changes
3131

3232

3333

34+
- When passing in an ax to ``df.plot( ..., ax=ax)``, the `sharex` kwarg will now default to `False`.
35+
The result is that the visibility of xlabels and xticklabels will not anymore be changed. You
36+
have to do that by yourself for the right axes in your figure or set ``sharex=True`` explicitly
37+
(but this changes the visible for all axes in the figure, not only the one which is passed in!).
38+
If pandas creates the subplots itself (e.g. no passed in `ax` kwarg), then the
39+
default is still ``sharex=True`` and the visibility changes are applied.
40+
41+
3442

3543
- Add support for separating years and quarters using dashes, for
3644
example 2014-Q1. (:issue:`9688`)

pandas/tests/test_graphics.py

+92-1
Original file line numberDiff line numberDiff line change
@@ -1000,8 +1000,14 @@ def test_plot(self):
10001000
_check_plot_works(df.plot, xticks=[1, 5, 10])
10011001
_check_plot_works(df.plot, ylim=(-100, 100), xlim=(-100, 100))
10021002

1003-
axes = _check_plot_works(df.plot, subplots=True, title='blah')
1003+
_check_plot_works(df.plot, subplots=True, title='blah')
1004+
# We have to redo it here because _check_plot_works does two plots, once without an ax
1005+
# kwarg and once with an ax kwarg and the new sharex behaviour does not remove the
1006+
# visibility of the latter axis (as ax is present).
1007+
# see: https://github.com/pydata/pandas/issues/9737
1008+
axes = df.plot(subplots=True, title='blah')
10041009
self._check_axes_shape(axes, axes_num=3, layout=(3, 1))
1010+
#axes[0].figure.savefig("test.png")
10051011
for ax in axes[:2]:
10061012
self._check_visible(ax.xaxis) # xaxis must be visible for grid
10071013
self._check_visible(ax.get_xticklabels(), visible=False)
@@ -3146,6 +3152,78 @@ def _check_errorbar_color(containers, expected, has_err='has_xerr'):
31463152
self._check_has_errorbars(ax, xerr=0, yerr=1)
31473153
_check_errorbar_color(ax.containers, 'green', has_err='has_yerr')
31483154

3155+
def test_sharex_and_ax(self):
3156+
# https://github.com/pydata/pandas/issues/9737
3157+
# using gridspec, the axis in fig.get_axis() are sorted differently than pandas expected
3158+
# them, so make sure that only the right ones are removed
3159+
import matplotlib.pyplot as plt
3160+
plt.close('all')
3161+
gs, axes = _generate_4_axes_via_gridspec()
3162+
3163+
df = DataFrame({"a":[1,2,3,4,5,6], "b":[1,2,3,4,5,6]})
3164+
3165+
for ax in axes:
3166+
df.plot(x="a", y="b", title="title", ax=ax, sharex=True)
3167+
3168+
gs.tight_layout(plt.gcf())
3169+
for ax in plt.gcf().get_axes():
3170+
for label in ax.get_xticklabels():
3171+
self.assertEqual(label.get_visible(), ax.is_last_row(),
3172+
"x ticklabel has wrong visiblity")
3173+
self.assertEqual(ax.xaxis.get_label().get_visible(), ax.is_last_row(),
3174+
"x label has wrong visiblity")
3175+
3176+
plt.close('all')
3177+
gs, axes = _generate_4_axes_via_gridspec()
3178+
# without sharex, no labels should be touched!
3179+
for ax in axes:
3180+
df.plot(x="a", y="b", title="title", ax=ax)
3181+
3182+
gs.tight_layout(plt.gcf())
3183+
for ax in plt.gcf().get_axes():
3184+
for label in ax.get_xticklabels():
3185+
self.assertTrue(label.get_visible(), "x ticklabel is invisible but shouldn't")
3186+
self.assertTrue(ax.xaxis.get_label().get_visible(),
3187+
"x label is invisible but shouldn't")
3188+
3189+
3190+
def test_sharey_and_ax(self):
3191+
# https://github.com/pydata/pandas/issues/9737
3192+
# using gridspec, the axis in fig.get_axis() are sorted differently than pandas expected
3193+
# them, so make sure that only the right ones are removed
3194+
import matplotlib.pyplot as plt
3195+
3196+
plt.close('all')
3197+
gs, axes = _generate_4_axes_via_gridspec()
3198+
3199+
df = DataFrame({"a":[1,2,3,4,5,6], "b":[1,2,3,4,5,6]})
3200+
3201+
for ax in axes:
3202+
df.plot(x="a", y="b", title="title", ax=ax, sharey=True)
3203+
3204+
gs.tight_layout(plt.gcf())
3205+
for ax in plt.gcf().get_axes():
3206+
for label in ax.get_yticklabels():
3207+
self.assertEqual(label.get_visible(), ax.is_first_col(),
3208+
"y ticklabel has wrong visiblity")
3209+
self.assertEqual(ax.yaxis.get_label().get_visible(), ax.is_first_col(),
3210+
"y label has wrong visiblity")
3211+
3212+
plt.close('all')
3213+
gs, axes = _generate_4_axes_via_gridspec()
3214+
3215+
# without sharex, no labels should be touched!
3216+
for ax in axes:
3217+
df.plot(x="a", y="b", title="title", ax=ax)
3218+
3219+
gs.tight_layout(plt.gcf())
3220+
for ax in plt.gcf().get_axes():
3221+
for label in ax.get_yticklabels():
3222+
self.assertTrue(label.get_visible(), "y ticklabel is invisible but shouldn't")
3223+
self.assertTrue(ax.yaxis.get_label().get_visible(),
3224+
"y label is invisible but shouldn't")
3225+
3226+
31493227

31503228
@tm.mplskip
31513229
class TestDataFrameGroupByPlots(TestPlotBase):
@@ -3620,6 +3698,19 @@ def _check_plot_works(f, *args, **kwargs):
36203698

36213699
return ret
36223700

3701+
def _generate_4_axes_via_gridspec():
3702+
import matplotlib.pyplot as plt
3703+
import matplotlib as mpl
3704+
import matplotlib.gridspec
3705+
3706+
gs = mpl.gridspec.GridSpec(2, 2)
3707+
ax_tl = plt.subplot(gs[0,0])
3708+
ax_ll = plt.subplot(gs[1,0])
3709+
ax_tr = plt.subplot(gs[0,1])
3710+
ax_lr = plt.subplot(gs[1,1])
3711+
3712+
return gs, [ax_tl, ax_ll, ax_tr, ax_lr]
3713+
36233714

36243715
def curpath():
36253716
pth, _ = os.path.split(os.path.abspath(__file__))

pandas/tools/plotting.py

+83-43
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,7 @@ class MPLPlot(object):
769769
_attr_defaults = {'logy': False, 'logx': False, 'loglog': False,
770770
'mark_right': True, 'stacked': False}
771771

772-
def __init__(self, data, kind=None, by=None, subplots=False, sharex=True,
772+
def __init__(self, data, kind=None, by=None, subplots=False, sharex=None,
773773
sharey=False, use_index=True,
774774
figsize=None, grid=None, legend=True, rot=None,
775775
ax=None, fig=None, title=None, xlim=None, ylim=None,
@@ -786,7 +786,16 @@ def __init__(self, data, kind=None, by=None, subplots=False, sharex=True,
786786
self.sort_columns = sort_columns
787787

788788
self.subplots = subplots
789-
self.sharex = sharex
789+
790+
if sharex is None:
791+
if ax is None:
792+
self.sharex = True
793+
else:
794+
# if we get an axis, the users should do the visibility setting...
795+
self.sharex = False
796+
else:
797+
self.sharex = sharex
798+
790799
self.sharey = sharey
791800
self.figsize = figsize
792801
self.layout = layout
@@ -2350,10 +2359,14 @@ def _plot(data, x=None, y=None, subplots=False,
23502359
df_ax = """ax : matplotlib axes object, default None
23512360
subplots : boolean, default False
23522361
Make separate subplots for each column
2353-
sharex : boolean, default True
2354-
In case subplots=True, share x axis
2362+
sharex : boolean, default True if ax is None else False
2363+
In case subplots=True, share x axis and set some x axis labels to
2364+
invisible; defaults to True if ax is None otherwise False if an ax
2365+
is passed in; Be aware, that passing in both an ax and sharex=True
2366+
will alter all x axis labels for all axis in a figure!
23552367
sharey : boolean, default False
2356-
In case subplots=True, share y axis
2368+
In case subplots=True, share y axis and set some y axis labels to
2369+
invisible
23572370
layout : tuple (optional)
23582371
(rows, columns) for the layout of subplots"""
23592372
series_ax = """ax : matplotlib axes object
@@ -2465,7 +2478,7 @@ def _plot(data, x=None, y=None, subplots=False,
24652478

24662479
@Appender(_shared_docs['plot'] % _shared_doc_df_kwargs)
24672480
def plot_frame(data, x=None, y=None, kind='line', ax=None, # Dataframe unique
2468-
subplots=False, sharex=True, sharey=False, layout=None, # Dataframe unique
2481+
subplots=False, sharex=None, sharey=False, layout=None, # Dataframe unique
24692482
figsize=None, use_index=True, title=None, grid=None,
24702483
legend=True, style=None, logx=False, logy=False, loglog=False,
24712484
xticks=None, yticks=None, xlim=None, ylim=None,
@@ -2730,8 +2743,14 @@ def hist_frame(data, column=None, by=None, grid=True, xlabelsize=None,
27302743
yrot : float, default None
27312744
rotation of y axis labels
27322745
ax : matplotlib axes object, default None
2733-
sharex : bool, if True, the X axis will be shared amongst all subplots.
2734-
sharey : bool, if True, the Y axis will be shared amongst all subplots.
2746+
sharex : boolean, default True if ax is None else False
2747+
In case subplots=True, share x axis and set some x axis labels to
2748+
invisible; defaults to True if ax is None otherwise False if an ax
2749+
is passed in; Be aware, that passing in both an ax and sharex=True
2750+
will alter all x axis labels for all subplots in a figure!
2751+
sharey : boolean, default False
2752+
In case subplots=True, share y axis and set some y axis labels to
2753+
invisible
27352754
figsize : tuple
27362755
The size of the figure to create in inches by default
27372756
layout: (optional) a tuple (rows, columns) for the layout of the histograms
@@ -3129,7 +3148,8 @@ def _subplots(naxes=None, sharex=False, sharey=False, squeeze=True,
31293148
Keyword arguments:
31303149
31313150
naxes : int
3132-
Number of required axes. Exceeded axes are set invisible. Default is nrows * ncols.
3151+
Number of required axes. Exceeded axes are set invisible. Default is
3152+
nrows * ncols.
31333153
31343154
sharex : bool
31353155
If True, the X axis will be shared amongst all subplots.
@@ -3256,12 +3276,12 @@ def _subplots(naxes=None, sharex=False, sharey=False, squeeze=True,
32563276
ax = fig.add_subplot(nrows, ncols, i + 1, **kwds)
32573277
axarr[i] = ax
32583278

3259-
_handle_shared_axes(axarr, nplots, naxes, nrows, ncols, sharex, sharey)
3260-
32613279
if naxes != nplots:
32623280
for ax in axarr[naxes:]:
32633281
ax.set_visible(False)
32643282

3283+
_handle_shared_axes(axarr, nplots, naxes, nrows, ncols, sharex, sharey)
3284+
32653285
if squeeze:
32663286
# Reshape the array to have the final desired dimension (nrow,ncol),
32673287
# though discarding unneeded dimensions that equal 1. If we only have
@@ -3276,44 +3296,64 @@ def _subplots(naxes=None, sharex=False, sharey=False, squeeze=True,
32763296

32773297
return fig, axes
32783298

3299+
def _remove_xlabels_from_axis(ax):
3300+
for label in ax.get_xticklabels():
3301+
label.set_visible(False)
3302+
try:
3303+
# set_visible will not be effective if
3304+
# minor axis has NullLocator and NullFormattor (default)
3305+
import matplotlib.ticker as ticker
3306+
3307+
if isinstance(ax.xaxis.get_minor_locator(), ticker.NullLocator):
3308+
ax.xaxis.set_minor_locator(ticker.AutoLocator())
3309+
if isinstance(ax.xaxis.get_minor_formatter(), ticker.NullFormatter):
3310+
ax.xaxis.set_minor_formatter(ticker.FormatStrFormatter(''))
3311+
for label in ax.get_xticklabels(minor=True):
3312+
label.set_visible(False)
3313+
except Exception: # pragma no cover
3314+
pass
3315+
ax.xaxis.get_label().set_visible(False)
3316+
3317+
def _remove_ylables_from_axis(ax):
3318+
for label in ax.get_yticklabels():
3319+
label.set_visible(False)
3320+
try:
3321+
import matplotlib.ticker as ticker
3322+
if isinstance(ax.yaxis.get_minor_locator(), ticker.NullLocator):
3323+
ax.yaxis.set_minor_locator(ticker.AutoLocator())
3324+
if isinstance(ax.yaxis.get_minor_formatter(), ticker.NullFormatter):
3325+
ax.yaxis.set_minor_formatter(ticker.FormatStrFormatter(''))
3326+
for label in ax.get_yticklabels(minor=True):
3327+
label.set_visible(False)
3328+
except Exception: # pragma no cover
3329+
pass
3330+
ax.yaxis.get_label().set_visible(False)
32793331

32803332
def _handle_shared_axes(axarr, nplots, naxes, nrows, ncols, sharex, sharey):
32813333
if nplots > 1:
32823334

3335+
# first find out the ax layout, so that we can correctly handle 'gaps"
3336+
layout = np.zeros((nrows+1,ncols+1), dtype=np.bool)
3337+
for ax in axarr:
3338+
layout[ax.rowNum, ax.colNum] = ax.get_visible()
3339+
32833340
if sharex and nrows > 1:
3284-
for ax in axarr[:naxes][:-ncols]: # only bottom row
3285-
for label in ax.get_xticklabels():
3286-
label.set_visible(False)
3287-
try:
3288-
# set_visible will not be effective if
3289-
# minor axis has NullLocator and NullFormattor (default)
3290-
import matplotlib.ticker as ticker
3291-
3292-
if isinstance(ax.xaxis.get_minor_locator(), ticker.NullLocator):
3293-
ax.xaxis.set_minor_locator(ticker.AutoLocator())
3294-
if isinstance(ax.xaxis.get_minor_formatter(), ticker.NullFormatter):
3295-
ax.xaxis.set_minor_formatter(ticker.FormatStrFormatter(''))
3296-
for label in ax.get_xticklabels(minor=True):
3297-
label.set_visible(False)
3298-
except Exception: # pragma no cover
3299-
pass
3300-
ax.xaxis.get_label().set_visible(False)
3341+
for ax in axarr:
3342+
# only the last row of subplots should get x labels -> all other off
3343+
# layout handles the case that the subplot is the last in the column,
3344+
# because below is no subplot/gap.
3345+
if not layout[ax.rowNum+1, ax.colNum]:
3346+
continue
3347+
_remove_xlabels_from_axis(ax)
33013348
if sharey and ncols > 1:
3302-
for i, ax in enumerate(axarr):
3303-
if (i % ncols) != 0: # only first column
3304-
for label in ax.get_yticklabels():
3305-
label.set_visible(False)
3306-
try:
3307-
import matplotlib.ticker as ticker
3308-
if isinstance(ax.yaxis.get_minor_locator(), ticker.NullLocator):
3309-
ax.yaxis.set_minor_locator(ticker.AutoLocator())
3310-
if isinstance(ax.yaxis.get_minor_formatter(), ticker.NullFormatter):
3311-
ax.yaxis.set_minor_formatter(ticker.FormatStrFormatter(''))
3312-
for label in ax.get_yticklabels(minor=True):
3313-
label.set_visible(False)
3314-
except Exception: # pragma no cover
3315-
pass
3316-
ax.yaxis.get_label().set_visible(False)
3349+
for ax in axarr:
3350+
# only the first column should get y labels -> set all other to off
3351+
# as we only have labels in teh first column and we always have a subplot there,
3352+
# we can skip the layout test
3353+
if ax.is_first_col():
3354+
continue
3355+
_remove_ylables_from_axis(ax)
3356+
33173357

33183358

33193359
def _flatten(axes):

0 commit comments

Comments
 (0)