☰
Matlab数据分析实践:从数据清洗到统计检验与可视化
2026/10/13 23:02:05 网站建设 项目流程

经常有朋友拿着一堆实验数据问我,Excel点得手快抽筋,转Python又得折腾环境配库,到底有没有一个工具能当天上手、当天出结果。我的答案始终是:Matlab数据分析与计算。它不是什么万金油,但在交互式探索、矩阵运算、统计检验和出版级绘图这几件事上,确实能让一个不熟悉编程的人快速跑通完整流程。这篇文章写给科研人员、工程师和课程学习者,我会把处理数据时真正绕不开的环节——导入清洗、统计计算、可视化、代码沉淀——完整展开,最后把我踩过的坑和提速建议一并放在后面。

1. 为什么把Matlab作为数据分析的第一站,而不是替代品

1.1 数据工作的“三分钟原则”

拿到一份陌生数据,第一要务不是立刻跑模型,而是理解结构。我给自己定了一条“三分钟原则”:三分钟内必须看清数据的行数、列数、每个字段的类型、缺失比例和大致分布,否则后面全是盲人摸象。

Matlab在这件事上非常顺手。鼠标双击一个csv文件,导入工具会直接弹出前几行预览,还能让你手动指定每一列是数值、文本还是日期,确认后自动生成导入代码。进了工作区以后,用几行命令就能立刻建立数据直觉:

whos summary(T) head(T, 10) histogram(T.value)

对比Excel,几十万行数据时滚动和公式计算已经明显发飘;对比Python,虽然pandas也能做相同的事,但你首先得搞定解释器、包管理器,再等Jupyter启动。Matlab的启动路径短,命令窗口就是草稿纸,适合分析过程本身充满试探和回退的数据工作。很多数据不是一次算完的,而是边看边改思路,这种交互式环境的价值被严重低估了。

1.2 矩阵思维是隐藏的效率杠杆

数据分析里最常见的操作,本质上都是数组运算。Matlab在设计之初就把矩阵当作唯一主角,这让代码从“遍历每个点”变成“直接描述整列数据的关系”。

举个例子,假设要对一组电压信号做标准化:

z = (x - mean(x)) / std(x);

这一行在Matlab里对整列生效,在其他语言里往往要写成循环。再看滑动平均,如果你手动写一个窗口循环,100万行数据可能要跑几十秒;换成内置的movmean,一条命令:

smoothed = movmean(x, 50);

速度可能快一到两个数量级。新版Matlab还支持隐式扩展,比如把一组测量值和一组校准系数直接做除法,维度对不上时它会自动广播,省掉了很多显式复制矩阵的功夫。数据分析的时间瓶颈往往是人在等机器算完,而向量化写得越彻底,等待时间越短,晚上加班就越少。

1.3 它适合谁,又不适合谁

我的经验是,Matlab特别适合三种人:第一种是科研人员,分析步骤多样、图表要进论文,需求不会一成不变;第二种是工程师,经常要快速验证一个算法想法,或者给上一级汇报画几张关键的图;第三种是学生,课程里大量算法用Matlab描述最直白,调试起来也简单。

但它并不适合所有场景。如果你的任务是每天自动跑一次相同流程、结果要放进生产系统,或者数据量大到单机内存装不下,那更合适的方案是专业数据流水线或分布式平台。很多人纠结“学Matlab还是学Python”,这不是二选一。我的看法很直接:做交互式分析和出图,Matlab效率高;做工程部署和生态整合,再考虑Python。工具是杠杆,不是信仰。

2. 数据导入与预处理:从混乱表格到干净变量的实际操作

2.1 三种导入姿势的选择标准

在Matlab里导入数据至少有三种常用写法,选错了不会报错,但会给自己埋坑。

第一种是readtable,我的主力工具。它面向表格型数据,会自动识别列名、类型,返回一个table对象。列名里有空格或中文时建议加一个参数:

T = readtable('sensor_log.csv', 'VariableNamingRule', 'preserve'); head(T, 5) summary(T)

VariableNamingRule保持列名原样,避免自动改名成“合法变量名”后,你对着原始文档找不到对应字段。table对象的好处是每一列可以有不同类型,文本、数值、日期混在一张表里也没问题。

第二种是readmatrix,适合你确定文件里全部是数值、不需要列名,直接拿来做纯矩阵运算更清爽:

