1. 从零开始:你的第一张MATLAB箱线图

说实话,我第一次用MATLAB画箱线图的时候,也犯过迷糊。看着一堆数据,想看看它们的分布情况,听说箱线图(Box Plot)特别直观,能一眼看出中位数、四分位数,甚至异常值。但打开MATLAB,面对那个叫 boxplot 的函数,参数一大堆,瞬间有点懵。后来在科研项目里用多了才发现,这东西真是数据分析的“利器”,尤其是当你需要对比多组数据的分布差异时,它比单纯看平均值靠谱多了。

箱线图,也有人叫它盒须图,它的核心就是用一个“盒子”和两根“须”来概括一组数据。盒子的上下边界对应数据的上四分位数(Q3)和下四分位数(Q1),盒子中间那条线就是中位数。从盒子伸出去的“须”通常延伸到非异常值的最小值和最大值,而那些单独画出来的小圆圈或者星号,就是被判定为异常值的数据点。这么一画,数据的集中趋势、离散程度以及偏态,全都一目了然。

在MATLAB里画箱线图,最核心的函数就是 boxplot。别看它名字简单,里面的门道可不少。咱们先别管那些高级技巧,就从最基础的、能把图先画出来开始。我经常跟实验室的师弟师妹说,学工具就像学开车,先能开上路,再研究怎么漂移。所以,这一部分,咱们就专注“上路”。

首先,你得有数据。数据从哪来?可能是你实验测的一堆数值,也可能是从Excel、CSV文件里导出来的。在MATLAB里准备数据,最常见的就是用数组或者矩阵。比如说,我手头有三组学生的考试成绩,想看看每组成绩的分布。我可以这样创建数据:

% 假设有三组学生的数学成绩
group1 = [78, 85, 92, 88, 76, 95, 81, 79, 90, 87]; % 第一组
group2 = [65, 72, 80, 68, 90, 62, 75, 78, 71, 85]; % 第二组
group3 = [95, 98, 89, 92, 96, 88, 93, 91, 97, 94]; % 第三组

