Skip to content

Commit

Permalink
Merge pull request #1240 from Mattriks/Stat_errorbar
Browse files Browse the repository at this point in the history
Stat.dodge
  • Loading branch information
bjarthur committed Jan 29, 2019
2 parents aa8dff9 + 1a355f6 commit 6240ca0
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 23 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ This is a log of major changes in Gadfly between releases. It is not exhaustive.
Each release typically has a number of minor bug fixes beyond what is listed here.

# Version 1.1.0
* Add `Stat.dodge` (#1240)
* `Stat.smooth(method=:lm)` confidence bands (#1231)


Expand Down
47 changes: 40 additions & 7 deletions docs/src/gallery/geometries.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,48 @@ hstack(pa,pb,pc)

```@example
using Gadfly, RDatasets, Distributions, Random
set_default_plot_size(14cm, 8cm)
set_default_plot_size(21cm, 8cm)
Random.seed!(1234)
sds = [1, 1/2, 1/4, 1/8, 1/16, 1/32]
n = 10
ys = [mean(rand(Normal(0, sd), n)) for sd in sds]
ymins = ys .- (1.96 * sds / sqrt(n))
ymaxs = ys .+ (1.96 * sds / sqrt(n))
plot(x=1:length(sds), y=ys, ymin=ymins, ymax=ymaxs,
Geom.point, Geom.errorbar)
sds = [1, 1/2, 1/4, 1/8, 1/16, 1/32]
ys = mean.(rand.(Normal.(0, sds), n))
df = DataFrame(x=1:length(sds), y=ys,
mins=ys.-(1.96*sds/sqrt(n)), maxs=ys.+(1.96*sds/sqrt(n)),
g=repeat(["a","b"], inner=3))
p1 = plot(df, x=1:length(sds), y=:y, ymin=:mins, ymax=:maxs, color=:g,
Geom.point, Geom.errorbar)
p2 = plot(df, y=1:length(sds), x=:y, xmin=:mins, xmax=:maxs, color=:g,
Geom.point, Geom.errorbar)
hstack(p1, p2)
```

```@example
using Compose, DataFrames, Gadfly, RDatasets, Statistics
set_default_plot_size(21cm, 8cm)
salaries = dataset("car","Salaries")
salaries.Salary /= 1000.0
salaries.Discipline = ["Discipline $(x)" for x in salaries.Discipline]
df = by(salaries, [:Rank,:Discipline], :Salary=>mean, :Salary=>std)
[df[i] = df.Salary_mean.+j*df.Salary_std for (i,j) in zip([:ymin, :ymax], [-1, 1.0])]
df[:label] = string.(round.(Int, df.Salary_mean))
p1 = plot(df, x=:Discipline, y=:Salary_mean, color=:Rank,
Scale.x_discrete(levels=["Discipline A", "Discipline B"]),
ymin=:ymin, ymax=:ymax, Geom.errorbar, Stat.dodge,
Geom.bar(position=:dodge),
Scale.color_discrete(levels=["Prof", "AssocProf", "AsstProf"]),
Guide.colorkey(title="", pos=[0.76w, -0.38h]),
Theme(bar_spacing=0mm, stroke_color=c->"black")
)
p2 = plot(df, y=:Discipline, x=:Salary_mean, color=:Rank,
Coord.cartesian(yflip=true), Scale.y_discrete,
xmin=:ymin, xmax=:ymax, Geom.errorbar, Stat.dodge(axis=:y),
Geom.bar(position=:dodge, orientation=:horizontal),
Scale.color_discrete(levels=["Prof", "AssocProf", "AsstProf"]),
Guide.yticks(orientation=:vertical), Guide.ylabel(nothing),
Theme(bar_spacing=0mm, stroke_color=c->"gray")
)
hstack(p1,p2)
```


Expand Down
27 changes: 27 additions & 0 deletions docs/src/gallery/statistics.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,33 @@ p2 = plot(vcat(Db...), x=:x, color=:u,
hstack(p1,p2)
```

## [`Stat.dodge`](@ref)

```@example
using DataFrames, Gadfly, RDatasets, Statistics
set_default_plot_size(21cm, 8cm)
salaries = dataset("car","Salaries")
salaries.Salary /= 1000.0
salaries.Discipline = ["Discipline $(x)" for x in salaries.Discipline]
df = by(salaries, [:Rank,:Discipline], :Salary=>mean, :Salary=>std)
[df[i] = df.Salary_mean.+j*df.Salary_std for (i,j) in zip([:ymin, :ymax], [-1, 1.0])]
df[:label] = string.(round.(Int, df.Salary_mean))
p1 = plot(df, x=:Discipline, y=:Salary_mean, color=:Rank,
Scale.x_discrete(levels=["Discipline A", "Discipline B"]),
label=:label, Geom.label(position=:centered), Stat.dodge(position=:stack),
Geom.bar(position=:stack)
)
p2 = plot(df, y=:Discipline, x=:Salary_mean, color=:Rank,
Coord.cartesian(yflip=true), Scale.y_discrete,
label=:label, Geom.label(position=:right), Stat.dodge(axis=:y),
Geom.bar(position=:dodge, orientation=:horizontal),
Scale.color_discrete(levels=["Prof", "AssocProf", "AsstProf"]),
Guide.yticks(orientation=:vertical), Guide.ylabel(nothing)
)
hstack(p1, p2)
```

## [`Stat.qq`](@ref)

```@example
Expand Down
4 changes: 2 additions & 2 deletions src/geom/bar.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Draw bars of height `y` centered at positions `x`, or from `xmin` to `xmax`.
If orientation is `:horizontal` switch x for y. Optionally categorically
groups bars using the `color` aesthetic. If `position` is `:stack` they will
be placed on top of each other; if it is `:dodge` they will be placed side by
side.
side. For labelling dodged or stacked bar plots see [`Stat.dodge`](@ref).
"""
const bar = BarGeometry

Expand Down Expand Up @@ -170,7 +170,7 @@ function render_dodged_bar(geom::BarGeometry,
orientation::Symbol)
# preserve factor orders of pooled data arrays
if isa(aes.color, IndirectArray)
idxs = sortperm(aes.color.index, rev=true)
idxs = sortperm(aes.color.index, rev=false)
else
idxs = 1:length(aes.color)
end
Expand Down
31 changes: 17 additions & 14 deletions src/geom/errorbar.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
struct ErrorBarGeometry <: Gadfly.GeometryElement
default_statistic::Gadfly.StatisticElement
tag::Symbol
end
ErrorBarGeometry(; tag=empty_tag) = ErrorBarGeometry(tag)
ErrorBarGeometry(default_statistic=Gadfly.Stat.identity(); tag=empty_tag) =
ErrorBarGeometry(default_statistic, tag)


struct XErrorBarGeometry <: Gadfly.GeometryElement
Expand Down Expand Up @@ -42,9 +44,14 @@ color them with `color`.
"""
const yerrorbar = YErrorBarGeometry

element_aesthetics(::ErrorBarGeometry) = [:x, :y, :xmin, :xmax, :ymin, :ymax]
element_aesthetics(::XErrorBarGeometry) = [:y, :xmin, :xmax]
element_aesthetics(::YErrorBarGeometry) = [:x, :ymin, :ymax]



element_aesthetics(::ErrorBarGeometry) = [:x, :y, :xmin, :xmax, :ymin, :ymax, :color]
element_aesthetics(::XErrorBarGeometry) = [:y, :xmin, :xmax, :color]
element_aesthetics(::YErrorBarGeometry) = [:x, :ymin, :ymax, :color]
default_statistic(geom::ErrorBarGeometry) = geom.default_statistic


# Generate a form for the errorbar geometry.
#
Expand All @@ -58,12 +65,12 @@ element_aesthetics(::YErrorBarGeometry) = [:x, :ymin, :ymax]
#
function render(geom::ErrorBarGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetics)
# check for X and Y error bar aesthetics
if isempty(Gadfly.undefined_aesthetics(aes, element_aesthetics(xerrorbar())...))
if isempty(Gadfly.undefined_aesthetics(aes, :y, :xmin, :xmax))
xctx = render(xerrorbar(), theme, aes)
else
xctx = nothing
end
if isempty(Gadfly.undefined_aesthetics(aes, element_aesthetics(yerrorbar())...))
if isempty(Gadfly.undefined_aesthetics(aes, :x, :ymin, :ymax))
yctx = render(yerrorbar(), theme, aes)
else
yctx = nothing
Expand All @@ -72,10 +79,8 @@ function render(geom::ErrorBarGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthet
end

function render(geom::YErrorBarGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetics)
Gadfly.assert_aesthetics_defined("Geom.errorbar", aes,
element_aesthetics(geom)...)
Gadfly.assert_aesthetics_equal_length("Geom.errorbar", aes,
element_aesthetics(geom)...)
Gadfly.assert_aesthetics_defined("Geom.errorbar", aes, :x, :ymin, :ymax)
Gadfly.assert_aesthetics_equal_length("Geom.errorbar", aes, :x, :ymin, :ymax)

default_aes = Gadfly.Aesthetics()
default_aes.color = discretize_make_ia(RGB{Float32}[theme.default_color])
Expand Down Expand Up @@ -112,10 +117,8 @@ function render(geom::YErrorBarGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthe
end

function render(geom::XErrorBarGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetics)
Gadfly.assert_aesthetics_defined("Geom.errorbar", aes,
element_aesthetics(geom)...)
Gadfly.assert_aesthetics_equal_length("Geom.errorbar", aes,
element_aesthetics(geom)...)
Gadfly.assert_aesthetics_defined("Geom.errorbar", aes, :y, :xmin, :xmax)
Gadfly.assert_aesthetics_equal_length("Geom.errorbar", aes, :y, :xmin, :xmax)

default_aes = Gadfly.Aesthetics()
default_aes.color = discretize_make_ia(RGB{Float32}[theme.default_color])
Expand Down
57 changes: 57 additions & 0 deletions src/statistics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2019,6 +2019,63 @@ function apply_statistic(stat::EllipseStatistic,
aes.y = ellipse_y
linestyle_scale = get(scales, :linestyle, Scale.linestyle_discrete())
Scale.apply_scale(linestyle_scale, [aes], Gadfly.Data(linestyle = aes.linestyle))
end


struct DodgeStatistic <: Gadfly.StatisticElement
position::Symbol
axis::Symbol
end
DodgeStatistic(; position::Symbol=:dodge, axis::Symbol=:x) = DodgeStatistic(position, axis)

input_aesthetics(stat::DodgeStatistic) = [:x, :y]
output_aesthetics(stat::DodgeStatistic) = [:x, :y]
default_scales(stat::DodgeStatistic) = [Gadfly.Scale.x_continuous(), Gadfly.Scale.y_continuous()]

"""
Stat.dodge[(; position=:dodge, axis=:x)]
Transform the points in $(aes2str(input_aesthetics(dodge()))) into set of dodged or stacked points
in $(aes2str(output_aesthetics(dodge()))). `position` is `:dodge` or `:stack`.
`axis` is `:x` or `:y`.
"""
const dodge = DodgeStatistic


function apply_statistic(stat::DodgeStatistic,
scales::Dict{Symbol, Gadfly.ScaleElement},
coord::Gadfly.CoordinateElement,
aes::Gadfly.Aesthetics)

aes.color==nothing && return
nbars = length(unique(aes.color))
othervar = (stat.axis == :x) ? :y : :x
vals = getfield(aes, stat.axis)

if stat.position==:dodge
nbars == length(aes.color) && return
offset = range(-0.5+0.5/nbars, 0.5, step=1/nbars)
dodge = getindex(offset, aes.color.index)
setfield!(aes, stat.axis, vals.+dodge)
elseif stat.position==:stack
ovals = getfield(aes, othervar)
if nbars == length(aes.color)
setfield!(aes, othervar, 0.5*ovals)
else
idxs = isa(aes.color, IndirectArray) ? sortperm(aes.color.index, rev=true) : 1:length(aes.color)
idxs2 = sortperm(idxs)
m1 = reshape(idxs, :, nbars)
m2 = cumsum(ovals[m1], dims=2) .- 0.5*ovals[m1]
setfield!(aes, othervar, m2[idxs2])
end
else
error("position can be :dodge (default) or :stack")
end
end






end # module Stat
25 changes: 25 additions & 0 deletions test/testscripts/stat_dodge.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Compose, DataFrames, Gadfly, RDatasets, Statistics
set_default_plot_size(21cm, 8cm)

salaries = dataset("car","Salaries")
salaries.Salary /= 1000.0
salaries.Discipline = ["Discipline $(x)" for x in salaries.Discipline]
df = by(salaries, [:Rank,:Discipline], :Salary=>mean, :Salary=>std)
[df[i] = df.Salary_mean.+j*df.Salary_std for (i,j) in zip([:ymin, :ymax], [-1, 1.0])]
df[:label] = string.(round.(Int, df.Salary_mean))

p1 = plot(df, x=:Discipline, y=:Salary_mean, color=:Rank,
Scale.x_discrete(levels=["Discipline A", "Discipline B"]),
ymin=:ymin, ymax=:ymax, Geom.errorbar, Stat.dodge,
Geom.bar(position=:dodge),
Scale.color_discrete(levels=["Prof", "AssocProf", "AsstProf"]),
Guide.colorkey(title="", pos=[0.76w, -0.38h]),
Theme(bar_spacing=0mm, stroke_color=c->"black")
)
p2 = plot(df, x=:Salary_mean, y=:Discipline, color=:Rank,
Scale.y_discrete(levels=["Discipline A", "Discipline B"]),
label=:label, Geom.label(position=:centered), Stat.dodge(position=:stack, axis=:y),
Geom.bar(position=:stack, orientation=:horizontal),
Guide.yticks(orientation=:vertical), Guide.ylabel(nothing)
)
hstack(p1, p2)

0 comments on commit 6240ca0

Please sign in to comment.