M = readmatrix('measurements.txt', 'NumHeaderLines', 1, 'Delimiter', ',');

第三种是importdata,老版本兼容性最强,遇到格式混乱的文件经常能用它兜底。但它的返回结构有时很别扭——文本部分变成cell数组、数值部分变成单独的字段,处理起来要多一层包装。所以能用前两种就尽量不用它。

判断用哪个,核心是回答两个问题:数据有没有列名?列的类型是否统一?有列名、类型混合,用readtable;纯数值、行列固定,用readmatrix。

2.2 缺失值、重复时间戳与单位问题:数据清洗三板斧

真实数据很少是干净的。我拿到手的数据里,最常见的三类问题就是缺失、重复和时间戳错乱。

缺失值处理分两步:先确认,后处理。确认用ismissing,处理可以删除也可以填充:

idxMissing = ismissing(T.raw); disp(sum(idxMissing)) T = rmmissing(T); % 直接删除有缺失的行 T.interpVal = fillmissing(T.value, 'linear');

删除和填充的选择要慎重。如果缺失比例很小且是随机缺失,rmmissing最省事;如果是时间序列,我一般倾向于填充。填充方法里,'linear'对线性趋势的数据最自然,'movmean'适合缓慢变化的信号,'previous'适合顺序记录的类别型数值,'spline'适合周期性和光滑数据。不要一上来就fillmissing填完拉倒,先画一张图看看缺失段的位置,再做决定。

重复时间戳的问题经常出现在多传感器合并数据时。对普通表格,用unique加stable参数去重;对时间序列,更推荐先把数据转成timetable,然后直接retime重采样:

tt = timetable(T.time, T.value); ttUnique = retime(tt, 'unique', 'mean');

retime的意思是按时间对齐数据,同一个时间戳如果有多个观测,可以用均值、最大值或第一个值合并。这一步把时间轴变成标准网格后,后续做相关分析、FFT都会方便很多。

单位问题听起来低级,实际坑人。传感器采集回来的电压要除以增益再乘灵敏度才变成物理量,这类换算我强烈建议集中写在一个函数或一个脚本顶部,而不是散落在各段代码里。否则两个月后你自己翻脚本,看到某个神秘系数0.0237,根本想不起它是哪来的。

2.3 统计速览与数据类型陷阱

清洗完之后,先用一组快速命令建立整体认知:

summary(T) tabulate(T.channel) histogram(T.tempC)

summary输出每列的最小值、最大值、中位数、缺失数量,一眼就能看出异常范围。tabulate对类别列做频次统计,适合检查分组是否均衡。

这里要特别提醒一个readtable的陷阱:文本列可能被识别成string类型,也可能被识别成cell数组,还可能在包含“NA”“NULL”时自动转成缺失值。当你发现某个列的类型莫名其妙时,先查一下:

class(T.raw)

如果是cell数组,可以用string(T.raw)转换;如果数值列被错认成文本,那就要在导入时指定类型。日期时间列建议直接用datetime类型,不要再碰老式的datenum。新版datetime可以直接做差、比较、重采样,语义清晰得多:

t = datetime(T.timestamp, 'InputFormat', 'yyyy-MM-dd HH:mm:ss'); T.elapsedSec = seconds(t - t(1));

3. 核心统计计算:从描述性统计到回归与检验的完整流程

3.1 描述性统计:一张表格看懂数据的全貌

描述性统计是整个分析的起点。mean、median、std、min、max、prctile这些函数很好记,但有一个细节必须强调:如果列里有NaN,默认很多统计函数会直接返回NaN,而不是跳过缺失值。必须显式加上'omitnan':

mu = mean(T.value, 'omitnan'); sd = std(T.value, 'omitnan'); p = prctile(T.value, [5 25 50 75 95]);

当处理的是矩阵、需要在每个维度上统计时,注意维度参数:

colMeans = mean(T.value, 1, 'omitnan'); rowMeans = mean(T.value, 2, 'omitnan');

除了均值方差,偏度skewness和峰度kurtosis值得养成习惯。遇到分布严重偏斜的数据,后续回归和检验的方法选择都会受影响。描述统计这一步,我建议把所有关键结果集中输出成一张表格,后面写报告时直接引用:

