Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Merged
timhoffm merged 13 commits intomatplotlib:mainfromlandoskape:violin-plot-color
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
13 commits
Select commitHold shift + click to select a range
c1190e6
Add color specification and pyi docs
landoskapeNov 10, 2023
4c6def1
edgecolor to linecolor, no more alpha
landoskapeJan 27, 2025
9ce5d12
Arg documentation
landoskapeJan 28, 2025
33d186b
completeness, formatting
landoskapeJan 28, 2025
9e97d6f
Default logic, docstring, straightforward testing
landoskapeJan 28, 2025
2d4c7b7
Spelling error
landoskapeJan 28, 2025
35f5c62
single "next color" for default face/line
landoskapeJan 28, 2025
8474cf1
Merge branch 'violin-plot-color' of https://github.com/landoskape/mat…
landoskapeJan 28, 2025
a206de8
clarify color specification
landoskapeJan 29, 2025
3998c6f
axes bug -- set with facecolor
landoskapeJan 29, 2025
f219c3f
direct comparison to source colors, face!=line
landoskapeJan 29, 2025
60343f5
adjust behavior for classic mode
landoskapeJan 30, 2025
e66e33a
Apply suggestions from code review
timhoffmJan 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletionsdoc/users/next_whats_new/violinplot_colors.rst
View file
Open in desktop
Original file line numberDiff line numberDiff 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.
30 changes: 20 additions & 10 deletionsgalleries/examples/statistics/customized_violin.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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) = plt.subplots(nrows=1, ncols=2, figsize=(9,4), sharey=True)
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('Customized violin plot')
parts = ax2.violinplot(
data, showmeans=False, showmedians=False,
showextrema=False)
ax2.set_title('Set colors of violins')
ax2.set_ylabel('Observed values')
ax2.violinplot(
data,
facecolor=[('yellow', 0.3), ('blue', 0.3), ('red', 0.3), ('green', 0.3)],
linecolor='black',
)
# 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='black')

for pc in parts['bodies']:
pc.set_facecolor('#D43F3A')
pc.set_edgecolor('black')
pc.set_linewidth(1)
pc.set_alpha(1)

quartile1, medians, quartile3 = np.percentile(data, [25, 50, 75], axis=1)
Expand All@@ -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)
ax2.scatter(inds, medians, marker='o', color='white', s=30, zorder=3)
ax2.vlines(inds, quartile1, quartile3, color='k', linestyle='-', lw=5)
ax2.vlines(inds, whiskers_min, whiskers_max, color='k', linestyle='-', lw=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]:
for ax in [ax1, ax2, ax3]:
set_axis_style(ax, labels)

plt.subplots_adjust(bottom=0.15, wspace=0.05)
Expand Down
77 changes: 68 additions & 9 deletionslib/matplotlib/axes/_axes.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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',):
points=100, bw_method=None, side='both',
facecolor=None, linecolor=None):
"""
Make a violin plot.

Expand DownExpand Up@@ -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
If provided, will set the face color(s) of the violins.

.. versionadded:: 3.11

linecolor : :mpltype:`color` or list of :mpltype:`color`, optional
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

Expand DownExpand Up@@ -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)
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'):
showextrema=True, showmedians=False, side='both',
facecolor=None, linecolor=None):
"""
Draw a violin plot from pre-computed statistics.

Expand DownExpand Up@@ -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
If provided, will set the face color(s) of the violins.

.. versionadded:: 3.11

linecolor : :mpltype:`color` or list of :mpltype:`color`, optional
If provided, will set the line color(s) of the violins (the
horizontal and vertical spines and body edges).

.. versionadded:: 3.11

Returns
-------
dict
Expand DownExpand Up@@ -8717,12 +8742,45 @@ def violin(self, vpstats, positions=None, vert=None,
[0.25 if side in ['both', 'high'] else 0]] \
* np.array(widths) + positions

# Colors.
# 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 mpl.rcParams['_internal.classic_mode']:
fillcolor = 'y'
linecolor = 'r'
# Classic mode uses patch.force_edgecolor=True, so we need to
# set the edgecolor to make sure it has an alpha.
body_edgecolor = ("k", 0.3)
else:
body_edgecolor = None

if linecolor is not None:
linecolor = cycle_color(linecolor)
else:
fillcolor = linecolor = self._get_lines.get_next_color()
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':
Expand All@@ -8748,14 +8806,15 @@ def violin(self, vpstats, positions=None, vert=None,

# Render violins
bodies = []
for stats, pos, width in zip(vpstats, positions, widths):
bodies_zip = zip(vpstats, positions, widths, facecolor)
for stats, pos, width, facecolor in bodies_zip:
# 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=fillcolor, alpha=0.3)]
facecolor=facecolor, edgecolor=body_edgecolor)]
means.append(stats['mean'])
mins.append(stats['min'])
maxes.append(stats['max'])
Expand Down
4 changes: 4 additions & 0 deletionslib/matplotlib/axes/_axes.pyi
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -755,6 +755,8 @@ class Axes(_AxesBase):
| Callable[[GaussianKDE], float]
| None = ...,
side: Literal["both", "low", "high"] = ...,
facecolor: Sequence[ColorType] | ColorType | None = ...,
linecolor: Sequence[ColorType] | ColorType | None = ...,
data=...,
) -> dict[str, Collection]: ...
def violin(
Expand All@@ -769,6 +771,8 @@ class Axes(_AxesBase):
showextrema: bool = ...,
showmedians: bool = ...,
side: Literal["both", "low", "high"] = ...,
facecolor: Sequence[ColorType] | ColorType | None = ...,
linecolor: Sequence[ColorType] | ColorType | None = ...,
) -> dict[str, Collection]: ...

table = mtable.table
Expand Down
4 changes: 4 additions & 0 deletionslib/matplotlib/pyplot.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4302,6 +4302,8 @@ def violinplot(
| Callable[[GaussianKDE], float]
| None = None,
side: Literal["both", "low", "high"] = "both",
facecolor: Sequence[ColorType] | ColorType | None = None,
linecolor: Sequence[ColorType] | ColorType | None = None,
*,
data=None,
) -> dict[str, Collection]:
Expand All@@ -4318,6 +4320,8 @@ def violinplot(
points=points,
bw_method=bw_method,
side=side,
facecolor=facecolor,
linecolor=linecolor,
**({"data": data} if data is not None else {}),
)

Expand Down
73 changes: 73 additions & 0 deletionslib/matplotlib/tests/test_axes.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4021,6 +4021,79 @@ 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))

# Color sequence
N = len(data)
positions = range(N)
facecolors = ['k', 'r', ('b', 0.5), ('g', 0.2)]
linecolors = [('y', 0.4), 'b', 'm', ('k', 0.8)]

# Test image
fig_test = plt.figure()
ax = fig_test.gca()
parts_test = ax.violinplot(data,
positions=positions,
facecolor=facecolors,
linecolor=linecolors,
**kwargs)

body_colors = [p.get_facecolor() for p in parts_test["bodies"]]
assert_colors_equal(body_colors, mcolors.to_rgba_array(facecolors))

for part in ["cbars", "cmins", "cmaxes", "cmeans", "cmedians"]:
colors_test = parts_test[part].get_edgecolor()
assert_colors_equal(colors_test, mcolors.to_rgba_array(linecolors))


@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
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp