BoxWhiskerPlot - how to specify boxes position on the horizontal 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}]]
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]
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.
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]