指标温度通道压力通道
均值56.23.82
标准差3.10.42
中位数56.53.85
缺失数120

3.2 相关分析到线性回归:不能只调一个函数

要理解变量之间的关联,先算相关矩阵:

[R, P] = corrcoef(T.tempC, T.pressure); Rho = corr(T.tempC, T.pressure, 'Type', 'Spearman');

corrcoef默认是皮尔逊相关,适合线性关系;如果数据分布偏斜或存在明显离群点,我会改用corr指定Spearman,它是基于秩的相关系数,对异常值稳健得多。

线性回归直接用fitlm,它会自动处理多个自变量和交互项:

mdl = fitlm(T, 'y ~ x1 + x2'); disp(mdl)

拟合结果里,mdl.Coefficients给出每个系数的估计值和p值,mdl.Rsquared给出拟合优度,mdl.Residuals可以画出来诊断模型是否满足假设。我几乎每次都会画残差图:

plotResiduals(mdl, 'fitted');

残差如果表现出明显趋势或喇叭形发散,说明模型形式有问题,而不是单纯地加个R方就交差。

回归里最容易犯的错是过度拟合。尤其自变量一多,模型在训练集上表现好看,换一批数据就崩。用交叉验证评估才是可靠的做法:

rng(0); cvp = cvpartition(height(T), 'KFold', 5); for k = 1:5 tr = training(cvp, k); te = test(cvp, k); mdl = fitlm(T(tr, :), 'y ~ x1 + x2'); pred = predict(mdl, T(te, :)); err(k) = sqrt(mean((T.y(te) - pred).^2)); end mean(err)

如果分类问题,fitcensemble、fitcknn这类模型也有对应的交叉验证接口,思路完全一致。记住:在本地数据上挖出来的规律好不好,得拿到没有参与训练的数据上去检验。

3.3 显著性检验:什么时候用参数检验,什么时候用非参数检验

比较两组数据的差异时,最常用的是独立样本t检验:

[h, p] = ttest2(groupA, groupB);

这里h=1表示在显著性水平0.05下拒绝原假设。如果两组数据是配对的——比如同一批样品处理前后的测量值——那就用配对t检验:

[h, p] = ttest(before, after);

参数检验对正态性和方差齐性有要求。当样本量很小或者分布明显偏态时,我会改用秩和检验:

[p, h] = ranksum(groupA, groupB); % 对应独立t检验 [p, h] = signrank(before, after); % 对应配对t检验

多组比较不要两两轮流做t检验,多重比较会让假阳性概率飙升。正确做法是先做单因素方差分析:

[p, tbl, stats] = anova1(data, groups);

如果数据不满足方差分析的正态假设,就用kruskalwallis。事后多重比较可以用multcompare,它会自动修正p值。

另外,p值不是终点。样本容量大的时候,差异小也可能p值极显著,但实际效果微不足道。我会顺手算一个效应量,比如Cohen's d:

d = (mean(A) - mean(B)) / sqrt((std(A)^2 + std(B)^2) / 2);

3.4 频域计算:给时序数据加上一副频谱眼镜

周期性信号靠眼睛看时域图很难看出名堂,这时需要FFT。很多人在这一步栽跟头是因为忘了归一化。正确做法是:

N = length(x); Y = fft(x); P2 = abs(Y / N); P1 = P2(1:N/2+1); P1(2:end-1) = 2 * P1(2:end-1); f = fs * (0:N/2) / N; plot(f, P1);

除以N以后,单频正弦波的幅值才能在频谱上恢复成真实幅值。取前半段是因为实信号频谱关于奈奎斯特频率对称,把后半段能量折回来才是单边谱。如果数据不是整数周期,裸FFT会有明显的频谱泄漏,我的习惯是先加窗:

w = hann(N); Y = fft(x .* w);

加窗会改变总能量,需要做幅度补偿,但这通常让频谱峰值更集中、更可读。对功率谱密度估计,我一般用pwelch,它内部做了分段和平均,结果更平滑:

[pxx, f] = pwelch(x, hamming(1024), 512, 1024, fs); plot(f, 10*log10(pxx));

如果用dB显示,纵轴就是功率谱密度,适合对比不同频段能量。

4. 可视化呈现:把计算结果做成能进报告的图表

4.1 从草稿图到展示图,差的不是天赋是设置

