# BoxWhiskerPlot - how to specify boxes position on the horizontal axis

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.

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]


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}]


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:

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}]]


• 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! – JohnnyM Jan 29 '13 at 20:11
• btw, any idea how to make x-axis logarithmic? – JohnnyM Jan 29 '13 at 20:14
• 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. – Jens Jan 30 '13 at 6:41
• Nice! How about making both axes logarithmic? – JohnnyM Jan 30 '13 at 7:54

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]


• Nice trick (+1) (didn't know about Missing) – Jens Jan 29 '13 at 20:51
• thank you @Jens. Actually, any non-real data works the same way as Missing[]. – kglr Jan 29 '13 at 20:53
• Nice, but much less universal. I assume it works only for natural values of ks which, in my case, wouldn't work. (ofcourse the question was simplified). – JohnnyM Jan 29 '13 at 21:21
• @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. – Verbeia Jan 29 '13 at 21:42
• @JohnnyM - in that case, Rationalize is your friend. And maybe Round. – Verbeia Jan 29 '13 at 22:42

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]


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.

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]


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}}]