Skip to content
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

Add custom colorization to boxplot #1032

Merged
merged 14 commits into from
Jun 11, 2021
Merged

Add custom colorization to boxplot #1032

merged 14 commits into from
Jun 11, 2021

Conversation

HenriDeh
Copy link
Contributor

@HenriDeh HenriDeh commented Jun 8, 2021

Hello,

I have noticed a missing feature in Makies boxplots which is that you cannot make boxplots with different colors, which is a very common use of boxplots to show multiple experiments separated by a dodge. After looking into the code, I understood that this is because the way it handles the color of the outliers. Specifically, the length of the color attribute had to match the number of boxes (if not a scalar) but the outliercolor attribute was passed to scatter!, which means that its length has to match the number of outliers. Currently, when the outliercolor attribute is left to automatic, specifying boxes colors fails:

Julia> boxplot(rand(1:3, 1000), randn(1000), dodge = repeat([1,2], inner = 500), color = [1:2;1:2;1;2])
ERROR: MethodError: no method matching red(::Vector{Int64})
Closest candidates are:
  red(::ColorTypes.RGB24) at C:\Users\Henri\.julia\packages\ColorTypes\7OlxI\src\traits.jl:16
  red(::ColorTypes.AbstractRGB) at C:\Users\Henri\.julia\packages\ColorTypes\7OlxI\src\traits.jl:14
  red(::ColorTypes.ARGB32) at C:\Users\Henri\.julia\packages\ColorTypes\7OlxI\src\traits.jl:17
  ...
