-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Thanks! Can you please add a test for this? I think either an image test, e.g. with a number of subplots using different color arguments, or an image comparison test where one check that using the arguments and setting the colors afterwards result in identical images. Edit: see https://matplotlib.org/stable/devel/testing.html#writing-an-image-comparison-test if more info on the testing is required. |
I couldn't find a doc-page for that, but in https://github.com/matplotlib/matplotlib/tree/main/doc/users/next_whats_new there are small notes to be included in the next What's New. Please add one for this feature, ideally with an example showing how it works to highlight this new and useful feature. |
Test added, I have never written one but I believe this should work as expected. |
And now there's a whats new page. I think that covers your requests. Let me know if there's anything else to do! Thanks for your feedback and support. |
You might want to install the pre-commit hooks to help with the linting/formatting errors ETA: Also, thanks for jumping headfirst into this, this is a good feature to have. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor comments.
Maybe one should change both face- and edgecolor (to different values) in one of the subplots to confirm that it also works?
Good point. I added two new subplot axes to the test - one that changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand if you feel this is out of scope, but since you're already implementing these parameters how do you feel about implementing them vectorized? Meaning that you can also pass in a list of colors/facecolor/edgecolor like in bar?
Good idea! That's now implemented with documentation, a test for this feature, and an example for the gallery. It's implemented the same way as it is in the |
lib/matplotlib/axes/_axes.py
Outdated
@@ -8226,6 +8227,21 @@ def violinplot(self, dataset, positions=None, vert=True, widths=0.5, | |||
its only parameter and return a scalar. If None (default), 'scott' | |||
is used. | |||
|
|||
facecolor : color or list of colors or None; see :ref:`colors_def` | |||
If provided, will set the face color(s) of the violin plots. The alpha | |||
value is automatically set to 0.3. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to figure out what the semantics should be to remain consistent with current behavior... It feels wrong to override the alpha value of a user provided color...
Though I will also note that the 0.3
alpha is actually applied to the patch object as a whole, not its color values...
Makes me wonder if the "better" solution would be to just pass **kwargs
to lower level artists (But I think that gets complicated quickly because some of them take the "edgecolor" as "color", for instance..., and other values would have to be filtered out likely...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it too much trouble to do a behavior change to 'alpha is now set via the color/must be set explicitly/behaves like alpha in bar/pie/etc?' -> it's a little pain now, but it pulls towards more overall cohesiveness
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point. I think that if the user doesn't specify the alpha, it makes sense to set it to 0.3. That just looks good, and honestly I'd prefer that as someone who wants to use the violinplot method with these new features.
I added a commit that changes the behavior:
If the user specifies the alpha, then that is the alpha that is used. If the user doesn't specify the alpha, then it defaults to 0.3.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for being late to the party.
Up to now, styling was not configurable at all. That was simple. When we now start to add options, we should be extremely careful with the API. To be consistent, we need to look towards bar()
as well as boxplot()
.
bar()
doc:
color
for the bar facesedgecolor
for the bar edgesecolors
for the errorbars
but inkwargs
, we have the standardcolor
,edgecolor
,facecolor
behavior, wherecolor
sets both edge and face. This conflicts with the color doc above. We need to check what's actually happening.
boxplot()
Styling is configured via dicts for the individual parts
boxprops
capprops
whiskerprops
flierprops
medianprops
meanprops
This allows very detailed configuration, but is cumbersome to use.
violinplot()
We have the following parts
- bodies
- cmeans
- cmins
- cmaxes
- cbars
- cmedians
- cquantiles
To follow the concept of boxplot()
, we would need to make them available via *props
, but that's quite a lift, and I'm unsure on the usefulness.
Generally, the proposed global color settings seem useful (and that doesn't mean we cannot add *props
later or add color kwargs to boxplot()
).
For now the two minimal requirements if have on API consistency are
- Check the
color
behavior ofbar()
and be consistent with it or argue why we should behave differently here. - Is
edgecolor
the correct name? Does it color thebodies
edges (if they have a finite width)? Should it?
@timhoffm I can tell you from my perspective as someone who thinks about matplotlib primarily as a user (this is my first PR) I'm very happy to have a plotting function that allows me to make something look good very quickly, but that has the option of more detailed customization if required. The way the violinplot works now (after this PR) seems to fit that criteria. You can make a violin or set of violins with a color scheme that works, in one line of code, then go and edit individual objects later if you need to. --
The color behavior between the new violinplot() and bar() are identical in terms of how the colors are handled within the function. However, bar() only has edgecolor and color as inputs. I changed the violinplot() to have edgecolor, facecolor, and color (which overwrites the other two) in response to @ksunden's point in issue #27298. This makes it consistent with patches. In my opinion, this makes a lot of sense, because a user might want specific control over the lines and patch within the violin plot (i.e. using edgecolor/facecolor), or might want to make everything look the same (i.e. using color). That seems like a perfect compromise between customizability and simplicity. So if anything, maybe bar() should copy this behavior? Extra point: I think this edgecolor/facecolor/color schema additionally makes sense given the way I plan to handle alpha values, which I'll explain in the response to @ksunden and @story645 in the above thread. --
I changed the name to edgecolor from linecolor because @ksunden and @story645 pointed out that that's the typical name for the rest of the library. I agree with that reasoning. As it is, the bodies by default do not have an edgeline for violinplots. This is how it was before and still is. I think this is great, because it creates a very clean looking plot, and if the user wants to, they can go in and add an edge to the bodies. We can add this as an example to the customized violin plot in the gallery for the interested user if you think that's a good idea. |
I guess I had it in my head when I made the original suggestion that the main shape were by default drawn with edges, which isn't actually true. I think Tim is right that I do think that the most full solution would be to do something like
|
Not to complicate things further but regarding the earlier discussion it also may be helpful to include an argument like Also, I'm not sure I understand why the tests failed. And the Main Pytest Windows_py39 failed because: But I'm not sure why that is happening or how it relates to the commits I've made. |
We added support for |
It shouldn't be directly related. While it is weird that it seems to happen consistently on this PR (and some others), there is no obvious reason why it happens. |
Yep. I now removed the alpha handling so the alpha will just directly be controlled by |
I just wanted to ping the reviewers in case there is continued interest in this PR. I'm happy to continue working on the changes to the violinplot constructor, but there was no clear agreement on how best to manage the additional color arguments. It seems to me like there is tension between which other plotting method In my opinion as a user, I'm very happy to have a few simple kwargs (like in |
@landoskape thanks for following up. We've got a developers call Thursday at I think 8:00PM UTC) where I think it might be good to discuss this - please join if you can make it: https://scientific-python.org/calendars/ I tend to agree w/ @landoskape on keeping it simple: color/face/edge & if folks want more complicated then they can pop into the artist layer. |
So looking at how seaborn just exposes body color, @mwaskom is one proposal (bar plot vs box plot) preferable? seaborn.violinplot(data=None, *, x=None, y=None, hue=None, order=None, hue_order=None, orient=None, color=None, palette=None, saturation=0.75, fill=True, inner='box', split=False, width=0.8, dodge='auto', gap=0, linewidth=None, linecolor='auto', cut=2, gridsize=100, bw_method='scott', bw_adjust=1, density_norm='area', common_norm=False, hue_norm=None, formatter=None, log_scale=None, native_scale=False, legend='auto', scale=<deprecated>, scale_hue=<deprecated>, bw=<deprecated>, inner_kws=None, ax=None, **kwargs) |
How does this look? |
lib/matplotlib/tests/test_axes.py
Outdated
# 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, 5)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 5)] | |
data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 2)] |
Two violins should be enough. We don't get more information out of more violins. It only takes more computation and the image is more cramped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fair point. I made it 3 so that we can set facecolor&linecolor, just facecolor, or just linecolor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. please have a quick visual check of the generated figure to ensure the violins have still a decent width (you have 5 subplots next to each other).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks good with just 3 subplots (you can clearly see the details of each violin)
The code is good. Left some comments on documentation and tests. |
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
Changes addressed
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
…plotlib into violin-plot-color
Minor formatting and code cleanup
I've added some minor formatting and code cleanup. See last commit. |
Looks good, thanks for the cleanup! that |
@landoskape Thanks for the patience to bring this through! |
Woot! Thanks for working with me on it. Glad it's part of matplotlib now |
@timhoffm I was in the middle of reviewing this, should I just do a follow up PR? |
@story645 sorry that was not obvious to me. Please make a followup PR. |
PR summary
This PR adds the option of specifying the fillcolor and linecolor of violinplots. It addresses
issue #27298 (and other discusssions and requests elsewhere).
PR checklist