From 0adab7b62f5adf01efd9920ba8ab7b748819b0ca Mon Sep 17 00:00:00 2001 From: Anja Beck Date: Fri, 23 Feb 2024 15:12:58 +0000 Subject: [PATCH] add side option for violinplot add test for side option in violinplot add whats new for side in violinplot add violin side option to _axes.pyi and pyplot.py use side option in violinplot also for lines add side option to violinplot example use newer style and correct seed in violin test update image change option naming use proper syntax for code in violin docs Co-authored-by: Kyle Sunden add changed violinplots (capstyle=projecting) hardcode capstyle only for left-right violins fix typo --- doc/users/next_whats_new/sides_violinplot.rst | 4 ++ galleries/examples/statistics/violinplot.py | 40 +++++++++++----- lib/matplotlib/axes/_axes.py | 45 ++++++++++++++---- lib/matplotlib/axes/_axes.pyi | 2 + lib/matplotlib/pyplot.py | 2 + .../test_axes/violinplot_sides.png | Bin 0 -> 11534 bytes lib/matplotlib/tests/test_axes.py | 15 ++++++ 7 files changed, 88 insertions(+), 20 deletions(-) create mode 100644 doc/users/next_whats_new/sides_violinplot.rst create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/violinplot_sides.png diff --git a/doc/users/next_whats_new/sides_violinplot.rst b/doc/users/next_whats_new/sides_violinplot.rst new file mode 100644 index 000000000000..f1643de8e322 --- /dev/null +++ b/doc/users/next_whats_new/sides_violinplot.rst @@ -0,0 +1,4 @@ +Add option to plot only one half of violin plot +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Setting the parameter *side* to 'low' or 'high' allows to only plot one half of the violin plot. diff --git a/galleries/examples/statistics/violinplot.py b/galleries/examples/statistics/violinplot.py index 8779022dd96c..afcc1c977034 100644 --- a/galleries/examples/statistics/violinplot.py +++ b/galleries/examples/statistics/violinplot.py @@ -28,55 +28,73 @@ pos = [1, 2, 4, 5, 7, 8] data = [np.random.normal(0, std, size=100) for std in pos] -fig, axs = plt.subplots(nrows=2, ncols=5, figsize=(10, 6)) +fig, axs = plt.subplots(nrows=2, ncols=6, figsize=(10, 4)) axs[0, 0].violinplot(data, pos, points=20, widths=0.3, showmeans=True, showextrema=True, showmedians=True) -axs[0, 0].set_title('Custom violinplot 1', fontsize=fs) +axs[0, 0].set_title('Custom violin 1', fontsize=fs) axs[0, 1].violinplot(data, pos, points=40, widths=0.5, showmeans=True, showextrema=True, showmedians=True, bw_method='silverman') -axs[0, 1].set_title('Custom violinplot 2', fontsize=fs) +axs[0, 1].set_title('Custom violin 2', fontsize=fs) axs[0, 2].violinplot(data, pos, points=60, widths=0.7, showmeans=True, showextrema=True, showmedians=True, bw_method=0.5) -axs[0, 2].set_title('Custom violinplot 3', fontsize=fs) +axs[0, 2].set_title('Custom violin 3', fontsize=fs) axs[0, 3].violinplot(data, pos, points=60, widths=0.7, showmeans=True, showextrema=True, showmedians=True, bw_method=0.5, quantiles=[[0.1], [], [], [0.175, 0.954], [0.75], [0.25]]) -axs[0, 3].set_title('Custom violinplot 4', fontsize=fs) +axs[0, 3].set_title('Custom violin 4', fontsize=fs) axs[0, 4].violinplot(data[-1:], pos[-1:], points=60, widths=0.7, showmeans=True, showextrema=True, showmedians=True, quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5) -axs[0, 4].set_title('Custom violinplot 5', fontsize=fs) +axs[0, 4].set_title('Custom violin 5', fontsize=fs) + +axs[0, 5].violinplot(data[-1:], pos[-1:], points=60, widths=0.7, + showmeans=True, showextrema=True, showmedians=True, + quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5, side='low') + +axs[0, 5].violinplot(data[-1:], pos[-1:], points=60, widths=0.7, + showmeans=True, showextrema=True, showmedians=True, + 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, showmeans=True, showextrema=True, showmedians=True) -axs[1, 0].set_title('Custom violinplot 6', fontsize=fs) +axs[1, 0].set_title('Custom violin 7', fontsize=fs) axs[1, 1].violinplot(data, pos, points=100, vert=False, widths=0.9, showmeans=True, showextrema=True, showmedians=True, bw_method='silverman') -axs[1, 1].set_title('Custom violinplot 7', fontsize=fs) +axs[1, 1].set_title('Custom violin 8', fontsize=fs) axs[1, 2].violinplot(data, pos, points=200, vert=False, widths=1.1, showmeans=True, showextrema=True, showmedians=True, bw_method=0.5) -axs[1, 2].set_title('Custom violinplot 8', fontsize=fs) +axs[1, 2].set_title('Custom violin 9', fontsize=fs) axs[1, 3].violinplot(data, pos, points=200, vert=False, 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 violinplot 9', fontsize=fs) +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, quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5) -axs[1, 4].set_title('Custom violinplot 10', fontsize=fs) +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, + 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, + 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) for ax in axs.flat: diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 55f1d31740e2..59d2862d775e 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -8205,7 +8205,7 @@ def matshow(self, Z, **kwargs): @_preprocess_data(replace_names=["dataset"]) def violinplot(self, dataset, positions=None, vert=True, widths=0.5, showmeans=False, showextrema=True, showmedians=False, - quantiles=None, points=100, bw_method=None): + quantiles=None, points=100, bw_method=None, side='both'): """ Make a violin plot. @@ -8258,6 +8258,10 @@ def violinplot(self, dataset, positions=None, vert=True, widths=0.5, its only parameter and return a scalar. If None (default), 'scott' is used. + side : {'both', 'low', 'high'}, default: 'both' + 'both' plots standard violins. 'low'/'high' only + plots the side below/above the positions value. + data : indexable object, optional DATA_PARAMETER_PLACEHOLDER @@ -8309,10 +8313,10 @@ def _kde_method(X, coords): quantiles=quantiles) return self.violin(vpstats, positions=positions, vert=vert, widths=widths, showmeans=showmeans, - showextrema=showextrema, showmedians=showmedians) + showextrema=showextrema, showmedians=showmedians, side=side) def violin(self, vpstats, positions=None, vert=True, widths=0.5, - showmeans=False, showextrema=True, showmedians=False): + showmeans=False, showextrema=True, showmedians=False, side='both'): """ Draw a violin plot from pre-computed statistics. @@ -8368,6 +8372,10 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5, showmedians : bool, default: False If true, will toggle rendering of the medians. + side : {'both', 'low', 'high'}, default: 'both' + 'both' plots standard violins. 'low'/'high' only + plots the side below/above the positions value. + Returns ------- dict @@ -8430,8 +8438,13 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5, elif len(widths) != N: raise ValueError(datashape_message.format("widths")) + # Validate side + _api.check_in_list(["both", "low", "high"], side=side) + # Calculate ranges for statistics lines (shape (2, N)). - line_ends = [[-0.25], [0.25]] * np.array(widths) + positions + line_ends = [[-0.25 if side in ['both', 'low'] else 0], + [0.25 if side in ['both', 'high'] else 0]] \ + * np.array(widths) + positions # Colors. if mpl.rcParams['_internal.classic_mode']: @@ -8443,12 +8456,24 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5, # Check whether we are rendering vertically or horizontally if vert: fill = self.fill_betweenx - perp_lines = functools.partial(self.hlines, colors=linecolor) - par_lines = functools.partial(self.vlines, colors=linecolor) + if side in ['low', 'high']: + perp_lines = functools.partial(self.hlines, colors=linecolor, + capstyle='projecting') + par_lines = functools.partial(self.vlines, colors=linecolor, + capstyle='projecting') + else: + perp_lines = functools.partial(self.hlines, colors=linecolor) + par_lines = functools.partial(self.vlines, colors=linecolor) else: fill = self.fill_between - perp_lines = functools.partial(self.vlines, colors=linecolor) - par_lines = functools.partial(self.hlines, colors=linecolor) + if side in ['low', 'high']: + perp_lines = functools.partial(self.vlines, colors=linecolor, + capstyle='projecting') + par_lines = functools.partial(self.hlines, colors=linecolor, + capstyle='projecting') + else: + perp_lines = functools.partial(self.vlines, colors=linecolor) + par_lines = functools.partial(self.hlines, colors=linecolor) # Render violins bodies = [] @@ -8456,7 +8481,9 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5, # The 0.5 factor reflects the fact that we plot from v-p to v+p. vals = np.array(stats['vals']) vals = 0.5 * width * vals / vals.max() - bodies += [fill(stats['coords'], -vals + pos, vals + pos, + bodies += [fill(stats['coords'], + -vals + pos if side in ['both', 'low'] else pos, + vals + pos if side in ['both', 'high'] else pos, facecolor=fillcolor, alpha=0.3)] means.append(stats['mean']) mins.append(stats['min']) diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 501cb933037a..b40ee337206f 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -743,6 +743,7 @@ class Axes(_AxesBase): | float | Callable[[GaussianKDE], float] | None = ..., + side: Literal["both", "low", "high"] = ..., *, data=..., ) -> dict[str, Collection]: ... @@ -755,6 +756,7 @@ class Axes(_AxesBase): showmeans: bool = ..., showextrema: bool = ..., showmedians: bool = ..., + side: Literal["both", "low", "high"] = ..., ) -> dict[str, Collection]: ... table = mtable.table diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 2cf0d5325a63..c5044fe7242f 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -4075,6 +4075,7 @@ def violinplot( | float | Callable[[GaussianKDE], float] | None = None, + side: Literal["both", "low", "high"] = "both", *, data=None, ) -> dict[str, Collection]: @@ -4089,6 +4090,7 @@ def violinplot( quantiles=quantiles, points=points, bw_method=bw_method, + side=side, **({"data": data} if data is not None else {}), ) diff --git a/lib/matplotlib/tests/baseline_images/test_axes/violinplot_sides.png b/lib/matplotlib/tests/baseline_images/test_axes/violinplot_sides.png new file mode 100644 index 0000000000000000000000000000000000000000..f30bc46b8c5c31be8070c4fb7d26c84d8d290a4c GIT binary patch literal 11534 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 OJ8yj1h;Z8Bum1(WIoWmq literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index f2f74f845338..8c43b04e0eb7 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3794,6 +3794,21 @@ def test_horiz_violinplot_custompoints_200(): showextrema=False, showmedians=False, points=200) +@image_comparison(['violinplot_sides.png'], remove_text=True, style='mpl20') +def test_violinplot_sides(): + ax = plt.axes() + np.random.seed(19680801) + 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, + 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, + showextrema=True, showmedians=True, side=side) + + def test_violinplot_bad_positions(): ax = plt.axes() # First 9 digits of frac(sqrt(47))