Stacktrace:
 [1] (::Makie.var"#797#808")(outliercolor::Makie.Automatic, color::Vector{Int64})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\stats\boxplot.jl:175
 [2] lift(f::Function, o1::Observable{Any}, rest::Observable{Any}; kw::Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\interaction\nodes.jl:13
 [3] lift(f::Function, o1::Observable{Any}, rest::Observable{Any})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\interaction\nodes.jl:10
 [4] plot!(plot::BoxPlot{Tuple{Vector{Int64}, Vector{Float64}}})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\stats\boxplot.jl:172
 [5] plot!(scene::Scene, P::Type{BoxPlot{Tuple{Vector{Int64}, Vector{Float64}}}}, attributes::Attributes, input::Tuple{Observable{Vector{Int64}}, Observable{Vector{Float64}}}, args::Observable{Tuple{Vector{Int64}, Vector{Float64}}})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\interfaces.jl:787
 [6] plot!(::Scene, ::Type{BoxPlot{ArgType} where ArgType}, ::Attributes, ::Vector{Int64}, ::Vararg{Any, N} where N; kw_attributes::Base.Iterators.Pairs{Symbol, Bool, Tuple{Symbol}, NamedTuple{(:show_axis,), Tuple{Bool}}})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\interfaces.jl:698
 [7] plot(::Type{BoxPlot{ArgType} where ArgType}, ::Vector{Int64}, ::Vararg{Any, N} where N; axis::NamedTuple{(), Tuple{}}, figure::NamedTuple{(), Tuple{}}, kw_attributes::Base.Iterators.Pairs{Symbol, Vector{Int64}, Tuple{Symbol, Symbol}, NamedTuple{(:dodge, :color), Tuple{Vector{Int64}, Vector{Int64}}}})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\figureplotting.jl:28
 [8] boxplot(::Vector{Int64}, ::Vararg{Any, N} where N; attributes::Base.Iterators.Pairs{Symbol, Vector{Int64}, Tuple{Symbol, Symbol}, NamedTuple{(:dodge, :color), Tuple{Vector{Int64}, Vector{Int64}}}})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\recipes.jl:15
 [9] top-level scope
   @ REPL[6]:1

Or with symbol colors:

julia> boxplot(rand(1:3, 1000), randn(1000), dodge = repeat([1,2], inner = 500), color = [:red, :blue, :red, :blue, :red, :blue])
ERROR: MethodError: no method matching red(::Vector{ColorTypes.RGBA{Float32}})
Closest candidates are:
  red(::ColorTypes.RGB24) at C:\Users\Henri\.julia\packages\ColorTypes\7OlxI\src\traits.jl:16
  red(::ColorTypes.AbstractRGB) at C:\Users\Henri\.julia\packages\ColorTypes\7OlxI\src\traits.jl:14
  red(::ColorTypes.ARGB32) at C:\Users\Henri\.julia\packages\ColorTypes\7OlxI\src\traits.jl:17
  ...
Stacktrace:
 [1] (::Makie.var"#797#808")(outliercolor::Makie.Automatic, color::Vector{Symbol})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\stats\boxplot.jl:175
 [2] lift(f::Function, o1::Observable{Any}, rest::Observable{Any}; kw::Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\interaction\nodes.jl:13
 [3] lift(f::Function, o1::Observable{Any}, rest::Observable{Any})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\interaction\nodes.jl:10
 [4] plot!(plot::BoxPlot{Tuple{Vector{Int64}, Vector{Float64}}})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\stats\boxplot.jl:172
 [5] plot!(scene::Scene, P::Type{BoxPlot{Tuple{Vector{Int64}, Vector{Float64}}}}, attributes::Attributes, input::Tuple{Observable{Vector{Int64}}, Observable{Vector{Float64}}}, args::Observable{Tuple{Vector{Int64}, Vector{Float64}}})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\interfaces.jl:787
 [6] plot!(::Scene, ::Type{BoxPlot{ArgType} where ArgType}, ::Attributes, ::Vector{Int64}, ::Vararg{Any, N} where N; kw_attributes::Base.Iterators.Pairs{Symbol, Bool, Tuple{Symbol}, NamedTuple{(:show_axis,), Tuple{Bool}}})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\interfaces.jl:698
 [7] plot(::Type{BoxPlot{ArgType} where ArgType}, ::Vector{Int64}, ::Vararg{Any, N} where N; axis::NamedTuple{(), Tuple{}}, figure::NamedTuple{(), Tuple{}}, kw_attributes::Base.Iterators.Pairs{Symbol, Vector{T} where T, Tuple{Symbol, Symbol}, NamedTuple{(:dodge, :color), Tuple{Vector{Int64}, Vector{Symbol}}}})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\figureplotting.jl:28
 [8] boxplot(::Vector{Int64}, ::Vararg{Any, N} where N; attributes::Base.Iterators.Pairs{Symbol, Vector{T} where T, Tuple{Symbol, Symbol}, NamedTuple{(:dodge, :color), Tuple{Vector{Int64}, Vector{Symbol}}}})
   @ Makie ~\OneDrive - UCL\Doctorat\Makie.jl\src\recipes.jl:15
 [9] top-level scope
   @ REPL[8]:1

That is because a scalar color is assumed when determining the outliercolor automatically. A partial workaround was thus to pass a custom value:

Julia> boxplot(rand(1:3, 1000), randn(1000), dodge = repeat([1,2], inner = 500), color = [1:2;1:2;1;2], outliercolor = :red)

image

But maybe we want the outlier colors to match that of the boxes. It is currently impossible to do this.

This PR fixes this. Now the automatic behavior is to color the outliers w.r.t. their boxes:

julia> boxplot(rand(1:3, 1000), randn(1000), dodge = repeat([1,2], inner = 500), color = [1:2;1:2;1:2])

image

This works also horizontally:

julia> boxplot(rand(1:3, 1000), randn(1000), dodge = repeat([1,2], inner = 500), color = [1:2;1:2;1:2], orientation = :horizontal)

image

One can still pass a custom scalar color for the boxes:

julia> boxplot(rand(1:3, 1000), randn(1000), dodge = repeat([1,2], inner = 500), color = :red)

makes 6 red boxplots with red outliers

Or a mapping for the boxes and a scalar for the outliers:

julia> boxplot(rand(1:3, 1000), randn(1000), dodge = repeat([1,2], inner = 500), color = [1:2;1:2;1:2], outliercolor = :red)

makes 6 purple and yellow boxes with red outliers

Or a custom mapping for the outliers, different from the boxes:

julia> boxplot(rand(1:3, 1000), randn(1000), dodge = repeat([1,2], inner = 500), color = [1:2;1:2;1:2], outliercolor = 1:6)

image

If these changes are accepted, I will also make some changes to the documentation to explain how this works.
I'm also open to adding some other features if you think this is incomplete.

Best

@HenriDeh
Copy link
Contributor Author

HenriDeh commented Jun 8, 2021

Oh and since there are no CI tests, all tests passed on my machine :
image

@SimonDanisch SimonDanisch requested a review from piever June 8, 2021 15:36
@SimonDanisch
Copy link
Member

Thank you :) I'll leave the review to @piever if possible...
Meanwhile, this example seems to be failing:

 Warning: failed to run `@example` block in src/plotting_functions/boxplot.md:9-18```@example
│ using CairoMakie
│ CairoMakie.activate!() # hide
│ Makie.inline!(true) # hide

│ xs = rand(1:3, 1000)
│ ys = randn(1000)

│ boxplot(xs, ys)
```
│   c.value =
│    MethodError: no method matching iterate(::RGBA{Float32})
│    Closest candidates are:iterate(::Union{LinRange, StepRangeLen}) at range.jl:664iterate(::Union{LinRange, StepRangeLen}, ::Int64) at range.jl:664iterate(::T) where T<:Union{Base.KeySet{var"#s79", var"#s78"} where {var"#s79", var"#s78"<:Dict}, Base.ValueIterator{var"#s77"} where var"#s77"<:Dict} at dict.jl:693...
└ @ Documenter.Expanders ~/.julia/packages/Documenter/xb5lD/src/Expanders.jl:563
┌ Warning: failed to run `@example` block in src/plotting_functions/boxplot.md:20-30```@example
│ using CairoMakie
│ CairoMakie.activate!() # hide
│ Makie.inline!(true) # hide

│ xs = rand(1:3, 1000)
│ ys = randn(1000)
│ dodge = rand(1:2, 1000)

│ boxplot(xs, ys, dodge = dodge, show_notch = true)
```
│   c.value =
│    MethodError: no method matching iterate(::RGBA{Float32})
│    Closest candidates are:iterate(::Union{LinRange, StepRangeLen}) at range.jl:664iterate(::Union{LinRange, StepRangeLen}, ::Int64) at range.jl:664iterate(::T) where T<:Union{Base.KeySet{var"#s79", var"#s78"} where {var"#s79", var"#s78"<:Dict}, Base.ValueIterator{var"#s77"} where var"#s77"<:Dict} at dict.jl:693...
└ @ Documenter.Expanders ~/.julia/packages/Documenter/xb5lD/src/Expanders.jl:563
[ Info: CrossReferences: building cross-references.
[ Info: CheckDocument: running document checks.
[ Info: Populate: populating indices.
ERROR: LoadError: `makedocs` encountered an error. Terminating build

@HenriDeh
Copy link
Contributor Author

HenriDeh commented Jun 8, 2021

Yes I forgot to take into account the case where the provided color is not a Symbol. My code is a bit hacky so it's not surprising that I did not cover all possibilities. I think it shoud be fine now. I hope there's no untested corner case.

src/stats/boxplot.jl Outdated Show resolved Hide resolved
src/stats/boxplot.jl Outdated Show resolved Hide resolved
@piever
Copy link
Contributor

piever commented Jun 9, 2021

Thanks for the PR! I've added a comment with a suggestion to make the code simpler / easier to reason about.

@HenriDeh
Copy link
Contributor Author

HenriDeh commented Jun 9, 2021

Ready to merge i think

@piever
Copy link
Contributor

piever commented Jun 9, 2021

This probably needs an example in the docs (to be added here). You can probably take one of the examples from violin, the exact same syntax should work..

About the interface, I think it'd be good to be consistent with violin (sorry for only bringing this up now, hadn't noticed the inconsistency before). At the moment violin requires color to be either a vector of the same length of the data, or a "scalar color". See also JuliaPlots/AbstractPlotting.jl#730 for some discussion on the API.

It should be pretty easy to get a consistent interface by reusing the code in the violin recipe. In particular, you'll probably need
getuniquevalue(color, idxs) as defined at https://github.com/JuliaPlots/Makie.jl/blob/master/src/stats/violin.jl#L70 to get the unique colors for the crossbar.

There is only a minor twist compared to violin because of the outliers, which could be all of different colors in principle (so no getuniquevalue there, but just accessing the vector by the index of the outlier within plot[2]).

@HenriDeh
Copy link
Contributor Author

HenriDeh commented Jun 10, 2021

Sorry I'm stuck and I'm spending way too much time on this. Please see in the latest commit how I'm trying to do your approach. I get this error because I can't find a way to store the colors in a convertible type. Can you help me with this stacktrace ?

julia> boxplot(rand(1:3,1000), randn(1000), dodge = repeat(1:2,500), color = repeat(1:2,500))
ERROR: MethodError: Cannot `convert` an object of type Observable{Any} to an object of type ColorTypes.RGBA{Float32}
Closest candidates are:
  convert(::Type{C}, ::ColorTypes.Color, ::Any) where C<:ColorTypes.TransparentColor at /home/dehaybe/.julia/packages/ColorTypes/7OlxI/src/conversions.jl:78
  convert(::Type{C}, ::C) where C<:ColorTypes.Colorant at /home/dehaybe/.julia/packages/ColorTypes/7OlxI/src/conversions.jl:72
  convert(::Type{C}, ::ColorTypes.Colorant) where C<:ColorTypes.Colorant at /home/dehaybe/.julia/packages/ColorTypes/7OlxI/src/conversions.jl:73
  ...
Stacktrace:
  [1] ColorTypes.RGBA{Float32}(x::Observable{Any})
    @ ColorTypes ~/.julia/packages/ColorTypes/7OlxI/src/types.jl:546
  [2] (::Makie.var"#562#564"{GeometryBasics.Mesh{2, Float32, GeometryBasics.Ngon{2, Float32, 3, Point{2, Float32}}, GeometryBasics.SimpleFaceView{2, Float32, 3, GeometryBasics.OffsetInteger{-1, UInt32}, Point{2, Float32}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}})(meshes::Vector{GeometryBasics.Mesh{2, Float32, GeometryBasics.Ngon{2, Float32, 3, Point{2, Float32}}, GeometryBasics.SimpleFaceView{2, Float32, 3, GeometryBasics.OffsetInteger{-1, UInt32}, Point{2, Float32}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}}, colors::Vector{Observable{Any}}, cmap::Nothing, crange::Nothing)
    @ Makie ~/.julia/packages/Makie/wqnws/src/basic_recipes/poly.jl:159
  [3] lift(::Function, ::Observable{Vector{GeometryBasics.Mesh{2, Float32, GeometryBasics.Ngon{2, Float32, 3, Point{2, Float32}}, GeometryBasics.SimpleFaceView{2, Float32, 3, GeometryBasics.OffsetInteger{-1, UInt32}, Point{2, Float32}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}}}, ::Observable{Any}, ::Vararg{Observable{Any}, N} where N; kw::Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/interaction/nodes.jl:13
  [4] lift(::Function, ::Observable{Vector{GeometryBasics.Mesh{2, Float32, GeometryBasics.Ngon{2, Float32, 3, Point{2, Float32}}, GeometryBasics.SimpleFaceView{2, Float32, 3, GeometryBasics.OffsetInteger{-1, UInt32}, Point{2, Float32}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}}}, ::Observable{Any}, ::Observable{Any}, ::Vararg{Observable{Any}, N} where N)
    @ Makie ~/.julia/packages/Makie/wqnws/src/interaction/nodes.jl:10
  [5] plot!(plot::Mesh{Tuple{Vector{GeometryBasics.Mesh{2, Float32, GeometryBasics.Ngon{2, Float32, 3, Point{2, Float32}}, GeometryBasics.SimpleFaceView{2, Float32, 3, GeometryBasics.OffsetInteger{-1, UInt32}, Point{2, Float32}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/basic_recipes/poly.jl:149
  [6] plot!(scene::Poly{Tuple{Vector{GeometryBasics.HyperRectangle{2, Float32}}}}, P::Type{Mesh{ArgType} where ArgType}, attributes::Attributes, args::Observable{Vector{GeometryBasics.Mesh{2, Float32, GeometryBasics.Ngon{2, Float32, 3, Point{2, Float32}}, GeometryBasics.SimpleFaceView{2, Float32, 3, GeometryBasics.OffsetInteger{-1, UInt32}, Point{2, Float32}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/interfaces.jl:815
  [7] #plot!#218
    @ ~/.julia/packages/Makie/wqnws/src/interfaces.jl:642 [inlined]
  [8] mesh!(::Poly{Tuple{Vector{GeometryBasics.HyperRectangle{2, Float32}}}}, ::Vararg{Any, N} where N; attributes::Base.Iterators.Pairs{Symbol, Observable{Any}, NTuple{9, Symbol}, NamedTuple{(:visible, :shading, :color, :colormap, :colorrange, :overdraw, :fxaa, :transparency, :inspectable), NTuple{9, Observable{Any}}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/recipes.jl:19
  [9] plot!(plot::Poly{Tuple{Vector{GeometryBasics.HyperRectangle{2, Float32}}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/basic_recipes/poly.jl:116
 [10] plot!(scene::CrossBar{NTuple{4, Vector{Float32}}}, P::Type{Poly{ArgType} where ArgType}, attributes::Attributes, args::Observable{Vector{GeometryBasics.HyperRectangle{2, Float32}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/interfaces.jl:815
 [11] #plot!#218
    @ ~/.julia/packages/Makie/wqnws/src/interfaces.jl:642 [inlined]
 [12] poly!(::CrossBar{NTuple{4, Vector{Float32}}}, ::Vararg{Any, N} where N; attributes::Base.Iterators.Pairs{Symbol, Observable{Any}, NTuple{6, Symbol}, NamedTuple{(:color, :colorrange, :colormap, :strokecolor, :strokewidth, :inspectable), NTuple{6, Observable{Any}}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/recipes.jl:19
 [13] plot!(plot::CrossBar{NTuple{4, Vector{Float32}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/stats/crossbar.jl:105
 [14] plot!(::BoxPlot{Tuple{Vector{Int64}, Vector{Float64}}}, ::Type{CrossBar{ArgType} where ArgType}, ::Attributes, ::Observable{Vector{Float32}}, ::Observable{Vector{Float32}}, ::Observable{Vector{Float32}}, ::Vararg{Observable{Vector{Float32}}, N} where N)
    @ Makie ~/.julia/packages/Makie/wqnws/src/interfaces.jl:815
 [15] plot!(::Type{CrossBar{ArgType} where ArgType}, ::BoxPlot{Tuple{Vector{Int64}, Vector{Float64}}}, ::Observable{Vector{Float32}}, ::Vararg{Observable{Vector{Float32}}, N} where N; kw_attributes::Base.Iterators.Pairs{Symbol, Observable, NTuple{15, Symbol}, NamedTuple{(:color, :colorrange, :colormap, :strokecolor, :strokewidth, :midlinecolor, :midlinewidth, :show_midline, :orientation, :width, :show_notch, :notchmin, :notchmax, :notchwidth, :inspectable), Tuple{Observable{Vector{Observable{Any}}}, Observable{Any}, Observable{Any}, Observable{Any}, Observable{Any}, Observable{Any}, Observable{Any}, Observable{Any}, Observable{Any}, Observable{Float64}, Observable{Any}, Observable{Makie.Automatic}, Observable{Makie.Automatic}, Observable{Any}, Observable{Any}}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/interfaces.jl:642
 [16] crossbar!(::BoxPlot{Tuple{Vector{Int64}, Vector{Float64}}}, ::Vararg{Any, N} where N; attributes::Base.Iterators.Pairs{Symbol, Observable, NTuple{15, Symbol}, NamedTuple{(:color, :colorrange, :colormap, :strokecolor, :strokewidth, :midlinecolor, :midlinewidth, :show_midline, :orientation, :width, :show_notch, :notchmin, :notchmax, :notchwidth, :inspectable), Tuple{Observable{Vector{Observable{Any}}}, Observable{Any}, Observable{Any}, Observable{Any}, Observable{Any}, Observable{Any}, Observable{Any}, Observable{Any}, Observable{Any}, Observable{Float64}, Observable{Any}, Observable{Makie.Automatic}, Observable{Makie.Automatic}, Observable{Any}, Observable{Any}}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/recipes.jl:19
 [17] plot!(plot::BoxPlot{Tuple{Vector{Int64}, Vector{Float64}}})
    @ Makie ~/Documents/Doctorat/Makie.jl/src/stats/boxplot.jl:213
 [18] plot!(scene::Scene, P::Type{BoxPlot{Tuple{Vector{Int64}, Vector{Float64}}}}, attributes::Attributes, input::Tuple{Observable{Vector{Int64}}, Observable{Vector{Float64}}}, args::Observable{Tuple{Vector{Int64}, Vector{Float64}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/interfaces.jl:787
 [19] plot!(::Scene, ::Type{BoxPlot{ArgType} where ArgType}, ::Attributes, ::Vector{Int64}, ::Vararg{Any, N} where N; kw_attributes::Base.Iterators.Pairs{Symbol, Bool, Tuple{Symbol}, NamedTuple{(:show_axis,), Tuple{Bool}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/interfaces.jl:698
 [20] plot(::Type{BoxPlot{ArgType} where ArgType}, ::Vector{Int64}, ::Vararg{Any, N} where N; axis::NamedTuple{(), Tuple{}}, figure::NamedTuple{(), Tuple{}}, kw_attributes::Base.Iterators.Pairs{Symbol, Vector{Int64}, Tuple{Symbol, Symbol}, NamedTuple{(:dodge, :color), Tuple{Vector{Int64}, Vector{Int64}}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/figureplotting.jl:28
 [21] boxplot(::Vector{Int64}, ::Vararg{Any, N} where N; attributes::Base.Iterators.Pairs{Symbol, Vector{Int64}, Tuple{Symbol, Symbol}, NamedTuple{(:dodge, :color), Tuple{Vector{Int64}, Vector{Int64}}}})
    @ Makie ~/.julia/packages/Makie/wqnws/src/recipes.jl:15
 [22] top-level scope
    @ REPL[36]:1

I also think the API is getting weird this way because we use color to specify the color of the boxes, but with data length, and we use outliercolor to specify the color of the outliers if we want to change them. It would be much easier to have color of nboxes length and outliercolor of data length. I think it also makes more sense with boxplots to think of one box as one element (one element is a median + other quartiles), just like a histogram bin is one element : the number of data points (n) counted in the bar, and not n elements:

data = randn(1000)
hist(data, bins = 10, color = 1:10)

image

Boxplot is a composition of two plots and it makes sense that attributes lengths are different for both. I know this has been discussed at length for violins, but I kind of think the same logic should apply. It would also make as much sense for boxplot's api to be consistent with crossbar (which works with box length and not data length).

@HenriDeh
Copy link
Contributor Author

Okay I just needed a tea break. Now it's working, here are the possible calls:
Automatic match between OL and boxes :

boxplot(rand(1:3,1000), randn(1000), dodge = repeat(1:2,500), color = repeat(1:2,500))

image
custom outlier colors:

boxplot(rand(1:3,1000), randn(1000), dodge = repeat(1:2,500), color = repeat(1:2,500), outliercolor = rand(1:6,1000))

image

scalar outliercolor:

boxplot(rand(1:3,1000), randn(1000), dodge = repeat(1:2,500), color = repeat(1:2,500), outliercolor = :red)

scalar color :

boxplot(rand(1:3,1000), randn(1000), dodge = repeat(1:2,500), color = :red)

both scalar :

boxplot(rand(1:3,1000), randn(1000), dodge = repeat(1:2,500), color = :red, outliercolor = :blue)

scalar color and custom outliercolors

boxplot(rand(1:3,1000), randn(1000), dodge = repeat(1:2,500), color = :red, outliercolor = rand(1:9,1000))

I think this covers all cases. I still think the logic of the API should be length of boxes but I understand that consistency is key. I guess this is a discussion for another PR, maybe for 1.0. In the meantime, this API allows any level of customization.

src/stats/boxplot.jl Outdated Show resolved Hide resolved
src/stats/boxplot.jl Outdated Show resolved Hide resolved
src/stats/boxplot.jl Outdated Show resolved Hide resolved
Co-authored-by: Pietro Vertechi <[email protected]>
@HenriDeh
Copy link
Contributor Author

Do I need to do something to merge to master ?

@SimonDanisch
Copy link
Member

Thank you for this PR and your perseverance to push it to the finish line! :)

@SimonDanisch SimonDanisch merged commit 28c995d into MakieOrg:master Jun 11, 2021
@HenriDeh
Copy link
Contributor Author

No problem, thank you for the feedback and for maintaining this nice package :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants