-
Notifications
You must be signed in to change notification settings - Fork 10
grouping prototype #7
Conversation
At the moment one can also pass a function or a dict rather than an array as a scale, for example: julia> scatter((:color => rand(Bool, 10) => t -> t ? :blue : :red, :markersize => rand(Bool, 10) => [0.03, 0.2]), rand(10), rand(10)) |
This now requires JuliaPlots/AbstractPlotting.jl#35 and MakieOrg/Makie.jl#203 if people want to try it out. I now use a special N = 20
scatter(
Group(
color = rand(Bool, 10) => [:blue, :red],
markersize = rand(Bool, 10) => [0.03, 0.2]),
rand(10),
rand(10)
) and I've started adding default scales: scatter(
Group(color = rand(1:4, N), marker = rand(Bool, N)),
rand(N),
rand(N)
) In this case To reduce verbosity, if no keyword is specified, it defaults to grouping over color, i.e. scatter( Group(rand(1:4, 10)), rand(N), rand(N)) is equivalent to scatter( Group(color=rand(1:4, 10)), rand(N), rand(N)) which is helpful for those used to the behavior of Plots. Important design remark: Makie is quite different from Plots in that there is no pipeline to process keywords sequentially, so for |
I feel it's making good progress. Now I can have some GoG flavor already. Default grouping is by color, like in Plots. Positional arguments and "styling that does not group" goes into Style. Outside Style are arguments that don't use the dataset. using StatsMakie, RDatasets
julia> iris = RDatasets.dataset("datasets", "iris");
julia> scatter(iris, Group(:Species), Style(:SepalLength, :SepalWidth)) Grouping by more than one thingjulia> mpg = RDatasets.dataset("ggplot2", "mpg");
julia> scatter(mpg,
Group(marker = :Manufacturer, color = :Class),
Style(:Displ, :Hwy), markersize = 1
) Give a pair of column and scale to use a custom scale:julia> scatter(mpg,
Group(marker = :Manufacturer, color = :Class => [:blue, :cyan, :magenta]), #cycle through these colors
Style(:Displ, :Hwy), markersize = 1
) Adding a continuous colorjulia> scatter(mpg,
Group(marker = :Manufacturer),
Style(:Displ, :Hwy, color = :Hwy), markersize = 1
) Building complex graphics from simple onesThe user can pass many group objects and styles that are merged together, for example: julia> scatter(mpg,
Group(marker = :Manufacturer), Group(:Class),
Style(:Displ, :Hwy), markersize = 1
)
julia> scatter(mpg,
Group(marker = :Manufacturer),
Style(:Displ, :Hwy), Style(color = :Hwy), markersize = 1
) which is the equivalent of adding things with the TODO list:
julia> scatter(Group(marker = rand(Bool, 10)), rand(10), markersize = 0.1:0.1:1)
Error showing value of type Scene:
ERROR: MethodError: no method matching glyph_scale!(::Observables.Observable{Char}, ::Array{Vec{2,Float32},1})
Closest candidates are:
glyph_scale!(::Char, ::Any) at /home/pietro/.julia/dev/AbstractPlotting/src/utilities/texture_atlas.jl:128 |
Looking good! The one comment I have is that to me it seems more general to have edit: or probably |
That's a good point. The support for many groups should happen for all recipes if I understand the Makie pipeline correctly (I'll be honest, that's a big if :) ). OTOH I agree that it's generally useful to play with this group / style objects as a user outside of plot calls: I guess one may even want to save them to disk for future sessions. Now you can manually combine groups and styles with each other using |
Right, I actually edited my post as well about I think as a first step it might make sense to just go with |
I like the argument for |
This is really progressing sweetly. Question: would it be clearer to do |
FWIW I had the thought that |
We mentioned this option already at JuliaPlots/Plots.jl#1530 and I initially decided against it here, but looking more closely it's a very nice solution. My "pair" strategy never seemed to impress instead the The only thing I'd like to mention is that we may need to allow using custom types for fancy scale behavior. For example, let's say I have |
Good point, but I think quite often that would mostly happen 1) in connection to facets or some other behaviour that groups over the entire dataset to create subplots/series, in which case the problem would not arise. In the relatively rare case where you'd want to create a single plot just on a subset of the data but still have the colors comparable to other unrelated plots (/scenes) in the paper, would that not be easily taken care off by passing a color vector explicitly? |
So, I've reimplemented it the way we suggested. What I'm doing is, I'm creating a trait So now it's something like this: using RDatasets
mpg = RDatasets.dataset("ggplot2", "mpg")
p1 = scatter(mpg,
Group(marker = :Class),
Style(:Displ, :Hwy), Style(color = :Hwy), markersize = 1,
)
new_theme = Theme(
scatter = Theme(marker = [:circle, :diamond])
)
AbstractPlotting.set_theme!(new_theme)
p2 = scatter(mpg,
Group(marker = :Class),
Style(:Displ, :Hwy), Style(color = :Hwy), markersize = 1,
)
vbox(p1, p2) |
This is basically ready, I think it only needs some bikeshedding on names, argument order, etcetera. Here we introduce 3 things: grouping support, "statistic" support and table support. Grouping is done by passing a scatter(Group(marker = rand(Bool, 100)), rand(100), rand(100)) "Statistics" (Grammar of Graphics terminology) is done by passing a function as first argument: plot(kde, rand(100)) They can be combined: plot(kde, Group(linestyle = rand(Bool, 100)), rand(100)) Table support is done by passing the table as first argument and the columns can be given as scatter(iris, Style(:SepalLength, :SepalWidth, color = :PetalLength)) It can also be mixed with a "statistic" and I use the same order as IndexedTables plot(kde, iris, Group(linestyle = :Species), Style(:SepalLength)) Here the order of I'm reasonably happy with the API (we can add extra methods to simplify some scenarios later), but one remark was that while OTOH, it is quite nice to have a unique concept for positional arguments and attributes, so I was planning to merge as is unless there are strong counter proposals wrt API. |
Another issue that it'd be nice to figure out is the following: kde((rand(100), rand(100)) # for a 2D distribution This leads to: plot(kde, Style((:x, :y)) Which looks a bit clumsy. Should we have our own |
I think it makes sense to implement local functions for Finally I'll say it's really impressive how you've managed to implement a nice and understandable Julia-ish API on Makie that allows very powerful GOG. This is one of my favourite PRs I've seen in a long time. |
I'd like to second that. Lately I have actively been eyeing my notifications specifically to see if you added new things here |
in this example: scatter(
Group(marker = rand(Bool, 100)),
rand(100), rand(100)
) how do i specify the two markers to choose from? My first naive guess based on earlier examples would be scatter(
Group(marker = rand(Bool, 100)),
rand(100), rand(100),
marker = [:circle, :rect]
) right? and if yes is this also true for tables (i.e. is the last |
Yes, you guessed correctly, it's: scatter(
Group(marker = rand(Bool, 100)),
rand(100), rand(100),
marker = [:circle, :rect]
) and it also works for tables. The nice thing about wrapping things in A cleaner way (proposed by Michael), is to set this thing in the theme: julia> new_theme = Theme(
scatter = Theme(
marker = [:rect, :circle], # don't do this! it works in this case, but we should use a Palette type!
markersize = 0.3
)
)
julia> set_theme!(new_theme)
julia> scatter(
Group(marker = rand(Bool, 100)),
rand(100), rand(100)
) Even though for colors we have the nice |
This looks very nice! To me it would be more natural to do something like
It at first seems kind of cool to be able to mix the different types of keyword arguments, but in the end I think it is more confusing to do so. |
@dpsanders yes, but how would you do that in practice? Given that @piever Could the |
Define a |
Yes but that's already possible, no? That's just defining recipes, like there should also be a recipe for |
Ah, I see what you mean - then you wouldn't get the extraction. So it would be to call plot(KDE(bandwidth = 0.1), iris, Style(:SepalWidth)) ? |
Yes, exactly. |
That's a very good point. However would currying address this? Meaning, it'd be enough to add a method: kde(; kwargs...) = (args...) -> kde(args...; kwargs...) and then plot(kde(bandwidth = 0.1), iris, Style(:SepalWidth)) would work as expected. If the KernelDensity devs are happy to add this method, we could just do this, otherwise we would define our own: density(; kwargs...) = (args...) -> kde(args...; kwargs...)
density(args...; kwargs...) = kde(args...; kwargs...) The "callable type" can also be made to work but I feel it's quite clumsy in the case where there are no keywords as you'd have to do: plot(KDE(), iris, Style(:SepalWidth)) That being said, I can quite easily add support to callable types, by adding a method: convert_arguments(P::PlotFunc, t::AbstractCallable, args...; kwargs...) =
convert_arguments(P, (v...) -> t(v...), args...; kwargs...) Is there any strong preference between curried functions and callable structs? |
I'm actually not sure, it may be better to ask @SimonDanisch, but I'm afraid this would cause issues if users actually want to pass a vector as a keyword argument (say |
This is a WIP to play around with ideas from JuliaPlots/Plots.jl#1530 in StatMakie.
Instead of a
group
keyword I'm using the first argument as I don't know how to implement this in Makie otherwise...The idea is that one can use the first argument to do some grouping and define some keywords on those grouping. Here for example I'm plotting a scatter plot of
rand(10)
versusrand(10)
using two vectorsrand(Bool,10)
andrand(Bool, 10)
to specify color and markersize respectively. Then I'm passing the list of colors and markersizes (this part should be optional and default to something sensible, but it is not at the moment).Would be happy to have feedback on syntax (I'm starting to like the
:color => v => scale
syntax but different people may have different taste) and on whether this should be closer to Plots grouping (meaning only work for categorical values) or to GoG aesthetics (also accept continuous values and try to detect automatically which it is).In combination with
@df
this could be used with column names rather than vectors and should provide some automated legend entries for the group as well.