Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork8.1k
Allow user to specify colors in violin plots with constructor method#27304
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Changes from8 commits
c1190e64c6def19ce5d1233d186b9e97d6f2d4c7b735f5c628474cf1a206de83998c6ff219c3f60343f5e66e33aFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| ``violinplot`` now accepts color arguments | ||
| ------------------------------------------- | ||
| `~.Axes.violinplot` and `~.Axes.violin` now accept ``facecolor`` and | ||
| ``linecolor`` as input arguments. This means that users can set the color of | ||
| violinplots as they make them, rather than setting the color of individual | ||
| objects afterwards. It is possible to pass a single color to be used for all | ||
| violins, or pass a sequence of colors. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -36,20 +36,30 @@ def set_axis_style(ax, labels): | ||
| np.random.seed(19680801) | ||
| data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 5)] | ||
| fig, (ax1, ax2, ax3) = plt.subplots(nrows=1, ncols=3, figsize=(9,3), sharey=True) | ||
| ax1.set_title('Default violin plot') | ||
| ax1.set_ylabel('Observed values') | ||
| ax1.violinplot(data) | ||
| ax2.set_title('Set colors of violins') | ||
| ax2.set_ylabel('Observed values') | ||
| ax2.violinplot( | ||
| data, | ||
| facecolor=[('y', 0.3), ('b', 0.3), ('r', 0.3), ('g', 0.3)], | ||
| linecolor='k', | ||
| ) | ||
| # Note that when passing a sequence of colors, the method will repeat the sequence if | ||
| # less colors are provided than data distributions. | ||
| ax3.set_title('Customized violin plot') | ||
| parts = ax3.violinplot( | ||
| data, showmeans=False, showmedians=False, showextrema=False, | ||
| facecolor='#D43F3A', linecolor='k') | ||
| for pc in parts['bodies']: | ||
| pc.set_edgecolor('k') | ||
landoskape marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| pc.set_linewidth(1) | ||
| pc.set_alpha(1) | ||
| quartile1, medians, quartile3 = np.percentile(data, [25, 50, 75], axis=1) | ||
| @@ -59,13 +69,13 @@ def set_axis_style(ax, labels): | ||
| whiskers_min, whiskers_max = whiskers[:, 0], whiskers[:, 1] | ||
| inds = np.arange(1, len(medians) + 1) | ||
| ax3.scatter(inds, medians, marker='o', color='white', s=30, zorder=3) | ||
| ax3.vlines(inds, quartile1, quartile3, color='k', linestyle='-', lw=5) | ||
| ax3.vlines(inds, whiskers_min, whiskers_max, color='k', linestyle='-', lw=1) | ||
| # set style for the axes | ||
| labels = ['A', 'B', 'C', 'D'] | ||
| for ax in [ax1, ax2, ax3]: | ||
| set_axis_style(ax, labels) | ||
| plt.subplots_adjust(bottom=0.15, wspace=0.05) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -8439,7 +8439,8 @@ def matshow(self, Z, **kwargs): | ||
| def violinplot(self, dataset, positions=None, vert=None, | ||
| orientation='vertical', widths=0.5, showmeans=False, | ||
| showextrema=True, showmedians=False, quantiles=None, | ||
| points=100, bw_method=None, side='both', | ||
| facecolor=None, linecolor=None): | ||
| """ | ||
| Make a violin plot. | ||
| @@ -8506,6 +8507,17 @@ def violinplot(self, dataset, positions=None, vert=None, | ||
| 'both' plots standard violins. 'low'/'high' only | ||
| plots the side below/above the positions value. | ||
| facecolor : :mpltype`color` or list of :mpltype:`color`, optional | ||
timhoffm marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| If provided, will set the face color(s) of the violins. | ||
oscargus marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| .. versionadded:: 3.11 | ||
| linecolor : :mpltype`color` or list of :mpltype:`color`, optional | ||
timhoffm marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| If provided, will set the line color(s) of the violins (the | ||
| horizontal and vertical spines and body edges). | ||
| .. versionadded:: 3.11 | ||
| data : indexable object, optional | ||
| DATA_PARAMETER_PLACEHOLDER | ||
| @@ -8558,12 +8570,14 @@ def _kde_method(X, coords): | ||
| return self.violin(vpstats, positions=positions, vert=vert, | ||
| orientation=orientation, widths=widths, | ||
| showmeans=showmeans, showextrema=showextrema, | ||
| showmedians=showmedians, side=side, | ||
| facecolor=facecolor, linecolor=linecolor) | ||
| @_api.make_keyword_only("3.9", "vert") | ||
| def violin(self, vpstats, positions=None, vert=None, | ||
| orientation='vertical', widths=0.5, showmeans=False, | ||
| showextrema=True, showmedians=False, side='both', | ||
| facecolor=None, linecolor=None): | ||
| """ | ||
| Draw a violin plot from pre-computed statistics. | ||
| @@ -8635,6 +8649,17 @@ def violin(self, vpstats, positions=None, vert=None, | ||
| 'both' plots standard violins. 'low'/'high' only | ||
| plots the side below/above the positions value. | ||
| facecolor : :mpltype`color` or list of :mpltype:`color`, optional | ||
timhoffm marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| If provided, will set the face color(s) of the violins. | ||
| .. versionadded:: 3.11 | ||
| linecolor : :mpltype`color` or list of :mpltype:`color`, optional | ||
timhoffm marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| If provided, will set the line color(s) of the violins (the | ||
| horizontal and vertical spines and body edges). | ||
| .. versionadded:: 3.11 | ||
| Returns | ||
| ------- | ||
| dict | ||
| @@ -8717,12 +8742,38 @@ def violin(self, vpstats, positions=None, vert=None, | ||
| [0.25 if side in ['both', 'high'] else 0]] \ | ||
| * np.array(widths) + positions | ||
| # Make a cycle of color to iterate through, using 'none' as fallback | ||
| def cycle_color(color, alpha=None): | ||
| rgba = mcolors.to_rgba_array(color, alpha=alpha) | ||
| color_cycler = itertools.chain(itertools.cycle(rgba), | ||
| itertools.repeat('none')) | ||
| color_list = [] | ||
| for _ in range(N): | ||
| color_list.append(next(color_cycler)) | ||
| return color_list | ||
| # Convert colors to chain (number of colors can be different from len(vpstats)) | ||
| if facecolor is None or linecolor is None: | ||
| if not mpl.rcParams['_internal.classic_mode']: | ||
| next_color = self._get_lines.get_next_color() | ||
| if facecolor is not None: | ||
| facecolor = cycle_color(facecolor) | ||
| else: | ||
| default_facealpha = 0.3 | ||
| # Use default colors if user doesn't provide them | ||
| if mpl.rcParams['_internal.classic_mode']: | ||
| facecolor = cycle_color('y', alpha=default_facealpha) | ||
| else: | ||
| facecolor = cycle_color(next_color, alpha=default_facealpha) | ||
| if linecolor is not None: | ||
| linecolor = cycle_color(linecolor) | ||
| else: | ||
| if mpl.rcParams['_internal.classic_mode']: | ||
| linecolor = cycle_color('r') | ||
| else: | ||
| linecolor = cycle_color(next_color) | ||
| # Check whether we are rendering vertically or horizontally | ||
| if orientation == 'vertical': | ||
| @@ -8748,14 +8799,15 @@ def violin(self, vpstats, positions=None, vert=None, | ||
| # Render violins | ||
| bodies = [] | ||
| bodies_zip = zip(vpstats, positions, widths, facecolor) | ||
| for stats, pos, width, facecol in bodies_zip: | ||
timhoffm marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| # 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 if side in ['both', 'low'] else pos, | ||
| vals + pos if side in ['both', 'high'] else pos, | ||
| facecolor=facecol, alpha=0.3)] | ||
| means.append(stats['mean']) | ||
| mins.append(stats['min']) | ||
| maxes.append(stats['max']) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -4021,6 +4021,91 @@ def test_violinplot_outofrange_quantiles(): | ||
| ax.violinplot(data, quantiles=[[-0.05, 0.2, 0.3, 0.75]]) | ||
| @check_figures_equal(extensions=["png"]) | ||
| def test_violinplot_color_specification(fig_test, fig_ref): | ||
| # Ensures that setting colors in violinplot constructor works | ||
| # the same way as setting the color of each object manually | ||
| np.random.seed(19680801) | ||
| data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 4)] | ||
| kwargs = {'showmeans': True, | ||
| 'showextrema': True, | ||
| 'showmedians': True | ||
| } | ||
| def color_violins(parts, facecolor=None, linecolor=None): | ||
| """Helper to color parts manually.""" | ||
| if facecolor is not None: | ||
| for pc in parts['bodies']: | ||
| pc.set_facecolor(facecolor) | ||
| if linecolor is not None: | ||
| for partname in ('cbars', 'cmins', 'cmaxes', 'cmeans', 'cmedians'): | ||
| if partname in parts: | ||
| lc = parts[partname] | ||
| lc.set_edgecolor(linecolor) | ||
| # Reference image | ||
| ax = fig_ref.subplots(1, 3) | ||
| parts0 = ax[0].violinplot(data, **kwargs) | ||
| parts1 = ax[1].violinplot(data, **kwargs) | ||
| parts2 = ax[2].violinplot(data, **kwargs) | ||
| color_violins(parts0, facecolor=('r', 0.5), linecolor=('r', 0.2)) | ||
| color_violins(parts1, facecolor='r') | ||
| color_violins(parts2, linecolor='r') | ||
| # Test image | ||
| ax = fig_test.subplots(1, 3) | ||
| ax[0].violinplot(data, facecolor=('r', 0.5), linecolor=('r', 0.2), **kwargs) | ||
| ax[1].violinplot(data, facecolor='r', **kwargs) | ||
| ax[2].violinplot(data, linecolor='r', **kwargs) | ||
| def test_violinplot_color_sequence(): | ||
| # Ensures that setting a sequence of colors works the same as setting | ||
| # each color independently | ||
| np.random.seed(19680801) | ||
| data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 5)] | ||
| kwargs = {'showmeans': True, | ||
| 'showextrema': True, | ||
| 'showmedians': True | ||
| } | ||
| def assert_colors_equal(colors1, colors2): | ||
| assert all(mcolors.same_color(c1, c2) for c1, c2 in zip(colors1, colors2)) | ||
| parts_with_facecolor = ["bodies"] | ||
| parts_with_edgecolor = ["cbars", "cmins", "cmaxes", "cmeans", "cmedians"] | ||
timhoffm marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| # Color sequence | ||
| N = len(data) | ||
| positions = range(N) | ||
| colors = ['k', 'r', ('b', 0.5), 'g', ('m', 0.2)] | ||
| # Test image | ||
| fig_test = plt.figure() | ||
| ax = fig_test.gca() | ||
| parts_test = ax.violinplot(data, positions=positions, facecolor=colors, | ||
| linecolor=colors, **kwargs) | ||
| # Reference image | ||
| fig_ref = plt.figure() | ||
| ax = fig_ref.gca() | ||
| parts_ref = [] | ||
| for (p, c, d) in zip(positions, colors, data): | ||
| cparts = ax.violinplot(d, positions=[p], facecolor=c, linecolor=c, **kwargs) | ||
| parts_ref.append(cparts) | ||
| for part in parts_with_facecolor: | ||
| colors_test = [p.get_facecolor() for p in parts_test[part]] | ||
| colors_ref = [p[part][0].get_facecolor() for p in parts_ref] | ||
| assert_colors_equal(colors_test, colors_ref) | ||
| for part in parts_with_edgecolor: | ||
timhoffm marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| colors_test = parts_test[part].get_edgecolor() | ||
| colors_ref = [p[part].get_edgecolor() for p in parts_ref] | ||
| assert_colors_equal(colors_test, colors_ref) | ||
landoskape marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| @check_figures_equal(extensions=["png"]) | ||
| def test_violinplot_single_list_quantiles(fig_test, fig_ref): | ||
| # Ensures quantile list for 1D can be passed in as single list | ||
Uh oh!
There was an error while loading.Please reload this page.