% 将三组数据合并成一个矩阵,每一列代表一组数据
score_data = [group1', group2', group3'];

这里有个小细节,boxplot 函数默认将矩阵的每一列视为一个独立的数据组来绘制一个箱线图。所以我把三个行向量转置成了列向量(用 ' 运算符),然后并排放在一起,形成了一个10行3列的矩阵 score_data。这样,boxplot 就会为我们画出三个并排的箱子。

数据准备好了,画图就一句话的事:

figure; % 新建一个图形窗口
boxplot(score_data); % 绘制箱线图
title('三组学生数学成绩分布对比'); % 给图加个标题
xlabel('学生分组'); % 给X轴加标签
ylabel('考试成绩'); % 给Y轴加标签

运行这几行代码,一张最基础的、带有三个箱子的箱线图就跃然屏上了。你会看到三个并排的盒子,每个盒子中间有根线(中位数),盒子上下有“须”。如果某个学生的成绩离群太远,可能会以“+”号的形式单独标记在图形外面,那就是异常值。这张图已经能告诉我们很多信息了:比如第三组(最右边)的成绩整体很高,盒子位置靠上,且盒子“瘦”,说明大家分数集中,差距小;第二组的盒子“胖”一些,可能分数分布更分散。

但这时候你可能会发现,X轴下面默认标的是1、2、3,而不是我们想要的“第一组”、“第二组”这样的名字。图看起来也有点“素”,没有网格线,不方便精确读数。别急,这些正是我们接下来要解决的“装修”问题。先把图画出来,建立信心,咱们再一步步把它变得既专业又美观。

2. 数据导入与整理:让MATLAB读懂你的数据

画图的第一步,永远是把数据“喂”给MATLAB。在实际的科研或工程中,我们的数据很少是直接在脚本里敲出来的,更多是来自外部文件。如果数据导入这一步没做好,后面画图全是白搭。我见过不少新手,代码写得没问题,但因为数据格式不对,图死活画不出来,或者画出来是错的。所以,咱们花点时间,把数据导入和前期整理这个地基打牢。

最常遇到的数据格式无非几种:文本文件(.txt, .csv)、Excel文件(.xlsx, .xls) 和 MATLAB自带的数据文件(.mat)。每种格式的“吃法”略有不同。

从CSV/TXT文件导入: 这类文件通用性最强。MATLAB提供了 readmatrix 和 readtable 两个好用的函数。readmatrix 会把数据读成一个数值矩阵,非常干净利落,适合纯数字的数据。比如你有一个 scores.csv 文件,里面三列数字就是三组成绩,没有表头,那么:

score_data_from_csv = readmatrix('scores.csv'); % 直接得到数值矩阵
boxplot(score_data_from_csv);

如果你的CSV文件有表头(比如第一行写着“Group1”,“Group2”,“Group3”),用 readmatrix 可能会把表头也当数字读,导致错误。这时候可以用 readtable:

data_table = readtable('scores_with_header.csv'); % 读成表格(table)格式
% 假设表格有三列,列名是‘Group1’,‘Group2’,‘Group3’
score_data_from_table = [data_table.Group1, data_table.Group2, data_table.Group3];
boxplot(score_data_from_table);

readtable 会智能地处理表头,并把数据存为一种叫 table 的类型,你可以通过 点号(.) 加列名来访问每一列数据,非常直观。

从Excel文件导入: 如果你习惯用Excel整理数据,那 readmatrix 和 readtable 同样适用,只需把文件名后缀改成 .xlsx 就行。MATLAB对Excel的支持很好,甚至可以指定读取哪个工作表(Sheet)。比如:

% 读取‘Sheet1’工作表中,从A1到C10范围的数据
score_data_from_excel = readmatrix('data.xlsx', 'Sheet', 'Sheet1', 'Range', 'A1:C10');

处理缺失值(NaN): 真实数据常有缺失,在文件里可能表现为空单元格、‘NA’、‘-999’等。readmatrix 默认会把无法解析为数字的单元格变成 NaN(Not a Number)。boxplot 函数是能处理 NaN 的,它会自动忽略这些缺失值进行计算和绘图。但你需要心里有数,知道数据里有缺失。你可以用 isnan 函数检查一下:

missing_count = sum(isnan(score_data(:))); % 计算整个矩阵中NaN的个数
disp(['数据中包含 ', num2str(missing_count), ' 个缺失值']);

数据导入后,整理是关键一步。boxplot 期望的输入数据格式主要有两种:

  1. 矩阵格式:就像我们之前做的,每一列是一个组。这是最常用、最直接的方式。
  2. 向量+分组变量格式:当你数据本身是一个很长的向量,同时有另一个同样长度的向量指明每个数据点属于哪一组时,可以用这种格式。这在数据存储形式比较“原始”时很方便。

举个例子,假设所有学生的成绩都混在一个长列表里,同时有一个列表记录每个成绩属于哪一组:

all_scores = [78, 85, 92, 65, 72, 95, 98, ...]; % 所有成绩混在一起
group_labels = [1, 1, 1, 2, 2, 3, 3, ...]; % 对应每个成绩的组别标签(1,2,3)

figure;
boxplot(all_scores, group_labels); % 使用第二个参数指定分组

这种方式画出的箱线图,效果和矩阵格式完全一样。选择哪种格式,取决于你手头数据的原始形态,哪种方便就用哪种。我个人的习惯是,如果数据本身是按组别分开收集的,就用矩阵格式;如果是从一个大的数据库或列表里筛选出来的,就用向量+分组格式。把数据整理成函数“爱吃”的样子,后面的一切都会顺畅很多。

3. boxplot()函数核心参数详解:掌控每一个细节

基础图会画了,数据也会导了,现在我们来“拆解”这个 boxplot 函数。它就像一个功能丰富的瑞士军刀,有很多参数可以调节,从而改变箱线图的外观和行为。不搞清楚这些参数,图就永远停留在“默认皮肤”。咱们挑几个最实用、改变最明显的参数来深挖一下。

最基本调用: boxplot(X) 或者 boxplot(X, G) 我们刚才已经见过了。其中 X 是数据,G 是可选的分组变量。

‘Labels’ 参数:给箱子命名。 这是解决我们之前“X轴标着1,2,3”问题的最直接方法。你可以在绘图时直接指定每个箱子的标签:

figure;
boxplot(score_data, 'Labels', {'第一组', '第二组', '第三组'});
title('带自定义标签的箱线图');

看,现在X轴下面显示的就是我们自定义的中文组名了,比冰冷的数字直观多了。这个 ‘Labels’ 参数接受一个细胞数组(cell array),里面按顺序存放每个组的标签字符串。

‘Colors’ 参数:给箱子上色。 默认的蓝色箱子虽然清晰,但看多了难免单调,或者当你想突出某一组时,颜色就派上用场了。你可以为每个箱子指定颜色:

figure;
boxplot(score_data, 'Labels', {'G1', 'G2', 'G3'}, 'Colors', 'rgb');
% ‘Colors’ 参数可以是一个字符序列,如‘rgb’代表红、绿、蓝
% 也可以是一个RGB三元组矩阵,如 [1 0 0; 0 1 0; 0 0 1] 同样代表红、绿、蓝

这里我用了 ‘rgb’,MATLAB会按顺序将第一个箱子涂红(r),第二个涂绿(g),第三个涂蓝(b)。颜色一上,不同组的区分度瞬间提升,在报告或论文里也更吸引眼球。

‘Whisker’ 参数:控制“须”的长度。 箱线图的“须”到底画到哪里?这有个标准。默认情况下,MATLAB的 boxplot 将须线延伸到不超过 四分位距(IQR = Q3 - Q1)的1.5倍 范围内的最远端数据点,超出此范围的点被视为异常值并单独绘制。这个1.5倍系数是 Tukey 提出的一个常用标准。但你可以改:

figure;
subplot(1,2,1);
boxplot(score_data, 'Whisker', 1.5); % 默认值
title('Whisker = 1.5 (默认)');

subplot(1,2,2);
boxplot(score_data, 'Whisker', 1.0); % 将系数改为1.0
title('Whisker = 1.0');

把 ‘Whisker’ 改小,比如设为1.0,意味着判定异常值的标准更严格,会有更多的数据点被当作异常值标出来,箱子的“须”也会相应地缩短。这在你想更敏感地探测离群点时有用。

‘Symbol’ 和 ‘OutlierSize’ 参数:打扮异常值。 默认的异常值标记是红色的‘+’号。你可以改变它的符号和大小。

figure;
boxplot(score_data, 'Symbol', 'ko', 'OutlierSize', 8);
% ‘Symbol’ 参数:‘k’代表黑色,‘o’代表圆圈。所以‘ko’就是黑色圆圈。
% ‘OutlierSize’ 参数控制标记点的大小。

现在异常值变成了黑色的圆圈,并且更大了一些。如果你觉得异常值干扰视线,甚至可以把符号设为空字符串来隐藏它们:‘Symbol’, ‘’。

‘Positions’ 参数:手动安排箱子的位置。 默认箱子是等间距排列在X轴的1,2,3...位置上。但有时你可能想让它们靠得更近或更远,或者按特定位置排列。

figure;
custom_positions = [1, 2.5, 4]; % 指定三个箱子的X轴位置分别为1, 2.5, 4
boxplot(score_data, 'Positions', custom_positions, 'Labels', {'A', 'B', 'C'});
xlim([0, 5]); % 调整X轴显示范围,让图看起来更协调

这个功能在你想把多张图并排对比,或者需要与另一个坐标轴对齐时特别有用。掌握了这几个核心参数,你已经能画出满足大部分需求的、高度定制化的箱线图了。但要让图表真正达到“出版级”质量,我们还得在绘图完成后,对图形对象进行更精细的“装修”。

4. 高级美化与定制:打造出版级图表

用 boxplot 函数本身的参数完成了初步设定后,我们得到的是一个“裸图”。MATLAB强大的地方在于,它创建的这个图形,里面每一个元素——箱子、中位线、须线、异常值——都是独立的图形对象,我们可以像玩橡皮泥一样,在画完之后再去捏它们的形状、颜色、粗细。这就需要用到 图形句柄(Graphics Handle) 和 get/set 函数。听起来有点技术,但操作起来就像在图形界面上点选设置一样直观,只不过是用代码实现。

首先,boxplot 函数会返回一个句柄,这个句柄就像是一组图形对象的“遥控器”。

figure;
h = boxplot(score_data, 'Labels', {'Control', 'Treatment A', 'Treatment B'});
% h 就是一个包含多个图形对象句柄的矩阵

这个 h 变量很重要。在MATLAB的新版图形系统中(R2014b以后),h 的每一列对应一个箱子(一组数据),每一行对应箱线图的不同部件。典型的,h(1,:) 可能对应异常值点,h(2,:) 对应下须线,h(3,:) 对应下四分位线(盒子底部),h(4,:) 对应中位线,h(5,:) 对应上四分位线(盒子顶部),h(6,:) 对应上须线,h(7,:) 对应箱子本身(盒子主体)。不同版本可能略有差异,最可靠的方法是画完图后,在命令行输入 h 看看它的结构,或者用 get 函数查看某个句柄的属性。

如何修改? 假设我想把所有箱子的中位线加粗并改成红色:

% 找到中位线对应的句柄行,通常是第4行
median_lines = findobj(gca, 'Tag', 'Median'); % 更稳健的方法:通过‘Tag’属性查找
% 或者 median_lines = h(4, :); (如果确定是第4行)

set(median_lines, 'LineWidth', 3, 'Color', 'r'); % 设置线宽为3,颜色为红

这里我用了一个更健壮的方法 findobj,它在当前坐标轴(gca)里寻找所有 ‘Tag’ 属性为 ‘Median’ 的对象。因为 boxplot 创建的中位线对象都有这个标记,这样找不容易出错。然后 set 函数就用来设置这些对象的属性。

再比如,我想单独把第二组(Treatment A)的箱子填充成浅绿色:

% 找到所有箱子对象
box_handles = findobj(gca, 'Tag', 'Box');
% box_handles 是一个包含三个箱子对象句柄的向量
% 第二个箱子就是第二组
set(box_handles(2), 'FaceColor', [0.8, 1, 0.8]); % 设置面颜色为浅绿 [R, G, B]

添加网格线,让读数更精确: 这是提升图表可读性最有效的方法之一。默认的图没有网格,我们手动加上。

grid on; % 打开网格
% 默认是同时显示X和Y轴的网格,如果只想显示Y轴网格(更常见于箱线图):
set(gca, 'XGrid', 'off', 'YGrid', 'on'); % 关闭X网格,打开Y网格
% 还可以设置网格线样式
set(gca, 'GridLineStyle', '--', 'GridAlpha', 0.3); % 设置为虚线,透明度0.3

调整坐标轴和字体: 让整个图看起来更专业。

xlabel('Experimental Group', 'FontSize', 12, 'FontWeight', 'bold');
ylabel('Measurement Value', 'FontSize', 12, 'FontWeight', 'bold');
title('Comparison of Effects Across Groups', 'FontSize', 14);
set(gca, 'FontSize', 11); % 设置坐标轴刻度字体大小

一个综合的美化示例: 我把上面这些技巧揉在一起,写一个完整的脚本。你可以运行看看效果,再根据自己的喜好调整颜色、线宽等参数。这种“先画图,后装修”的工作流非常灵活,你可以反复调整代码,直到得到最满意的视觉效果。

% 1. 准备数据
group1 = randn(30,1)*2 + 5; % 均值为5,标准差为2的正态分布数据
group2 = randn(30,1)*3 + 7; % 均值为7,标准差为3
group3 = randn(30,1)*1.5 + 6; % 均值为6,标准差为1.5
data = [group1, group2, group3];

% 2. 绘制基础箱线图
figure('Position', [100, 100, 800, 500]); % 设置图形窗口位置和大小
h = boxplot(data, ...
    'Labels', {'Control', 'Drug A', 'Drug B'}, ...
    'Whisker', 1.5, ...
    'Symbol', 'k+'); % 异常值用黑色‘+’

% 3. 高级美化
% 3.1 设置箱子颜色(用暖色调)
box_colors = [0.9 0.6 0.6; 0.6 0.9 0.6; 0.6 0.6 0.9]; % 浅红,浅绿,浅蓝
box_handles = findobj(gca, 'Tag', 'Box');
for i = 1:length(box_handles)
    set(box_handles(i), 'Color', box_colors(i,:)*0.7, ... % 边框颜色深一些
                        'LineWidth', 1.5, ...
                        'FaceColor', box_colors(i,:)); % 填充颜色浅一些
end

% 3.2 加粗中位线并改为深色
median_lines = findobj(gca, 'Tag', 'Median');
set(median_lines, 'Color', 'k', 'LineWidth', 2.5);

% 3.3 设置须线样式
whisker_lines = findobj(gca, 'Tag', 'Whisker');
set(whisker_lines, 'LineStyle', '-', 'Color', [0.3 0.3 0.3], 'LineWidth', 1.2);

% 3.4 添加网格
set(gca, 'YGrid', 'on', 'GridLineStyle', ':', 'GridAlpha', 0.4);

% 3.5 设置坐标轴和标题
xlabel('Treatment Group', 'FontSize', 13, 'FontWeight', 'bold');
ylabel('Response Level (units)', 'FontSize', 13, 'FontWeight', 'bold');
title('Box Plot of Treatment Effects with Custom Styling', 'FontSize', 15, 'FontWeight', 'bold');
set(gca, 'FontSize', 11, 'LineWidth', 1.2); % 坐标轴线宽

% 3.6 调整Y轴范围,留出一点空间
ylim([min(data(:))-1, max(data(:))+1]);

运行这段代码,你会得到一张色彩柔和、线条清晰、元素分明、带有网格参考线的箱线图。它已经具备了直接放入学术报告或论文插图中的潜力。这种精细控制的能力,是MATLAB可视化强大的体现。多尝试,多组合,你就能形成自己的图表美化风格。

5. 实战技巧与避坑指南

纸上得来终觉浅,绝知此事要躬行。在真正用箱线图分析数据的过程中,我踩过不少坑,也总结出一些能让效率翻倍的小技巧。这部分就是我的“实战笔记”,希望能帮你少走弯路。

技巧一:并排比较多个子图。 很多时候,我们不止有一组数据要画箱线图,可能有多组,或者想从不同维度对比。这时候用 subplot 功能非常方便。

figure('Position', [100, 100, 1200, 400]); % 宽一点的窗口

% 子图1:原始数据
subplot(1, 3, 1);
boxplot(original_data, 'Labels', {'Pre'});
title('Pre-treatment');
ylabel('Value');

% 子图2:处理后的数据
subplot(1, 3, 2);
boxplot(treated_data, 'Labels', {'Post'});
title('Post-treatment');
ylabel('Value');

% 子图3:前后对比(将两组数据放在一起)
subplot(1, 3, 3);
combined_data = [original_data(:), treated_data(:)]; % 确保都是列向量
boxplot(combined_data, 'Labels', {'Pre', 'Post'});
title('Direct Comparison');
ylabel('Value');

这样,三张关联的图就在一个窗口里整齐排列,对比效果一目了然。记得用 ylabel 只在最左边或中间的图设置一次,避免重复,让图面更干净。

技巧二:添加数据点(抖动散点)。 箱线图概括了分布,但损失了原始数据点的信息。有时,特别是在数据点不多的时候,我们希望在箱线图上叠加原始数据点,以展示数据的真实分布。这可以通过“抖动散点图(Jittered Scatter Plot)”来实现。

figure;
h = boxplot(score_data, 'Labels', {'G1', 'G2', 'G3'}, 'Widths', 0.7);
hold on; % 保持当前图形,以便在上面继续画

% 为每组数据添加抖动的散点
group_positions = [1, 2, 3]; % 箱子的中心位置
for i = 1:size(score_data, 2)
    x = group_positions(i) + 0.1 * randn(size(score_data, 1), 1); % 在X位置附近加随机抖动
    y = score_data(:, i);
    scatter(x, y, 40, 'k', 'filled', 'MarkerFaceAlpha', 0.6, 'MarkerEdgeAlpha', 0.6);
end
hold off;

这里 randn 生成了一个小幅度的高斯随机数,让数据点在X方向轻微散开,避免完全重叠。‘MarkerFaceAlpha’ 参数设置了点的填充透明度,这样点密集的地方颜色会加深,能直观反映数据密度。这个组合图既保留了箱线图的统计概括,又展示了原始数据分布,非常有说服力。

避坑指南:

  1. 异常值判断标准要清楚: 一定要明白箱线图的“须”和异常值是基于 1.5倍IQR 的规则画出来的。这个规则是经验性的,并非金科玉律。如果你的数据本身就有重尾分布(比如金融数据),可能会标记出大量“异常值”。此时需要结合业务知识判断,是数据真的有问题,还是这个规则不适用。你可以通过调整 ‘Whisker’ 参数来改变标准。
  2. 数据方向要对: boxplot 默认对矩阵的每一列画一个箱线图。如果你的数据是每一行代表一个变量,你需要先转置(data’)再绘图,或者使用 boxplot(data, ‘Orientation’, ‘horizontal’) 画水平箱线图,它有时会按行处理数据,具体看文档。画图前,先用 size(data) 确认一下矩阵维度。
  3. 图形句柄版本差异: 正如前面提到的,boxplot 返回的句柄结构在MATLAB不同版本间可能有细微变化。依赖于 h(4,:) 去找中位线在新版本中可能失效。最稳健的方法是使用 findobj 函数通过 ‘Tag’ 属性来查找对象,如 findobj(gca, ‘Tag’, ‘Box’)。‘Tag’ 属性是MATLAB给这些图形对象打上的内部标记,相对稳定。
  4. 保存高清图: 论文或报告需要高清图片。不要用截图!用 print 或 saveas 函数。
    % 保存为PNG格式,300 dpi
    print('my_boxplot.png', '-dpng', '-r300');
    % 保存为PDF格式(矢量图,无限放大不模糊)
    print('my_boxplot.pdf', '-dpdf', '-bestfit');
    % 或者直接使用“另存为”窗口提供的选项,选择‘矢量格式’如PDF或EPS。
    
    矢量图格式(PDF, EPS)是出版物的首选,因为它们在任何缩放级别下都保持清晰。

把这些技巧和注意事项记在心里,下次再遇到画箱线图的任务,你就能从容不迫,快速画出一张既准确又美观的图表。数据可视化不仅是技术,也是艺术,多练、多调、多思考,你就能成为用图表讲好数据故事的高手。

更多推荐