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

Add a new orientation parameter to Violinplot and deprecate vert #27998

Merged
merged 4 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/api/next_api_changes/deprecations/27998-TS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
``violinplot`` and ``violin`` *vert* parameter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The parameter *vert: bool* has been deprecated on `~.Axes.violinplot` and
`~.Axes.violin`.
It will be replaced by *orientation: {"vertical", "horizontal"}* for API
consistency.
21 changes: 21 additions & 0 deletions doc/users/next_whats_new/violinplot_orientation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
``violinplot`` and ``violin`` orientation parameter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Violinplots have a new parameter *orientation: {"vertical", "horizontal"}*
to change the orientation of the plot. This will replace the deprecated
*vert: bool* parameter.


.. plot::
:include-source: true
:alt: Example of creating 4 horizontal violinplots.

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.violinplot(all_data, orientation='horizontal')
plt.show()
20 changes: 10 additions & 10 deletions galleries/examples/statistics/violinplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,37 +62,37 @@
quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5, side='high')
axs[0, 5].set_title('Custom violin 6', fontsize=fs)

axs[1, 0].violinplot(data, pos, points=80, vert=False, widths=0.7,
axs[1, 0].violinplot(data, pos, points=80, orientation='horizontal', widths=0.7,
showmeans=True, showextrema=True, showmedians=True)
axs[1, 0].set_title('Custom violin 7', fontsize=fs)

axs[1, 1].violinplot(data, pos, points=100, vert=False, widths=0.9,
axs[1, 1].violinplot(data, pos, points=100, orientation='horizontal', widths=0.9,
showmeans=True, showextrema=True, showmedians=True,
bw_method='silverman')
axs[1, 1].set_title('Custom violin 8', fontsize=fs)

axs[1, 2].violinplot(data, pos, points=200, vert=False, widths=1.1,
axs[1, 2].violinplot(data, pos, points=200, orientation='horizontal', widths=1.1,
showmeans=True, showextrema=True, showmedians=True,
bw_method=0.5)
axs[1, 2].set_title('Custom violin 9', fontsize=fs)

axs[1, 3].violinplot(data, pos, points=200, vert=False, widths=1.1,
axs[1, 3].violinplot(data, pos, points=200, orientation='horizontal', widths=1.1,
showmeans=True, showextrema=True, showmedians=True,
quantiles=[[0.1], [], [], [0.175, 0.954], [0.75], [0.25]],
bw_method=0.5)
axs[1, 3].set_title('Custom violin 10', fontsize=fs)

axs[1, 4].violinplot(data[-1:], pos[-1:], points=200, vert=False, widths=1.1,
showmeans=True, showextrema=True, showmedians=True,
axs[1, 4].violinplot(data[-1:], pos[-1:], points=200, orientation='horizontal',
widths=1.1, showmeans=True, showextrema=True, showmedians=True,
quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5)
axs[1, 4].set_title('Custom violin 11', fontsize=fs)

axs[1, 5].violinplot(data[-1:], pos[-1:], points=200, vert=False, widths=1.1,
showmeans=True, showextrema=True, showmedians=True,
axs[1, 5].violinplot(data[-1:], pos[-1:], points=200, orientation='horizontal',
widths=1.1, showmeans=True, showextrema=True, showmedians=True,
quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5, side='low')

axs[1, 5].violinplot(data[-1:], pos[-1:], points=200, vert=False, widths=1.1,
showmeans=True, showextrema=True, showmedians=True,
axs[1, 5].violinplot(data[-1:], pos[-1:], points=200, orientation='horizontal',
widths=1.1, showmeans=True, showextrema=True, showmedians=True,
quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5, side='high')
axs[1, 5].set_title('Custom violin 12', fontsize=fs)

Expand Down
67 changes: 56 additions & 11 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8350,9 +8350,10 @@ def matshow(self, Z, **kwargs):

@_api.make_keyword_only("3.9", "vert")
@_preprocess_data(replace_names=["dataset"])
def violinplot(self, dataset, positions=None, vert=True, widths=0.5,
def violinplot(self, dataset, positions=None, vert=None, widths=0.5,
showmeans=False, showextrema=True, showmedians=False,
quantiles=None, points=100, bw_method=None, side='both'):
quantiles=None, points=100, bw_method=None, side='both',
orientation=None):
"""
Make a violin plot.

Expand All @@ -8371,8 +8372,14 @@ def violinplot(self, dataset, positions=None, vert=True, widths=0.5,
vertical violins (or y-axis for horizontal violins).

vert : bool, default: True.
Copy link
Member

@rcomer rcomer May 12, 2024

Choose a reason for hiding this comment

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

Suggested change
vert : bool, default: True.
vert : bool, optional

(optional is synonymous with default: None)

If true, creates a vertical violin plot.
Otherwise, creates a horizontal violin plot.
.. deprecated:: 3.10
Use *orientation* instead.

If this is given during the deprecation period, it overrides
the *orientation* parameter.

If True, plots the violins vertically.
If False, plots the violins horizontally.

widths : float or array-like, default: 0.5
The maximum width of each violin in units of the *positions* axis.
Expand Down Expand Up @@ -8407,6 +8414,12 @@ def violinplot(self, dataset, positions=None, vert=True, widths=0.5,
'both' plots standard violins. 'low'/'high' only
plots the side below/above the positions value.

orientation : {'vertical', 'horizontal'}, default: 'vertical'
If 'horizontal', plots the violins horizontally.
Otherwise, plots the violins vertically.

.. versionadded:: 3.10

data : indexable object, optional
DATA_PARAMETER_PLACEHOLDER

Expand Down Expand Up @@ -8457,12 +8470,14 @@ def _kde_method(X, coords):
vpstats = cbook.violin_stats(dataset, _kde_method, points=points,
quantiles=quantiles)
return self.violin(vpstats, positions=positions, vert=vert,
widths=widths, showmeans=showmeans,
showextrema=showextrema, showmedians=showmedians, side=side)
orientation=orientation, widths=widths,
showmeans=showmeans, showextrema=showextrema,
showmedians=showmedians, side=side)

@_api.make_keyword_only("3.9", "vert")
def violin(self, vpstats, positions=None, vert=True, widths=0.5,
showmeans=False, showextrema=True, showmedians=False, side='both'):
def violin(self, vpstats, positions=None, vert=None, widths=0.5,
showmeans=False, showextrema=True, showmedians=False, side='both',
orientation=None):
"""
Draw a violin plot from pre-computed statistics.

Expand Down Expand Up @@ -8501,8 +8516,14 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5,
vertical violins (or y-axis for horizontal violins).

vert : bool, default: True.
If true, plots the violins vertically.
Otherwise, plots the violins horizontally.
.. deprecated:: 3.10
Use *orientation* instead.

If this is given during the deprecation period, it overrides
the *orientation* parameter.

If True, plots the violins vertically.
If False, plots the violins horizontally.

widths : float or array-like, default: 0.5
The maximum width of each violin in units of the *positions* axis.
Expand All @@ -8522,6 +8543,12 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5,
'both' plots standard violins. 'low'/'high' only
plots the side below/above the positions value.

orientation : {'vertical', 'horizontal'}, default: 'vertical'
If 'horizontal', plots the violins horizontally.
Otherwise, plots the violins vertically.

.. versionadded:: 3.10

Returns
-------
dict
Expand Down Expand Up @@ -8572,6 +8599,24 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5,
datashape_message = ("List of violinplot statistics and `{0}` "
"values must have the same length")

if vert is not None:
_api.warn_deprecated(
"3.10",
name="vert: bool",
alternative="orientation: {'vertical', 'horizontal'}"
)

# vert and orientation parameters are linked until vert's
# deprecation period expires. If both are selected,
# vert takes precedence.
if vert or vert is None and orientation is None:
orientation = 'vertical'
elif vert is False:
orientation = 'horizontal'

if orientation is not None:
_api.check_in_list(['horizontal', 'vertical'], orientation=orientation)
Copy link
Member

Choose a reason for hiding this comment

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

I'm not following the logic here completely. After whatever mangling vert and orientation, shouldn't we have a normalized orientation that must be 'horizontal' or 'vertical'? In other words

  • Why do we allow orientation to be None in 8617 and would that even work for the following code?

Can't we simply

if vert is not None:
    _api.warn_deprecated(...)
    orientation = 'vertical' if vert else 'horizontal'
_api.check_in_list(['horizontal', 'vertical'], orientation=orientation)

and default orientation='vertical' instead of defaulting it to None?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it should default to vertical instead of None (it even says so in the docstring) so there was no reason for it other than making it more convoluted. That code change is great, I'll add it to the next commit


# Validate positions
if positions is None:
positions = range(1, N + 1)
Expand Down Expand Up @@ -8600,7 +8645,7 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5,
fillcolor = linecolor = self._get_lines.get_next_color()

# Check whether we are rendering vertically or horizontally
if vert:
if orientation == 'vertical':
saranti marked this conversation as resolved.
Show resolved Hide resolved
fill = self.fill_betweenx
if side in ['low', 'high']:
perp_lines = functools.partial(self.hlines, colors=linecolor,
Expand Down
6 changes: 4 additions & 2 deletions lib/matplotlib/axes/_axes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ class Axes(_AxesBase):
dataset: ArrayLike | Sequence[ArrayLike],
positions: ArrayLike | None = ...,
*,
vert: bool = ...,
vert: bool | None = ...,
widths: float | ArrayLike = ...,
showmeans: bool = ...,
showextrema: bool = ...,
Expand All @@ -751,19 +751,21 @@ class Axes(_AxesBase):
| Callable[[GaussianKDE], float]
| None = ...,
side: Literal["both", "low", "high"] = ...,
orientation: Literal["vertical", "horizontal"] | None = ...,
data=...,
) -> dict[str, Collection]: ...
def violin(
self,
vpstats: Sequence[dict[str, Any]],
positions: ArrayLike | None = ...,
*,
vert: bool = ...,
vert: bool | None = ...,
widths: float | ArrayLike = ...,
showmeans: bool = ...,
showextrema: bool = ...,
showmedians: bool = ...,
side: Literal["both", "low", "high"] = ...,
orientation: Literal["vertical", "horizontal"] | None = ...,
) -> dict[str, Collection]: ...

table = mtable.table
Expand Down
4 changes: 3 additions & 1 deletion lib/matplotlib/pyplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4142,7 +4142,7 @@ def triplot(*args, **kwargs):
def violinplot(
dataset: ArrayLike | Sequence[ArrayLike],
positions: ArrayLike | None = None,
vert: bool = True,
vert: bool | None = None,
widths: float | ArrayLike = 0.5,
showmeans: bool = False,
showextrema: bool = True,
Expand All @@ -4154,6 +4154,7 @@ def violinplot(
| Callable[[GaussianKDE], float]
| None = None,
side: Literal["both", "low", "high"] = "both",
orientation: Literal["vertical", "horizontal"] | None = None,
*,
data=None,
) -> dict[str, Collection]:
Expand All @@ -4169,6 +4170,7 @@ def violinplot(
points=points,
bw_method=bw_method,
side=side,
orientation=orientation,
**({"data": data} if data is not None else {}),
)

Expand Down
55 changes: 46 additions & 9 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3747,7 +3747,7 @@ def test_horiz_violinplot_baseline():
# First 9 digits of frac(sqrt(19))
np.random.seed(358898943)
data = [np.random.normal(size=100) for _ in range(4)]
ax.violinplot(data, positions=range(4), vert=False, showmeans=False,
ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
showextrema=False, showmedians=False)


Expand All @@ -3757,7 +3757,7 @@ def test_horiz_violinplot_showmedians():
# First 9 digits of frac(sqrt(23))
np.random.seed(795831523)
data = [np.random.normal(size=100) for _ in range(4)]
ax.violinplot(data, positions=range(4), vert=False, showmeans=False,
ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
showextrema=False, showmedians=True)


Expand All @@ -3767,7 +3767,7 @@ def test_horiz_violinplot_showmeans():
# First 9 digits of frac(sqrt(29))
np.random.seed(385164807)
data = [np.random.normal(size=100) for _ in range(4)]
ax.violinplot(data, positions=range(4), vert=False, showmeans=True,
ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=True,
showextrema=False, showmedians=False)


Expand All @@ -3777,7 +3777,7 @@ def test_horiz_violinplot_showextrema():
# First 9 digits of frac(sqrt(31))
np.random.seed(567764362)
data = [np.random.normal(size=100) for _ in range(4)]
ax.violinplot(data, positions=range(4), vert=False, showmeans=False,
ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
showextrema=True, showmedians=False)


Expand All @@ -3787,7 +3787,7 @@ def test_horiz_violinplot_showall():
# First 9 digits of frac(sqrt(37))
np.random.seed(82762530)
data = [np.random.normal(size=100) for _ in range(4)]
ax.violinplot(data, positions=range(4), vert=False, showmeans=True,
ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=True,
showextrema=True, showmedians=True,
quantiles=[[0.1, 0.9], [0.2, 0.8], [0.3, 0.7], [0.4, 0.6]])

Expand All @@ -3798,7 +3798,7 @@ def test_horiz_violinplot_custompoints_10():
# First 9 digits of frac(sqrt(41))
np.random.seed(403124237)
data = [np.random.normal(size=100) for _ in range(4)]
ax.violinplot(data, positions=range(4), vert=False, showmeans=False,
ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
showextrema=False, showmedians=False, points=10)


Expand All @@ -3808,7 +3808,7 @@ def test_horiz_violinplot_custompoints_200():
# First 9 digits of frac(sqrt(43))
np.random.seed(557438524)
data = [np.random.normal(size=100) for _ in range(4)]
ax.violinplot(data, positions=range(4), vert=False, showmeans=False,
ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
showextrema=False, showmedians=False, points=200)


Expand All @@ -3819,11 +3819,11 @@ def test_violinplot_sides():
data = [np.random.normal(size=100)]
# Check horizontal violinplot
for pos, side in zip([0, -0.5, 0.5], ['both', 'low', 'high']):
ax.violinplot(data, positions=[pos], vert=False, showmeans=False,
ax.violinplot(data, positions=[pos], orientation='horizontal', showmeans=False,
showextrema=True, showmedians=True, side=side)
# Check vertical violinplot
for pos, side in zip([4, 3.5, 4.5], ['both', 'low', 'high']):
ax.violinplot(data, positions=[pos], vert=True, showmeans=False,
ax.violinplot(data, positions=[pos], orientation='vertical', showmeans=False,
showextrema=True, showmedians=True, side=side)


Expand Down Expand Up @@ -9034,3 +9034,40 @@ def test_latex_pie_percent(fig_test, fig_ref):

ax1 = fig_ref.subplots()
ax1.pie(data, autopct=r"%1.0f\%%", textprops={'usetex': True})


@check_figures_equal(extensions=['png'])
def test_violinplot_orientation(fig_test, fig_ref):
# Test the `orientation : {'vertical', 'horizontal'}`
# parameter and deprecation of `vert: bool`.
fig, axs = plt.subplots(nrows=1, ncols=3)
np.random.seed(19680801)
all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]

axs[0].violinplot(all_data) # Default vertical plot.
# xticks and yticks should be at their default position.
assert all(axs[0].get_xticks() == np.array(
[0.5, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5]))
assert all(axs[0].get_yticks() == np.array(
[-30., -20., -10., 0., 10., 20., 30.]))

# Horizontal plot using new `orientation` keyword.
axs[1].violinplot(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(
[0.5, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5]))

plt.close()

# Deprecation of `vert: bool` keyword
with pytest.warns(mpl.MatplotlibDeprecationWarning,
match='vert: bool was deprecated in Matplotlib 3.10'):
# Compare images between a figure that
# uses vert and one that uses orientation.
ax_ref = fig_ref.subplots()
ax_ref.violinplot(all_data, vert=False)

ax_test = fig_test.subplots()
ax_test.violinplot(all_data, orientation='horizontal')