很多人画图就是plot一按,弹出一张图截屏放进报告,线条细、坐标字号小、没有单位,一看就是临时产物。让图能进报告,至少要做到:线宽足够、颜色有对比、坐标轴带标签、有必要的网格。

plot(t, y, 'LineWidth', 1.5, 'Color', [0.8500 0.3250 0.0980]); xlabel('时间 / s'); ylabel('振幅 / V'); grid on; legend('实测值', 'Location', 'best');

颜色推荐直接用Matlab默认配色方案里的前几种,饱和度适中,打印出来也分得清。多子图布局现在优先用tiledlayout而不是老式subplot,因为前者自动对齐坐标轴、共享标签更方便:

tiledlayout(2, 1); nexttile; plot(...); nexttile; plot(...);

展示回归结果时,我习惯把散点和拟合线画在一起,再加置信带:

scatter(T.x, T.y, 20, [0.4 0.4 0.4], 'filled'); hold on; [pred, ci] = predict(mdl, newX, 'Prediction', 'curve'); plot(newX, pred, 'r-', 'LineWidth', 1.6); plot(newX, ci, 'r--', 'LineWidth', 1);

4.2 导出高清图的隐藏规则

导出图片时,老办法是print,但我现在统一用exportgraphics,它对字体、分辨率和尺寸的处理更稳定:

exportgraphics(gcf, 'result.png', 'Resolution', 300);

如果需要把多张图拼成一张大图给汇报用,还可以直接导出矢量格式:

exportgraphics(gcf, 'result.pdf', 'ContentType', 'vector');

矢量PDF放进论文或幻灯片里,放大不糊,字体也能保持锐利。还有一个全局设置建议在脚本开头执行一次,避免每张图重新调字号:

set(groot, 'DefaultAxesFontSize', 12); set(groot, 'DefaultLineLineWidth', 1.2);

4.3 大数据可视化最有效的几个策略

数据点太多时,每个点都画出来没有意义,还会让图和机器一起卡顿。常见做法是降采样,比如最多画5000个点:

idx = randsample(length(x), min(length(x), 5000)); plot(t(idx), y(idx), '.');

二维密度数据用scatter画几十万点非常吃力,改用histogram2或imagesc热力图不仅快,而且能看出局部密度。多维成对关系探索时,gplotmatrix一行代码就能生成散点图矩阵:

gplotmatrix(T{:, 1:4}, [], [], [], [], [], [], 'variable', T.Properties.VariableNames(1:4));

它不会直接给出最终报告图,但能帮你快速发现哪些变量有线性趋势、哪些分布像正态、哪些组之间存在明显分离。可视化首先是给自己看的探索工具,其次才是给别人看的沟通工具。

5. 脚本、函数与Live Script:让分析方法可以复用和传承

5.1 从脚本到函数:换一批数据不用改代码

很多人写完一版分析脚本,下一批数据进来,得手动改文件名、改列名、改阈值。把流程封装成函数是质的改变:

function [stats, fig] = analyzeSensorData(filepath, opts) arguments filepath string opts.threshold double = 3 opts.plot logical = true end T = readtable(filepath); % 核心处理... end

arguments块可以给参数加默认值,调用方不传就自动使用默认。这样同一个函数可以用在不同文件上:

stats1 = analyzeSensorData('batch1.csv'); stats2 = analyzeSensorData('batch2.csv', threshold=2.5, plot=false);

封装的好处不只是少改代码,更重要的是调用方式清晰、不会污染工作区,别人接手时只需要看懂函数的输入输出。

5.2 Live Script是把分析过程变成报告的好工具

分析做完以后,要给别人讲清楚整个过程,靠截图加聊天记录是最差的方式。Matlab的Live Script(.mlx)把代码、输出和文字说明放在同一个页面里,执行的图表就嵌在代码旁边,导成PDF就能直接当技术报告用。

我一般会在Live Script里做这些事:首先写一段标题和背景说明,然后分节导入数据,节与节之间插入Markdown文字描述每个处理步骤的理由,最后把关键图表的结论直接写在图下面。这对非技术背景的同事尤其友好,他不用装软件,只要看你导出的PDF就能明白数据经历了什么处理、结果可信度如何。

需要提醒的是,新版Live Script文件格式在老版本软件里可能打不开。如果你的合作方还在用老版本,最好同时导出一份静态PDF或传统.m脚本。

