10
$\begingroup$

Let's say I have a folowing set of data:

  • k = 1 : list of values
  • k = 3 : list of values
  • k = 10 : list of values

I know that to make a BoxWhiskerChart I have to give it a list of listen of these values as data and ks as labels.

How do I force the offset between the boxes for different ks to be proportional to the values of ks?

This is like combining ListPlot and BoxWhiskerChart - list plot gives appropriate position of boxes relative to the x-axis.

$\endgroup$

4 Answers 4

11
$\begingroup$

Since the BoxWhiskerChart doesn't label its x-axis coordinate, I decided to make use of the functionality that already exists in ErrorListPlot. The latter is part of the "ErrorBarPlots" package which has to be loaded explicitly.

First I generate the data, then I extract a list of coordinates for ErrorListPlot, then I specify a function that renders the "error bar" as a BoxWhiskerChart for each data set individually. The scale has to be common to all plots, and that's achieved with a combination of fixed PlotRange and AspectRatio -> Full for the inset whisker plots.

data = {{1, RandomInteger[{-10, 10}, 10]}, {3, 
    RandomInteger[{-10, 10}, 10]}, {10, RandomInteger[{-10, 10}, 10]}};

data = {{1, {7, -9, 5, 1, 3, 8, 10, 10, -3, 
     0}}, {3, {-8, -6, -6, 7, -8, -8, 2, -8, -2, -4}}, {10, {-9, -3, 
     9, 5, -1, 0, 9, 2, 3, -1}}};

dataValues = data[[All, 2]];

The original chart is here:

BoxWhiskerChart[dataValues]

chart

Now for the modifications to ErrorListPlot:

