GiovineItalia / Gadfly.jl

Crafty statistical graphics for Julia.
http://gadflyjl.org/stable/
Other
1.9k stars 250 forks source link

Feature request: Make Gadfly seemlessly compatible with the Measurements.jl package #1307

Open CNOT opened 5 years ago

CNOT commented 5 years ago

What I really like to see is whenever Gadfly.plot detects an array of Measurement{Float64} or any other subtype of Real numbers, it automatically plots error bars; similar to how Plots.jl does it.

For the very simplest case something along the following lines would work:

using Gadfly, Measurements
import Gadfly.plot

function plot(;y=y::Array{Measurement{N},1}) where N <: Real
    er = Measurements.uncertainty.(y)
    val = Measurements.value.(y)
    return plot(y=val,ymin=val-er,ymax=val+er,Geom.errorbar())
end

a = [i±0.1i for i in 1:30]

plot(y=a)

However, this is by no means general, and twiddling a little bit with it, I couldn't make the splat to function properly, so it would pipe the rest of the arguments to the inside plot function.

tlnagy commented 5 years ago

I must admit that would be pretty slick. I would be happy to review a PR, but I'm unlikely to have time to do this myself.

Mattriks commented 5 years ago

A Gadflyesque way to do this is with a custom statistic (#894). This example will work as is:

module stat

using Measurements, Gadfly

struct MeasurementStatistic <: Gadfly.StatisticElement
end

measurement() = MeasurementStatistic()

# This could all be expanded to handle `x` errors too
Gadfly.Stat.input_aesthetics(stat::MeasurementStatistic) =  [:y]
Gadfly.Stat.output_aesthetics(stat::MeasurementStatistic) = [:y, :ymin, :ymax]
Gadfly.Stat.default_scales(stat::MeasurementStatistic) =
                           [Scale.x_continuous(), Scale.y_continuous()]

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

    dy = Measurements.uncertainty.(aes.y)
    aes.y = Measurements.value.(aes.y)
    aes.ymin = aes.y - dy 
    aes.ymax = aes.y + dy

end

end

using Measurements, Gadfly
y = measurement.(10*rand(10), rand(10))

p = plot(x=1:10, y=y, 
    layer(stat.measurement, Geom.point, Geom.errorbar)
)

Measurements Support for Measurements is also another example where chained statistics (#570) would be useful.

tlnagy commented 4 years ago

It's been awhile since I've looked at Gadfly's codebase. Why does the new statistic need to be a separate layer? Shouldn't it be applied prior to any of the geometries?

I'm not sure I understand why this

p = plot(x=1:10, y=y, stats.measurement, Geom.ribbon)

works, but this

p = plot(x=1:10, y=y, Geom.point, stats.measurement, Geom.ribbon)

doesn't yield a plot?

EDIT: I see your link to the chained statistics PR, is that why?

tlnagy commented 4 years ago

Interestingly, this seems to work:

image

Mattriks commented 4 years ago

There 's an explanation in the docs about layer and statistics here. See text under the "Iris data" plot. Within a layer, all geoms should use the given layer statistic, so try:

layer(stat.measurement, Geom.point, Geom.ribbon)
tlnagy commented 4 years ago

I should've played around with the code more. It turns out you can't use unitful aesthetics with Geom.ribbon and that's why I was having issues when applying it to my own data.