haskell-chart icon indicating copy to clipboard operation
haskell-chart copied to clipboard

Bar graph w/ error bars

Open mitchellwrosen opened this issue 10 years ago • 8 comments

I'm struggling to put together a graph like this one: http://mathbench.umd.edu/modules/prob-stat_bargraph/graphics-final/graph-male-female.jpg

Is this possible with the current API? I don't think there's any way to "hook" into a PlotBars render to capture the correct x-axis values to feed into a PlotErrBars.

mitchellwrosen avatar Jan 22 '15 19:01 mitchellwrosen

That should be possible. What is the type of your x axis? Can you post your example code? On 23 Jan 2015 06:43, "Mitchell Rosen" [email protected] wrote:

I'm struggling to put together a graph like this one: http://mathbench.umd.edu/modules/prob-stat_bargraph/graphics-final/graph-male-female.jpg

Is this possible with the current API? I don't think there's any way to "hook" into a PlotBars render to capture the correct x-axis values to feed into a PlotErrBars.

— Reply to this email directly or view it on GitHub https://github.com/timbod7/haskell-chart/issues/70.

timbod7 avatar Jan 22 '15 19:01 timbod7

@timbod7 I believe it was PlotIndex. Unfortunately I just deleted all of my sample code, but it was some combination of the bar-graph example code and error-bars example code (first time using Chart). Tonight I'll re-create it for you. In the meantime, what should the x axis type be?

EDIT: For the record, the above image was a bad example. The graph shape I'm actually going for is this: http://www.ats.ucla.edu/Stat/stata/faq/barcap1.gif. I was indeed able to superimpose error bars on top of a one-bar-per-x-axis-group graph, but beyond that I could only get the error bars to stack on top of each other in the center of the bars. Hope that makes sense.

mitchellwrosen avatar Jan 23 '15 00:01 mitchellwrosen

Here you go @timbod7:

module Main where

import Control.Applicative
import Graphics.Rendering.Chart.Easy
import Graphics.Rendering.Chart.Backend.Cairo

titles :: [String]
titles = ["foo", "bar"]

values :: [(String, [Double], [Double])]
values =
    [ ("group 1", [1, 2], [0.5, 0.5])
    , ("group 2", [3, 4], [1, 2])
    ]

main :: IO ()
main = toFile def "out.png" ec
  where
    ec :: EC (Layout PlotIndex Double) ()
    ec = do
        layout_x_axis.laxis_generate .= autoIndexAxis (values ^.. traverse._1)
        plot barplot
        plot errplot

barplot :: EC (Layout PlotIndex Double) (Plot PlotIndex Double)
barplot = plotBars <$> bars titles (addIndexes (values ^.. traverse._2))

errplot :: EC (Layout PlotIndex Double) (PlotErrBars PlotIndex Double)
errplot = liftEC $
    plot_errbars_values .= [symErrPoint x y 0 dy | (_, ys, dys) <- values
                                                 , (x, (y, dy)) <- addIndexes (zip ys dys)]

out

The problem is I'm not sure how to capture the correct dx by which the bar graphs are shifted, when creating the error bars.

mitchellwrosen avatar Jan 23 '15 18:01 mitchellwrosen

In the chart library, bar plots can show:

  • a single value per x cordinate
  • multiple "clustered" values per x coordinate
  • multiple "stacked" values per x coordinate

In clustered bar plots, extra horizontal space is added and each value is x offset such that they are shown side by side. In stacked bar plots, extra vertical space is added and each value is y offset.

As you have discovered, the logic to offset the bars is contained within the bar rendering code, and hence is not accessible to other plots (eg error markers). I think that the only solution to this would be to extend the bar plot function so that it understands and can render error ranges. I can provide suggestions if this is something you are interested in pursuing.

However, your original example has only a single bar per x-coordinate. Because there are no offsets in this case, you can overlay an errbars plot over a bar plot to get what you need. It's a bit fiddly, for a couple of reasons. An example:

import Graphics.Rendering.Chart.Easy
import Graphics.Rendering.Chart.Backend.Cairo

alabels = [ "Jun", "Jul", "Aug", "Sep", "Oct" ]

values :: [ (PlotIndex,(Double,Double)) ] 
values = addIndexes [ (20,6), (45,5), (30,6), (10,2), (20,7) ]

fillStyle = solidFillStyle (opaque lightblue)
lineStyle = solidLine 1 (opaque black)

main = toFile def "errbars.png" $ do
  layout_title .= "Example Bars"
  layout_x_axis . laxis_generate .= autoIndexAxis alabels
  layout_y_axis . laxis_override .= axisGridHide
  layout_left_axis_visibility . axis_show_ticks .= False
  plot $ fmap plotBars $ liftEC $ do
    plot_bars_values .= [ (x,[y]) | (x,(y,yerr)) <- values ]
    plot_bars_item_styles .= [ (fillStyle, Just lineStyle) ]
  plot $ liftEC $ do
    plot_errbars_values .=  [ symErrPoint x y 0 yerr | (x,(y,yerr)) <- values ]
    plot_errbars_line_style .= lineStyle

errbars

timbod7 avatar Jan 25 '15 21:01 timbod7

Ah - I didn't notice your edit in the second comment. To get this sort of chart to work would required error ranges to be added as a specific feature to bar charts.

timbod7 avatar Jan 26 '15 07:01 timbod7

Sounds good!

I think that the only solution to this would be to extend the bar plot function so that it understands and can render error ranges. I can provide suggestions if this is something you are interested in pursuing.

I'm interested in pursuing this, time permitting. If you wouldn't mind posting a quick brain-dump here on this ticket, perhaps I or someone else can pick it up at some point.

mitchellwrosen avatar Jan 27 '15 00:01 mitchellwrosen

A possible revised API:

data PlotBars x y = PlotBars {
   _plot_bars_style           :: PlotBarsStyle,
   _plot_bars_item_styles     :: [ (FillStyle,Maybe LineStyle) ],
   _plot_bars_titles          :: [String],
   _plot_bars_spacing         :: PlotBarsSpacing,
   _plot_bars_alignment       :: PlotBarsAlignment,
   _plot_bars_reference       :: y,
   _plot_bars_singleton_width :: Double,
   _plot_bars_values          :: [ (x,[y]) ],
   _plot_bars_errbars         :: [ (x,[Maybe (y,y)]) ]   -- Added
}

This would allow you to have error bars on some values and not others.

I think it only makes sense to render the error bars should only be shown in BarsClustered mode. Hence, you'd need to update the clusteredBars function (inside renderPlotBars) to actually render the error bars. You'd also need to update allBarPoints to ensure that the correct range is calculated to include the errors.

timbod7 avatar Jan 27 '15 10:01 timbod7

I'd like to resurface this issue, now that @clayrat has refactored bar charts. I realize that it would be somewhat involved, but clustered bar charts with error bars are everywhere, and it would be extremely useful to be able to make them.

PeteRegan avatar Jan 31 '24 17:01 PeteRegan