{min, max} = {Min[#], Max[#]} &@Flatten[dataValues]

(* ==> {-9, 10} *)

kValues = MapIndexed[Flatten[{#, min, #2}] &, data[[All, 1]]];

whiskers = Map[
   BoxWhiskerChart[#, Frame -> None, PlotRange -> {min, max}, 
     AspectRatio -> Full, PlotRangePadding -> 0, 
     PlotRangeClipping -> False] &,
   dataValues];

Needs["ErrorBarPlots`"]

errorfunction[{x_, y_}, err_] := Inset[
  whiskers[[Last[Flatten[List @@ err]]]],
  {x, y}, {Center, Bottom}, Scaled[{.1, 1}]]

ErrorListPlot[kValues, ErrorBarFunction -> errorfunction, 
 PlotMarkers -> "", PlotRange -> {min, max}]

error plot

The tooltips of the original chart still work in this plot, too.

Edit

In response to the additional question about logarithmic x axis, we can work with the same data but can't use ErrorListPlot. So instead one could do this:

ListLogLinearPlot[
   List /@ kValues[[All, 1 ;; 2]], 
   PlotMarkers -> Map[{#, 1} &, whiskers], 
   PlotRange -> {min, max}] /. 
  Inset[i_, pos_, align_, scl_] :> 
   Inset[i, pos, {Center, Bottom}, Scaled[{.1, 1}]]

The idea is the same as above: "inject" the BoxWhiskerChart at the correct location by means of an Inset. The positioning can be taken care of by the plot function, but the scaling is not under my control in the plotting process. So I have to fix that by post-processing using the replacement /. with a rule that looks for Inset in the finished plot and aligns its bottom with the plot point coordinate. The latter is always equal to the minimum data value (min), so that by setting PlotRange and using Scaled[{.1, 1}] for the fourth Inset argument we get the same result as in ErrorListPlot, only with a logarithmic axis:

log axis

This last approach would of course also work with ListPlot and could therefore be a replacement for my my first solution.

A limitation of both approaches is that the vertical PlotRange must correspond exactly to the one in the BoxWhiskerChart, so that the scaling works properly by assuming the Inset should span 100% of the vertical space. This means you can't add PlotRangePadding in the vertical direction. You can add it in the horizontal direction, though. Here is an illustration of an explicit horizontal PlotRange to keep the bars away from the edges:

ListLogLinearPlot[
  List /@ kValues[[All, 1 ;; 2]], 
  PlotMarkers -> Map[{#, 1} &, whiskers],
  AxesOrigin -> {.9, 0},
  PlotRange -> {{.9, 11}, {min, max}}] /. 
 Inset[i_, pos_, align_, scl_] :> 
  Inset[i, pos, {Center, Bottom}, Scaled[{.1, 1}]]

plotrange example

$\endgroup$
4
  • $\begingroup$ Awesome! Needed some tampering with origins and min and max (might be good to set to Min[{0,#}] but worked like a charm! You just saved me hours of work, thank you! $\endgroup$
    – JohnnyM
    Commented Jan 29, 2013 at 20:11
  • $\begingroup$ btw, any idea how to make x-axis logarithmic? $\endgroup$
    – JohnnyM
    Commented Jan 29, 2013 at 20:14
  • $\begingroup$ The error plot can only have a linear axis, unfortunately. I guess you could simply replace k by Log[10, k] in the labels for your data. But instead, I'll add a different approach that uses ListLogLinearPlot. $\endgroup$
    – Jens
    Commented Jan 30, 2013 at 6:41
  • $\begingroup$ Nice! How about making both axes logarithmic? $\endgroup$
    – JohnnyM
    Commented Jan 30, 2013 at 7:54
9
$\begingroup$

From docs > BoxWhiskerChart >Scope > Data and Wrappers:

Nonreal data is taken to be missing and typically yields a gap in the box-and-whisker chart

So one can use the following approach:

salaries = ExampleData[{"Statistics", "UniversitySalaries"}, "DataElements"];
depts = {"Mathematics", "History", "English", "Chemistry", "Law", "Physics", "Statistics"};
data = Table[Cases[salaries, {d, _, salary_, "A"} :> salary], {d, depts}];
xrange = {1, 2, 5, 7, 10, 11, 12};
newdata = ConstantArray[Missing[], Max[xrange]];
newdata[[xrange]] = data;
xlabels = ConstantArray["", Max[xrange]];
xlabels[[xrange]] = xrange;

BoxWhiskerChart[newdata, ChartLabels -> Placed[xlabels, Axis],
  ChartStyle -> 10, Joined -> True, ImageSize -> 500]

enter image description here

$\endgroup$
9
  • $\begingroup$ Nice trick (+1) (didn't know about Missing) $\endgroup$
    – Jens
    Commented Jan 29, 2013 at 20:51
  • $\begingroup$ thank you @Jens. Actually, any non-real data works the same way as Missing[]. $\endgroup$
    – kglr
    Commented Jan 29, 2013 at 20:53
  • 1
    $\begingroup$ @JohnnyM - you could always rescale your k's by their least common multiple so that the x-axis is integers, and then just use the FrameTicks option to label them as you want. $\endgroup$
    – Verbeia
    Commented Jan 29, 2013 at 21:42
  • 1
    $\begingroup$ @JohnnyM - in that case, Rationalize is your friend. And maybe Round. $\endgroup$
    – Verbeia
    Commented Jan 29, 2013 at 22:42
  • 1
    $\begingroup$ @CuriousBadger, maybe you can use something like grid = Prepend[ MapIndexed[Flatten[{#2, Quantile[#, {0, .25, .5, .75, 1}]}] &]@ data, {"data set", "min", "25%", "median", "75%", "max"}]; Grid[grid, Alignment -> {{Center, {"."}}}, Dividers -> GrayLevel[0.3]]? $\endgroup$
    – kglr
    Commented Sep 15, 2020 at 15:42
3
$\begingroup$

At the request of one user I included a BoxWhisherDraw statement in the latest release of Presentations (which I sell for $50.). This essentially draws a chart centered at zero and then you can easily Scale/Rotate/Translate it to wherever you wish. So here is a solution to your problem using BoxWiskerDraw:

<<Presentations`

data[1] = Array[RandomReal[{0, 1}] &, 10];
data[3] = Array[1 + RandomReal[{0, 3}] &, 10];
data[10] = Array[5 + RandomReal[{0, 10}] &, 10];


xticks = CustomTicks[Identity, databased[{1, 3, 10}]];
Draw2D[
 {({BoxWhiskerDraw[data[#],
         ChartStyle -> 
          Directive[FaceForm[Opacity[0.2, Blue]], EdgeForm[Black], 
           Thin]],
        CirclePoint[{0, #}, 3, Black, White] & /@ data[#]} // 
       ScaleOp[{3, 1}, {0, 0}] // TranslateOp[{#, 0}]) & /@ {1, 3, 
    10}},
 Frame -> True,
 FrameTicks -> {{Automatic, Automatic}, {xticks, 
    xticks // NoTickLabels}},
 PlotLabel -> Style["Custom BoxWhisker Chart", 13, Bold],
 ImageSize -> 300]

Mathematica graphics

I also used CirclePoint to mark the data points for each data set and I used CustomTicks to put ticks at just the k values.

Addition

The poster asked if a log x axis could be used. Yes, very simple. In the above we simply add a Log10 as the scaling function in CustomTicks and to the TranslateOp command. And we change the width scaling in ScaleOp.

xticks = CustomTicks[Log10, databased[{1, 3, 10}]];
Draw2D[
 {({BoxWhiskerDraw[data[#],
         ChartStyle -> 
          Directive[FaceForm[Opacity[0.2, Blue]], EdgeForm[Black], 
           Thin]],
        CirclePoint[{0, #}, 3, Black, White] & /@ data[#]} // 
       ScaleOp[{1/2, 1}, {0, 0}] // 
      TranslateOp[{Log10[#], 0}]) & /@ ({1, 3, 10})},
 AspectRatio -> 1,
 PlotRange -> {{-0.3, 1.3}, {Automatic, Automatic}},
 Frame -> True,
 FrameTicks -> {{Automatic, Automatic}, {xticks, 
    xticks // NoTickLabels}},
 PlotLabel -> Style["Custom BoxWhisker Chart", 13, Bold],
 ImageSize -> 300]

Mathematica graphics

$\endgroup$
0
3
$\begingroup$

Using a custom ChartElementFunction that translates the graphics primitives based on specified x values passed as metadata:

ClearAll[ceF]
ceF[func_: "GlassBoxWhisker"][delta_] := 
 ChartElementData[func][{#3[[1]] + {-delta, delta}, #[[2]]}, ##2] &

salaries = ExampleData[{"Statistics", "UniversitySalaries"}, "DataElements"];
depts = {"Mathematics", "History", "English", "Chemistry", "Law", 
   "Physics", "Statistics"};
data = Table[Cases[salaries, {d, _, salary_, "A"} :> salary], {d, depts}];

SeedRandom[3]
xvalues = Sort@RandomReal[100, Length@data];
delta = 2 Min[Differences[xvalues]]/5;

Show[BoxWhiskerChart[Thread[data -> xvalues], ChartStyle -> 10, 
  ImageSize -> 500, ChartElementFunction -> (ceF[][delta]), ChartLegends -> depts], 
 ListLinePlot[Transpose[{xvalues, Median /@ data}], PlotStyle -> Thick], 
 FrameTicks -> {{Automatic, Automatic}, 
   {{#, Rotate[Style[#, 14, Bold, "Panel"], 90 Degree]} & /@ 
     xvalues, {#, ""} & /@ xvalues}}, 
 ImagePadding -> {{40, 10}, {70, 10}}]

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.