5.3 批量处理多个文件的循环与汇总

当你要处理几十个同类文件时,千万别一个一个手动跑。我用得最多的模式是这样:

files = dir('data/*.csv'); results = table(); for i = 1:length(files) T = readtable(fullfile(files(i).folder, files(i).name)); mu = mean(T.value, 'omitnan'); sd = std(T.value, 'omitnan'); results = [results; {files(i).name, mu, sd}]; end writetable(results, 'summary.csv');

如果文件量特别大,我倾向于把中间结果分文件缓存,而不是把几GB数据全部压进内存。datastore可以按块读取,但日常场景先把单个文件读入、算完、清空再用下一轮,往往就够用了。写循环时注意预分配results容量,否则每添加一行都要重新拷贝整张表,文件一多速度会明显变慢。

6. 常见坑位与效率提升:几个值得记住的细节

6.1 向量生成的浮点误差:冒号与linspace的区别

1:0.1:10这种写法看着自然,实则可能因为浮点累加导致最后一个元素不是精确的10。如果你后续要用这个向量去和某个已知的值比较,很容易出问题。更稳妥的写法是指定点数:

t = linspace(0, 10, 101);

linspace明确告诉程序生成101个点,不受步长累加误差影响。另外,1:10生成的是行向量,如果数据是按列组织的,别忘了转置。这个不起眼的细节,在矩阵乘法或者绘图时经常会突然冒出来。

6.2 NaN和Inf比你想象的更麻烦

默认情况下,很多统计函数遇到NaN就返回NaN。你要么在所有调用里写'omitnan',要么干脆在数据预处理阶段就把缺失值统一处理好,让后续计算的环境尽量干净。我习惯把这两个模式分开:计算关键表格时保留原始列,复制一份cleanCol去做统计。

绘图函数遇到NaN会当作断点,这一条可以反过来利用——想在一张图里隐藏某段数据,直接把那段设为NaN就行,不用切割数组。

6.3 预分配内存:别让循环越跑越慢

写循环给结果赋值时,如果不提前初始化数组,Matlab会在每步迭代里重新分配内存,数据量大时速度会慢到让人怀疑人生。正确的做法是先创建定长数组:

out = zeros(N, 1); for i = 1:N out(i) = computeValue(...); end

同样的道理适用于循环里不断拼接[a; b]。与其在循环里扩张,不如提前算好规模,或者先放cell数组最后一次性合并。向量化能做的事情尽量向量化,但不要为了秀技巧把代码写成一行天书——分析脚本最重要的是自己和同事能看懂。

6.4 FFT结果归一化,十个人里九个忘

前文已经提到FFT要除以N,这里再强调一次。不除N,频谱幅值会大N倍;不加窗,非整周期信号会出现严重频谱泄漏。还有一个容易忽略的点:当信号是实信号时,负频率部分不画,通常要把单边谱的中间段幅度乘以2,否则幅值对不上时域波形的真实幅度。

6.5 readtable自动识别类型的坑,用detectImportOptions提前控制

readtable自动识别列类型很方便,但有时会把“01”这种编号列识别成数值,把带“NA”的文本列识别为缺失值。想完全掌控列类型,用detectImportOptions:

opts = detectImportOptions('data.csv'); opts = setvartype(opts, {'id', 'tempC', 'pressure'}, {'string', 'double', 'double'}); T = readtable('data.csv', opts);

这个方法能提前看到每一列会被识别成什么类型,再手动修正,比读进来之后再各种转换要省事得多。我处理格式混乱的历史数据时,几乎都会先跑一遍detectImportOptions,再把列类型按预期设定好。


最后分享一点个人体会。用了这么多年Matlab做数据分析,我最大的变化不是记了多少函数,而是养成了“先把数据看清楚再谈分析”的习惯。很多问题在画图阶段就会暴露——单位不对、缺失成片、分组失衡,这些靠统计数值未必一眼看得出来,但图不会骗你。如果你刚开始接触Matlab,我建议不要急着背所有函数,先拿一份真实数据完整走一遍导入、清洗、统计、画图、导出这个闭环。走通之后,再把常用的分析流程封装成自己的函数库,下次遇到类似数据就是几分钟的事。这个从“会用”到“顺手”的过程,比看十篇教程都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询