boxchart - many questions! Assign BoxFaceColors? separation lines? numeric xGroupData? Location of boxes? number of points?

HI,
with r2020 there is finally a grouped boxplot called "boxchart". However. I don't seem to get the structure and options of it. Multiple questions.
How does one apply any property simultaneously to all boxes instead of looping? For instance like b.Notch = 'on' or a colormap to the ColorGroup? The standard colors especially their order is not what I'm looking for. however, assigning a colormap doesn't work, nor can I apply a b=boxchart()... b.BoxFaceColor = parula(size(b,1)) with n-colors for n-Groups. What am I not seeing here? All I can do is loop over all.
cols = parula(size(b,1));
for i = 1:size(b,1)
bb(i).BoxFaceColor = cols(i,:);
end
% this seems extremely cumbersome
here I have looped over each bb(i) as shown above in order to apply a colr map of my choice. .
Another question that pops up: How could I add separation lines? In the example image above I have added them with an image editor.. obviously not the choice!
Next: Why can't I seem to get a correct looking diagram when plotting when input xGroupData are unique numbers of discrete groups.. The diagram's x-axis or x-grouping is then entirely messed up.
xgroupdata = repmat([0, 0.0125, 0.0250, 0.0375], [1,100]);
ydata = rand(size(xgroupdata));
group = datasample(1:1:5,size(xgroupdata,2));
figure;
boxchart(xgroupdata,ydata, 'GroupByColor', group);
legend;
here is what that code produces. Only Group3 seems to be correctly placed at the proper xgroup-values. But the automatic box-width, doesn't work and the group spacing is also totally messed up, seems to be 0.2 but I can't even find a property in boxchart's documentation
I would like to be able to add a textlabel to any of the boxes with the sample size. For this I need to a) figure out the position of the boxes but I don't manage to understand the structure of the handle... there are zero Children for instance...

 Accepted Answer

boxchart vs boxplot
>with r2020 there is finally a grouped boxplot called "boxchart".
The boxplot() function also has grouping options, has been around for much longer, and is often easier to customize than the newer standalone visualzation boxchart function.
ColorGroup & Notches
>How does one apply any property simultaneously to all boxes instead of looping? For instance like b.Notch = 'on' or a colormap to the ColorGroup?
See examples in the boxplot documentation for notches, and a description of ColorGroup.
If you want to use boxchart see the documentation regarding notches.
To apply changes to a group of handles to existing graphics objects, follow this example, although the loop you mentioned is also fine and just as fast (faster?),
h = boxchart(___); % h is a vector of handles
set(h, {'BoxFaceColor'}, mat2cell(jet(numel(h)),ones(numel(h),1),3)) % jet is a colormap
Matlab documentation has a demo showing how to change colors in single bars or groups.
Separation lines
>How could I add separation lines?
For boxplot, use the FactorSeparator option.
For boxchart, this option currently doesn't exist (r2020b). If the x-axis is numeric, add divider lines using,
h = boxchart(__);
xline(x) % where x is the location of the line
If the x-axis is categorical it's a lot more complicated because divider lines cannot be added between categories. To add divider lines on a categorical x-axis you have to add additional categories to the x axis ticks, set their order, and then use xline() (or plot() or line()) with categorical x-values.
% Create boxchart showing 12 months of data
monthNum = randi(12,200,1);
data = rand(200,1)*100.*(normpdf(monthNum,6,1)+.25);
monthAbrv = categorical(monthNum,1:12,month(datetime(1, 1:12, 1), 's'));
figure()
h = boxchart(monthAbrv, data);
% Separate seasons by divider lines
% First add categories to the x-axis
ax = h.Parent; % axis handle
ax.XAxis.Categories' % view current categories.
ans = 1x12 cell array
{'Jan'} {'Feb'} {'Mar'} {'Apr'} {'May'} {'Jun'} {'Jul'} {'Aug'} {'Sep'} {'Oct'} {'Nov'} {'Dec'}
ax.XAxis.Categories = {
'Jan' 'Feb' 'Spring',...
'Mar' 'Apr' 'May' 'Summer',...
'Jun' 'Jul' 'Aug' 'Fall',...
'Sep' 'Oct' 'Nov' 'Winter',...
'Dec'};
% Add xlines on seasonal categories
arrayfun(@(x)xline(x,'r-','LineWidth',1.5),categorical({'Spring','Summer','Fall','Winter'}))
% xline(__) for single lines
% if you want to label each line,
% arrayfun(@(x)xline(x,'r-',x,'LineWidth',1.5),categorical({'Spring','Summer','Fall','Winter'}))
% If needed, removed seasonal tick labels
ax.XTickLabel([3,7,11,15]) = {''};
Fix overlapping boxes in boxchart
>Why can't I seem to get a correct looking diagram when plotting [with grouped data]?
Follow the examples in the documentation for boxchart and look for what's different in your setup.
You can fix the overlapping boxes by making the grouping variable categorical
boxchart(categorical(xgroupdata),ydata, 'GroupByColor', group);
Adding text labels
> I would like to be able to add a textlabel to any of the boxes
Use the text() function. See the bottom of this answer to learn how to get the x value of the boxplot centers.
For numeric axes it's as easy as using the text() function but for categorical axes, you must use categorical coordinates. This demo below shows the number of samples in each category and plots that number above the median lines.
% Produce boxchart
figure()
h = boxchart(randi(5,100,1),rand(100,1));
% compute median and number of samples for each box
[groupID, groups] = findgroups(h.XData);
medians = splitapply(@median,h.YData,groupID);
nSamples = splitapply(@numel,h.YData,groupID);
% Plot text
text(groups, medians, compose('n=%d',nSamples),...
'HorizontalAlignment','Center','VerticalAlignment','bottom', ...
'FontSize',8)
Extracting location of boxs
>I need to a) figure out the position of the boxes but I don't manage to understand the structure of the handle
Extracting the location of the boxes from the graphics handle involves accessing each graphics element and examining its coordinates. It's not easy but achievable with boxplot (see this answer). I haven't figured out a way to achieve this with boxchart as of r2020b.
Of course many of the parameters can be computed using the raw data.
The median marker are easy to get using,
h = boxchart(__);
[groupID, groups] = findgroups(h.XData);
medians = splitapply(@median,h.YData,groupID);
The upper and lower edges of the boxes are the 25th and 75th quartiles,
h = boxchart(__);
[groupID, groups] = findgroups(h.XData);
bounds = splitapply(@(y)quantile(y,[.25,.75]),h.YData,groupID);
The whisker ends are a bit more difficult. They mark the max/min values that are not outliers so first you have to compute the outlier thresholds. Outliers are any values that are greater than 1.5*IQR from the medians. IQR is computed for each box below. See documentation for more info.
h = boxchart(__);
[groupID, groups] = findgroups(h.XData);
IQR = splitapply(@iqr,h.YData,groupID);
The box centers and Left/Right edges for numeric axes are calculated using
h = boxchart(__);
boxCenters = unique(h.XData);
boxEdges = boxCenters(:) + h.BoxWidth.*[-1,1]; % for numeric matrices only

