diff --git a/doc/api/next_api_changes/deprecations/28074-TS.rst b/doc/api/next_api_changes/deprecations/28074-TS.rst new file mode 100644 index 000000000000..6a8b5d4b21b8 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/28074-TS.rst @@ -0,0 +1,9 @@ +``boxplot`` and ``bxp`` *vert* parameter, and ``rcParams["boxplot.vertical"]`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The parameter *vert: bool* has been deprecated on `~.Axes.boxplot` and +`~.Axes.bxp`. It is replaced by *orientation: {"vertical", "horizontal"}* +for API consistency. + +``rcParams["boxplot.vertical"]``, which controlled the orientation of ``boxplot``, +is deprecated without replacement. diff --git a/doc/users/next_whats_new/boxplot_orientation.rst b/doc/users/next_whats_new/boxplot_orientation.rst new file mode 100644 index 000000000000..19193b530a9e --- /dev/null +++ b/doc/users/next_whats_new/boxplot_orientation.rst @@ -0,0 +1,21 @@ +``boxplot`` and ``bxp`` orientation parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Boxplots have a new parameter *orientation: {"vertical", "horizontal"}* +to change the orientation of the plot. This replaces the deprecated +*vert: bool* parameter. + + +.. plot:: + :include-source: true + :alt: Example of creating 4 horizontal boxplots. + + import matplotlib.pyplot as plt + import numpy as np + + fig, ax = plt.subplots() + np.random.seed(19680801) + all_data = [np.random.normal(0, std, 100) for std in range(6, 10)] + + ax.boxplot(all_data, orientation='horizontal') + plt.show() diff --git a/galleries/examples/statistics/boxplot_demo.py b/galleries/examples/statistics/boxplot_demo.py index f7f1078b2d27..46d6c7609807 100644 --- a/galleries/examples/statistics/boxplot_demo.py +++ b/galleries/examples/statistics/boxplot_demo.py @@ -46,11 +46,11 @@ axs[1, 0].set_title("don't show\noutlier points") # horizontal boxes -axs[1, 1].boxplot(data, sym='rs', vert=False) +axs[1, 1].boxplot(data, sym='rs', orientation='horizontal') axs[1, 1].set_title('horizontal boxes') # change whisker length -axs[1, 2].boxplot(data, sym='rs', vert=False, whis=0.75) +axs[1, 2].boxplot(data, sym='rs', orientation='horizontal', whis=0.75) axs[1, 2].set_title('change whisker length') fig.subplots_adjust(left=0.08, right=0.98, bottom=0.05, top=0.9, @@ -107,7 +107,7 @@ fig.canvas.manager.set_window_title('A Boxplot Example') fig.subplots_adjust(left=0.075, right=0.95, top=0.9, bottom=0.25) -bp = ax1.boxplot(data, notch=False, sym='+', vert=True, whis=1.5) +bp = ax1.boxplot(data, notch=False, sym='+', orientation='vertical', whis=1.5) plt.setp(bp['boxes'], color='black') plt.setp(bp['whiskers'], color='black') plt.setp(bp['fliers'], color='red', marker='+') diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 50540d862b5f..e7b484bc99a9 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3819,9 +3819,10 @@ def apply_mask(arrays, mask): @_api.make_keyword_only("3.9", "notch") @_preprocess_data() @_api.rename_parameter("3.9", "labels", "tick_labels") - def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, - positions=None, widths=None, patch_artist=None, - bootstrap=None, usermedians=None, conf_intervals=None, + def boxplot(self, x, notch=None, sym=None, vert=None, + orientation='vertical', whis=None, positions=None, + widths=None, patch_artist=None, bootstrap=None, + usermedians=None, conf_intervals=None, meanline=None, showmeans=None, showcaps=None, showbox=None, showfliers=None, boxprops=None, tick_labels=None, flierprops=None, medianprops=None, @@ -3877,9 +3878,21 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, the fliers. If `None`, then the fliers default to 'b+'. More control is provided by the *flierprops* parameter. - vert : bool, default: :rc:`boxplot.vertical` - If `True`, draws vertical boxes. - If `False`, draw horizontal boxes. + vert : bool, optional + .. deprecated:: 3.10 + Use *orientation* instead. + + If this is given during the deprecation period, it overrides + the *orientation* parameter. + + If True, plots the boxes vertically. + If False, plots the boxes horizontally. + + orientation : {'vertical', 'horizontal'}, default: 'vertical' + If 'horizontal', plots the boxes horizontally. + Otherwise, plots the boxes vertically. + + .. versionadded:: 3.10 whis : float or (float, float), default: 1.5 The position of the whiskers. @@ -4047,8 +4060,6 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, labels=tick_labels, autorange=autorange) if notch is None: notch = mpl.rcParams['boxplot.notch'] - if vert is None: - vert = mpl.rcParams['boxplot.vertical'] if patch_artist is None: patch_artist = mpl.rcParams['boxplot.patchartist'] if meanline is None: @@ -4148,13 +4159,14 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, meanline=meanline, showfliers=showfliers, capprops=capprops, whiskerprops=whiskerprops, manage_ticks=manage_ticks, zorder=zorder, - capwidths=capwidths, label=label) + capwidths=capwidths, label=label, + orientation=orientation) return artists @_api.make_keyword_only("3.9", "widths") - def bxp(self, bxpstats, positions=None, widths=None, vert=True, - patch_artist=False, shownotches=False, showmeans=False, - showcaps=True, showbox=True, showfliers=True, + def bxp(self, bxpstats, positions=None, widths=None, vert=None, + orientation='vertical', patch_artist=False, shownotches=False, + showmeans=False, showcaps=True, showbox=True, showfliers=True, boxprops=None, whiskerprops=None, flierprops=None, medianprops=None, capprops=None, meanprops=None, meanline=False, manage_ticks=True, zorder=None, @@ -4213,9 +4225,21 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, Either a scalar or a vector and sets the width of each cap. The default is ``0.5*(width of the box)``, see *widths*. - vert : bool, default: True - If `True` (default), makes the boxes vertical. - If `False`, makes horizontal boxes. + vert : bool, optional + .. deprecated:: 3.10 + Use *orientation* instead. + + If this is given during the deprecation period, it overrides + the *orientation* parameter. + + If True, plots the boxes vertically. + If False, plots the boxes horizontally. + + orientation : {'vertical', 'horizontal'}, default: 'vertical' + If 'horizontal', plots the boxes horizontally. + Otherwise, plots the boxes vertically. + + .. versionadded:: 3.10 patch_artist : bool, default: False If `False` produces boxes with the `.Line2D` artist. @@ -4334,8 +4358,29 @@ def merge_kw_rc(subkey, explicit, zdelta=0, usemarker=True): if meanprops is None or removed_prop not in meanprops: mean_kw[removed_prop] = '' + # vert and orientation parameters are linked until vert's + # deprecation period expires. vert only takes precedence + # if set to False. + if vert is None: + vert = mpl.rcParams['boxplot.vertical'] + else: + _api.warn_deprecated( + "3.10", + name="vert: bool", + alternative="orientation: {'vertical', 'horizontal'}" + ) + if vert is False: + orientation = 'horizontal' + _api.check_in_list(['horizontal', 'vertical'], orientation=orientation) + + if not mpl.rcParams['boxplot.vertical']: + _api.warn_deprecated( + "3.10", + name='boxplot.vertical', obj_type="rcparam" + ) + # vertical or horizontal plot? - maybe_swap = slice(None) if vert else slice(None, None, -1) + maybe_swap = slice(None) if orientation == 'vertical' else slice(None, None, -1) def do_plot(xs, ys, **kwargs): return self.plot(*[xs, ys][maybe_swap], **kwargs)[0] @@ -4460,7 +4505,7 @@ def do_patch(xs, ys, **kwargs): artist.set_label(lbl) if manage_ticks: - axis_name = "x" if vert else "y" + axis_name = "x" if orientation == 'vertical' else "y" interval = getattr(self.dataLim, f"interval{axis_name}") axis = self._axis_map[axis_name] positions = axis.convert_units(positions) diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 2f5f6b4f3fde..b728d24d9fe9 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -350,6 +350,7 @@ class Axes(_AxesBase): notch: bool | None = ..., sym: str | None = ..., vert: bool | None = ..., + orientation: Literal["vertical", "horizontal"] = ..., whis: float | tuple[float, float] | None = ..., positions: ArrayLike | None = ..., widths: float | ArrayLike | None = ..., @@ -382,7 +383,8 @@ class Axes(_AxesBase): positions: ArrayLike | None = ..., *, widths: float | ArrayLike | None = ..., - vert: bool = ..., + vert: bool | None = ..., + orientation: Literal["vertical", "horizontal"] = ..., patch_artist: bool = ..., shownotches: bool = ..., showmeans: bool = ..., diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle index 976ab291907b..50516d831ae4 100644 --- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle @@ -380,7 +380,6 @@ boxplot.showbox: True boxplot.showcaps: True boxplot.showfliers: True boxplot.showmeans: False -boxplot.vertical: True boxplot.whiskerprops.color: b boxplot.whiskerprops.linestyle: -- boxplot.whiskerprops.linewidth: 1.0 diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 5d9d2f42f6ac..00e623dd649e 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2935,6 +2935,7 @@ def boxplot( notch: bool | None = None, sym: str | None = None, vert: bool | None = None, + orientation: Literal["vertical", "horizontal"] = "vertical", whis: float | tuple[float, float] | None = None, positions: ArrayLike | None = None, widths: float | ArrayLike | None = None, @@ -2967,6 +2968,7 @@ def boxplot( notch=notch, sym=sym, vert=vert, + orientation=orientation, whis=whis, positions=positions, widths=widths, diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.pdf b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.pdf index cc9433bebd31..c424bc5e982f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.png index 07b8a5da7247..944f9451285c 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.png and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.svg b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.svg index 054af5b4f2f3..c3a3cda7a9a0 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.svg @@ -1,538 +1,535 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + 2024-04-17T16:38:51.018485 + image/svg+xml + + + Matplotlib v3.9.0.dev1517+g1fa7dd164e.d20240417, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index c48519377290..ef0b7c7db29e 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3186,7 +3186,7 @@ def _bxp_test_helper( logstats = mpl.cbook.boxplot_stats( np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)), **stats_kwargs) fig, ax = plt.subplots() - if bxp_kwargs.get('vert', True): + if bxp_kwargs.get('orientation', 'vertical') == 'vertical': ax.set_yscale('log') else: ax.set_xscale('log') @@ -3237,7 +3237,7 @@ def transform(stats): style='default', tol=0.1) def test_bxp_horizontal(): - _bxp_test_helper(bxp_kwargs=dict(vert=False)) + _bxp_test_helper(bxp_kwargs=dict(orientation='horizontal')) @image_comparison(['bxp_with_ylabels.png'], @@ -3250,7 +3250,8 @@ def transform(stats): s['label'] = label return stats - _bxp_test_helper(transform_stats=transform, bxp_kwargs=dict(vert=False)) + _bxp_test_helper(transform_stats=transform, + bxp_kwargs=dict(orientation='horizontal')) @image_comparison(['bxp_patchartist.png'], @@ -3579,7 +3580,6 @@ def test_boxplot_rc_parameters(): } rc_axis1 = { - 'boxplot.vertical': False, 'boxplot.whiskers': [0, 100], 'boxplot.patchartist': True, } @@ -9102,3 +9102,42 @@ def test_violinplot_orientation(fig_test, fig_ref): ax_test = fig_test.subplots() ax_test.violinplot(all_data, orientation='horizontal') + + +@check_figures_equal(extensions=['png']) +def test_boxplot_orientation(fig_test, fig_ref): + # Test the `orientation : {'vertical', 'horizontal'}` + # parameter and deprecation of `vert: bool`. + fig, axs = plt.subplots(nrows=1, ncols=2) + np.random.seed(19680801) + all_data = [np.random.normal(0, std, 100) for std in range(6, 10)] + + axs[0].boxplot(all_data) # Default vertical plot. + # xticks and yticks should be at their default position. + assert all(axs[0].get_xticks() == np.array( + [1, 2, 3, 4])) + assert all(axs[0].get_yticks() == np.array( + [-30., -20., -10., 0., 10., 20., 30.])) + + # Horizontal plot using new `orientation` keyword. + axs[1].boxplot(all_data, orientation='horizontal') + # xticks and yticks should be swapped. + assert all(axs[1].get_xticks() == np.array( + [-30., -20., -10., 0., 10., 20., 30.])) + assert all(axs[1].get_yticks() == np.array( + [1, 2, 3, 4])) + + plt.close() + + # Deprecation of `vert: bool` keyword and + # 'boxplot.vertical' rcparam. + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match='was deprecated in Matplotlib 3.10'): + # Compare images between a figure that + # uses vert and one that uses orientation. + with mpl.rc_context({'boxplot.vertical': False}): + ax_ref = fig_ref.subplots() + ax_ref.boxplot(all_data) + + ax_test = fig_test.subplots() + ax_test.boxplot(all_data, orientation='horizontal') diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 4b693eb7d1ca..276056d044ae 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -255,7 +255,7 @@ def test_bxp(self): datetime.datetime(2020, 1, 27) ] }] - ax.bxp(data, vert=False) + ax.bxp(data, orientation='horizontal') ax.xaxis.set_major_formatter(mpl.dates.DateFormatter("%Y-%m-%d")) ax.set_title('Box plot with datetime data')