From fdf3b3f581679ba2ce444d0074ecb4b5f22063b5 Mon Sep 17 00:00:00 2001 From: Thomas Breloff Date: Thu, 7 Apr 2016 17:05:31 -0400 Subject: [PATCH] added violin plot --- src/Plots.jl | 4 +++ src/args.jl | 2 +- src/backends/supported.jl | 4 +-- src/recipes.jl | 56 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/Plots.jl b/src/Plots.jl index c894e9e9..fc3d87d0 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -68,6 +68,8 @@ export abline!, boxplot, boxplot!, + violin, + violin!, title!, xlabel!, @@ -192,6 +194,8 @@ scatter3d(args...; kw...) = plot(args...; kw..., linetype = :scatter3d) scatter3d!(args...; kw...) = plot!(args...; kw..., linetype = :scatter3d) boxplot(args...; kw...) = plot(args...; kw..., linetype = :box) boxplot!(args...; kw...) = plot!(args...; kw..., linetype = :box) +violin(args...; kw...) = plot(args...; kw..., linetype = :violin) +violin!(args...; kw...) = plot!(args...; kw..., linetype = :violin) title!(s::AbstractString; kw...) = plot!(; title = s, kw...) diff --git a/src/args.jl b/src/args.jl index a110cf0f..8c7460f0 100644 --- a/src/args.jl +++ b/src/args.jl @@ -11,7 +11,7 @@ const _3dTypes = [:path3d, :scatter3d, :surface, :wireframe] const _allTypes = vcat([ :none, :line, :path, :steppre, :steppost, :sticks, :scatter, :heatmap, :hexbin, :hist, :hist2d, :hist3d, :density, :bar, :hline, :vline, :ohlc, - :contour, :pie, :shape, :box + :contour, :pie, :shape, :box, :violin ], _3dTypes) @compat const _typeAliases = KW( :n => :none, diff --git a/src/backends/supported.jl b/src/backends/supported.jl index d27c9404..63cdee5c 100644 --- a/src/backends/supported.jl +++ b/src/backends/supported.jl @@ -80,7 +80,7 @@ supportedArgs(::GadflyBackend) = [ ] supportedAxes(::GadflyBackend) = [:auto, :left] supportedTypes(::GadflyBackend) = [:none, :line, :path, :steppre, :steppost, :sticks, - :scatter, :hist2d, :hexbin, :hist, :bar, :box, + :scatter, :hist2d, :hexbin, :hist, :bar, :box, :violin, :hline, :vline, :contour, :shape] supportedStyles(::GadflyBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] supportedMarkers(::GadflyBackend) = vcat(_allMarkers, Shape) @@ -167,7 +167,7 @@ supportedArgs(::PyPlotBackend) = [ ] supportedAxes(::PyPlotBackend) = _allAxes supportedTypes(::PyPlotBackend) = [:none, :line, :path, :steppre, :steppost, #:sticks, - :scatter, :hist2d, :hexbin, :hist, :density, :bar, :box, + :scatter, :hist2d, :hexbin, :hist, :density, :bar, :box, :violin, :hline, :vline, :contour, :path3d, :scatter3d, :surface, :wireframe, :heatmap] supportedStyles(::PyPlotBackend) = [:auto, :solid, :dash, :dot, :dashdot] # supportedMarkers(::PyPlotBackend) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :hexagon] diff --git a/src/recipes.jl b/src/recipes.jl index 5bed50bd..4d052d4d 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -43,6 +43,8 @@ apply_series_recipe(d::KW, lt) = KW[d] # ------------------------------------------------- # Box Plot +const _box_halfwidth = 0.4 + function apply_series_recipe(d::KW, ::Type{Val{:box}}) # dumpdict(d, "box before", true) # TODO: add scatter series with outliers @@ -58,7 +60,7 @@ function apply_series_recipe(d::KW, ::Type{Val{:box}}) q1,q2,q3,q4,q5 = quantile(d[:y][groupby.groupIds[i]], linspace(0,1,5)) # make the shape - l, m, r = i - 0.3, i, i + 0.3 + l, m, r = i - _box_halfwidth, i, i + _box_halfwidth xcoords = [ m, l, r, m, m, NaN, # lower T l, l, r, r, l, NaN, # lower box @@ -80,6 +82,58 @@ function apply_series_recipe(d::KW, ::Type{Val{:box}}) KW[d] end +# ------------------------------------------------- +# Violin Plot + +try + Pkg.installed("KernelDensity") + import KernelDensity + + warn("using KD for violin") + function violin_coords(y) + kd = KernelDensity.kde(y, npoints = 30) + kd.density, kd.x + end +catch + warn("using hist for violin") + function violin_coords(y) + edges, widths = hist(y, 20) + centers = 0.5 * (edges[1:end-1] + edges[2:end]) + ymin, ymax = extrema(y) + vcat(0.0, widths, 0.0), vcat(ymin, centers, ymax) + end +end + + +function apply_series_recipe(d::KW, ::Type{Val{:violin}}) + # dumpdict(d, "box before", true) + # TODO: add scatter series with outliers + + # create a list of shapes, where each shape is a single boxplot + shapes = Shape[] + d[:linetype] = :shape + groupby = extractGroupArgs(d[:x]) + + for (i, glabel) in enumerate(groupby.groupLabels) + + # get the edges and widths + y = d[:y][groupby.groupIds[i]] + widths, centers = violin_coords(y) + + # normalize + widths = _box_halfwidth * widths / maximum(widths) + + # make the violin + xcoords = vcat(widths, -reverse(widths)) + i + ycoords = vcat(centers, reverse(centers)) + push!(shapes, Shape(xcoords, ycoords)) + end + + d[:x], d[:y] = shape_coords(shapes) + d[:plotarg_overrides] = KW(:xticks => (1:length(shapes), groupby.groupLabels)) + + KW[d] +end # -------------------------------------------------