4 Comments

thanks for your lengthy response Adam. I am familiar with the boxplot() function wich has been around for much longer. There is a reason I was looking at boxchart. Boxplot() to my knowledge, is missing some core features but maybe you can help me with these...
  • no legend support as far as I have discovered. That's not usable for what I've planned/showed then since I'd really want a legend for the group color, that's the biggest drawback that always made me use something else in the past!
  • Also the labels are not neat enough to publish, but I suppose that's something one can work on? How to create a tick and center them for instance, since setting 'LabelVerbosity' 'minor' only leaves out the consecutive ones that don't change.. but don't center the one being displayed.
  • since h = boxplot() doesn't give a handle back in order to set all kinds of properties after the fact.
  • how to turn the line colors into a FaceColor of the boxes for instance?
... just to name a few. If I could add a legend, fill the box-faces and clean up those xTickLabels... I wouldn't even have asked about boxchart. It remains a shame that Matlab's BoxPlot plotting capabilities are so cluttered. I'm not a Matlab Pro but I usually am up to fiddling a little bit to get the final result yet it's not really worth it to have to fiddle each time one wants a presentable boxplot diagram.
I'd appreciate it if you or others could help me out finding a robust solution for this that prevents me from going python for this too as I don't want to further clutter my workflow but consolidate it - understandable, I think.
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% boxchart
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
bb = boxchart(data.x, data.y, ...
'GroupByColor', data.group);
lg = legend;
lg.Title.String = 'legendTitle';
lg.Title.String = 'legendTitle';
title('boxchart')
set(gca,'yminorgrid','on');
box on;
% coloring boxes
cols = viridis(size(data.group,1));
for i = 1:size(bb,1)
bb(i).BoxFaceColor = cols(i,:);
bb(i).BoxFaceAlpha = 0.4;
end
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% boxplot
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
cols = viridis(size(data.group,1));
figure;
b = boxplot(data.y, {data.x, data.group}, ...
'ColorGroup', data.group, ...
'LabelVerbosity', 'majorminor', ...
'FactorSeparator', [1], ...
'FactorGap', [2,1], ...
'Colors', cols, ...
'LabelOrientation', 'horizontal');
grid minor;
title('boxplot', 'LabelVerbosity, minor - FactorSeparator, [1]')
There are tradeoffs between boxplot and boxchart. Some of the stuff you want like a legend and filled boxplots is simpler with boxchart while others like group divider lines is easier with boxplot. I say 'easier' because there are workarounds for all of these problems though some of them will require knowledge of undocumented features.
For example, if you're working with a boxplot and want to fill the boxes with a color, you just need to get the box coordinates and apply them to the patch() function which will show up in the legend. Alternatively, you can use the ColorGroup property of boxplot, get the line object handles to the boxes, and specify them in the legend function,
boxplot(___, 'ColorGroup', ___);
boxHandles = findobj(gca, 'Tag', 'Box');
legend(boxHandles,___)
The legend in boxchart is much more user friendly, though.
As for the xticklabels in boxplot, you can customize them or completely replace them with whatever you want. For example, it's not difficult to replace the tick labels using the line below and recall that tick labels can have multiple lines.
set(gca, 'xtick', _____, 'XTickLabel',_____)
But again, it appears that the boxchart function has better tick labels for your needs.
It looks like boxchart might be easiest for you. I'll update my answer with some suggestions to address your original questions.
Happy new year y'all!
I have solved the overlapping boxes for continuous x-groups by applying a categorical() to numeric x-groups. So that's easily done.
What I still would like to pull off in order to be fully satisfied with using boxchart is
  • add separation lines
  • adding the number of datapoints to each box - extremely useful in order to gauge how meaningful the data is
for both I need some inspiration since currently I don't have even an idea on how to do it.
Adding divider lines and text is tricky but I'll update the answer to show how.

Sign in to comment.

More Answers (0)

Categories

Products

Asked:

on 20 Dec 2020

Edited:

on 14 Jan 2021

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!