MATLAB数据分析入门:均值、标准差、偏度与峰度的实战解读
1. 项目概述:从四个数字读懂你的数据
做数据分析,尤其是刚开始接触一堆新数据的时候,很多人会习惯性地一头扎进复杂的模型和炫酷的可视化里。但我的经验是,在按动任何复杂的算法按钮之前,你得先学会和你的数据“聊聊天”。怎么聊?最直接的方式就是看看它的几个基本“体貌特征”:均值、变异度、偏度和峰度。这就像认识一个人,你先得知道他的平均身高(均值)、身材是匀称还是胖瘦差异大(变异度)、站姿是向左歪还是向右斜(偏度)、以及是精瘦结实还是圆润丰满(峰度)。
在MATLAB这个强大的计算环境中,计算这些指标不过是几行代码的事。但真正有价值的,不是你会敲mean()或std(),而是你能从这些简单的数字里读出什么故事,发现什么问题,以及如何指导你后续的分析决策。今天,我就结合自己处理工程信号、金融时间序列和实验数据的实际经历,来拆解这四个看似基础却至关重要的描述性统计量,让你在MATLAB里不仅会算,更会看、会用。
2. 均值:数据的“重心”与它的多种面孔
均值,通常指算术平均值,是所有人最熟悉的统计量。在MATLAB里,计算一维数组data的均值简单到只需m = mean(data)。但均值真的只是把所有数加起来再除一下那么简单吗?在实际分析中,远非如此。
2.1 算术平均:最直观的“中心”估计
算术平均值代表了一组数据的“重心”位置。假设你有一组产品直径的测量值(单位:毫米):[10.1, 10.0, 10.2, 9.9, 10.3, 100.0]。用MATLAB计算:
data = [10.1, 10.0, 10.2, 9.9, 10.3, 100.0]; mean_arithmetic = mean(data)结果大约是25.08。这个值显然不能代表大多数产品直径(都在10mm左右),因为最后一个值100.0是一个明显的异常值(可能是测量错误或录入错误)。算术平均值对极端值非常敏感,这是它最大的优点(能反映所有信息),也是最大的缺点(容易被“带偏”)。
注意:在计算均值前,务必进行数据清洗和异常值诊断。盲目地对包含异常值的数据求平均,会得到极具误导性的“中心”估计。可以使用箱线图(
boxplot)或基于标准差的方法(如剔除均值±3倍标准差以外的点)进行初步筛查。
2.2 截尾均值与中位数:对抗异常值的稳健选择
当数据可能存在异常值或分布严重偏斜时,我们需要更稳健的“中心”度量。这里有两个好朋友:
中位数(Median):将数据排序后位于正中间的值。计算命令是
median(data)。对于上面的直径数据,中位数是10.15((10.1+10.2)/2),这个值显然更能代表数据的典型水平。中位数对极端值完全不敏感,只有一半的数据比它大,一半比它小。截尾均值(Trimmed Mean):去掉一定比例的最大值和最小值后,再计算剩余数据的算术平均。MATLAB中可以使用
trimmean(data, percent)。例如,计算20%截尾均值(去掉最小的10%和最大的10%的数据):data_large = randn(1000,1)*10 + 50; % 生成正态分布数据 data_large(randi(1000, 10,1)) = data_large(randi(1000,10,1)) + 200; % 加入10个异常高值 mean_trimmed_20 = trimmean(data_large, 20)截尾均值在抵抗异常值的同时,比中位数利用了更多的数据信息,是实践中非常实用的一个折中指标。
如何选择?我的经验法则是:先看分布,再选指标。对于近似对称且无明显异常值的数据,用算术均值;对于有异常值或明显偏态的数据,报告中位数或截尾均值更为稳妥。在MATLAB中,可以快速绘制直方图(histogram)或核密度估计图(ksdensity)来直观判断分布形状。
2.3 加权均值:当每个数据点“分量”不同
在有些场景下,每个数据点的重要性不同。例如,计算多个市场的平均价格时,需要以各市场的交易量为权重。加权均值的公式是sum(w .* x) / sum(w),在MATLAB中可以轻松实现:
prices = [100, 102, 99]; % 三个市场的价格 volumes = [1000, 500, 1500]; % 对应的交易量 weighted_mean_price = sum(prices .* volumes) / sum(volumes)MATLAB没有内置的加权均值函数,但上述写法清晰易懂。对于更复杂的统计计算,Statistics and Machine Learning Toolbox中的函数(如nansum处理带权重的缺失值)会更有帮助。
3. 变异度:数据是“紧密”还是“散漫”
知道了数据的中心在哪,下一步自然要问:这些数据是紧密团结在中心周围,还是自由散漫地四处分布?这就是变异度(或称离散程度)要回答的问题。标准差和方差是最常用的指标,但它们也有“亲戚”。
3.1 方差与标准差:最主流的离散度量
方差(Variance)是每个数据点与均值之差的平方的平均数,强调了较大偏差的影响。标准差(Standard Deviation)是方差的平方根,恢复了原始数据的量纲,更便于解释。
在MATLAB中:
data = randn(100,1)*5 + 10; % 均值为10,标准差为5的正态分布样本 v = var(data); % 计算方差 s = std(data); % 计算标准差这里有一个至关重要的细节:var和std函数默认使用N-1进行归一化(即样本方差/标准差),其中N是样本大小。这是为了对总体方差进行无偏估计。如果你计算的是总体的全部数据(而非样本),需要使用var(data, 1)和std(data, 1)来使用N进行归一化。
实操心得:在报告结果时,永远注明你使用的是样本标准差还是总体标准差,尤其是在与领域内其他研究结果比较时。一个简单的标注可以避免很多误解。对于工程测量数据,我通常将其视为样本。对于仿真生成的全部可能结果,我将其视为总体。
3.2 极差与四分位距:直观且稳健的补充
虽然标准差很强大,但它和均值一样,受异常值影响大。因此,我们还需要一些稳健的变异度指标。
- 极差(Range):最大值与最小值之差。计算简单(
range(data)),但同样对异常值极度敏感,一个异常大值就能让极差失去意义。 - 四分位距(Interquartile Range, IQR):这是我最常用的稳健离散度度量。它是第三四分位数(Q3,75%分位数)与第一四分位数(Q1,25%分位数)之差,即
IQR = Q3 - Q1。它描述了中间50%数据的分布范围,对异常值不敏感。
IQR在绘制箱线图(data_with_outlier = [randn(50,1); 100]; % 50个正常值+1个巨大异常值 iqr_value = iqr(data_with_outlier); % 计算IQR q1 = quantile(data_with_outlier, 0.25); q3 = quantile(data_with_outlier, 0.75);boxplot)时是核心元素,箱子的长度就是IQR。通常,将小于Q1 - 1.5*IQR或大于Q3 + 1.5*IQR的点视为潜在的异常值。
3.3 变异系数:比较不同尺度数据的离散程度
标准差是有量纲的。比较身高(厘米)和体重(公斤)的离散程度,直接比较标准差没有意义。这时需要变异系数(Coefficient of Variation, CV),它是标准差与均值的比值:CV = std(data) / mean(data)。
height = randn(100,1)*5 + 170; % 身高,均值170cm,标准差5cm weight = randn(100,1)*8 + 65; % 体重,均值65kg,标准差8kg cv_height = std(height) / mean(height); cv_weight = std(weight) / mean(weight); % 比较cv_height和cv_weight,数值大的相对离散程度更高。CV是一个无量纲量,非常适合比较不同单位或量级差异很大的数据集的相对波动性。在金融领域,常用它来比较不同资产的风险收益比(夏普比率的一个简化视角);在质量控制中,用来比较不同生产线的稳定性。
4. 偏度:数据分布的“不对称性”
偏度描述了数据分布偏离对称分布的程度和方向。它是三阶标准化的矩。
4.1 正偏、负偏与零偏
- 正偏(右偏):分布右侧有长尾,均值 > 中位数 > 众数。例如,个人收入数据,多数人收入集中在较低水平,少数高收入者将尾巴拉向右侧。在MATLAB中,
skewness(data) > 0。 - 负偏(左偏):分布左侧有长尾,均值 < 中位数 < 众数。例如,考试分数数据,如果题目非常容易,多数学生考高分,少数低分将尾巴拉向左侧。
skewness(data) < 0。 - 零偏:近似对称分布,如完美的正态分布。
skewness(data) ≈ 0。
计算偏度使用skewness(data)。和方差一样,默认基于样本进行无偏估计。你可以通过skewness(data, 0)(默认,无偏)或skewness(data, 1)(有偏,基于总体)来指定。
4.2 偏度的实际意义与视觉判断
偏度不仅是一个数字。一个显著不为零的偏度值对你选择分析方法有重要影响。
- 对均值的影响:如前所述,在偏态分布中,均值会被拉向长尾方向,因此它可能不再是“典型值”的良好代表。此时报告中位数更为合适。
- 对模型假设的挑战:许多经典的统计模型(如线性回归、t检验)都假设数据残差近似正态分布(即偏度接近零)。显著的偏度可能违背这一假设,导致模型推断不准。此时可能需要考虑数据变换(如对数变换处理正偏数据)或使用非参数方法。
- 视觉辅助判断:在计算偏度值的同时,一定要绘制直方图或核密度图。有时,一个极端的异常值会导致偏度值很大,但分布主体仍是对称的。此时,需要结合稳健的偏度估计(如基于分位数的Bowley偏度)或先处理异常值。
% 生成正偏和负偏数据示例 positive_skew_data = exprnd(2, 1000, 1); % 指数分布,通常正偏 negative_skew_data = max(positive_skew_data) + 1 - positive_skew_data; % 构造一个负偏分布 figure; subplot(1,2,1); histogram(positive_skew_data); title(['正偏,偏度=', num2str(skewness(positive_skew_data))]); subplot(1,2,2); histogram(negative_skew_data); title(['负偏,偏度=', num2str(skewness(negative_skew_data))]);5. 峰度:数据分布的“尖锐”或“平坦”程度
峰度衡量的是分布尾部“厚重”的程度,或者说数据是集中在均值附近(尖峰)还是更均匀地分散(扁峰)。它是四阶标准化的矩。这里有一个最常见的误解:峰度并不是描述分布峰态的“尖”或“平”,而是描述尾部“厚”或“薄”。高峰度意味着有更多数据分布在远离均值的尾部(即极端值出现的概率高于正态分布)。
5.1 超额峰度与三种定义
MATLAB中kurtosis(data)函数返回的是超额峰度。它的定义是:超额峰度 = (数据四阶中心矩 / 方差^2) - 3。
为什么要减3?因为标准正态分布的峰度恰好是3。所以:
- 超额峰度 = 0:分布尾部与正态分布相似(常称为“常峰态”)。
- 超额峰度 > 0:分布具有“厚尾”(尖峰厚尾,Leptokurtic)。极端值出现的可能性高于正态分布。金融收益率数据常呈现此特征。
- 超额峰度 < 0:分布具有“薄尾”(平峰薄尾,Platykurtic)。数据更集中,极端值较少。均匀分布是典型的例子。
重要提示:有些软件(如Excel)和教材中定义的峰度没有减3(称为“原始峰度”)。因此,看到峰度值时,必须明确它是“原始峰度”还是“超额峰度”。MATLAB默认输出超额峰度,这是一个非常好的实践,因为它直接以正态分布为基准进行比较。
5.2 峰度的应用与陷阱
高峰度(厚尾)在实际中意味着更大的风险。在金融领域,如果资产回报率的分布呈现高峰度,那么发生极端暴涨或暴跌的概率就比基于正态分布的模型预测的要高,基于正态假设的风险模型(如VaR)就会低估真实风险。
计算示例:
% 比较正态分布、t分布(厚尾)和均匀分布(薄尾)的峰度 data_normal = randn(10000,1); data_t = trnd(5, 10000,1); % 自由度为5的t分布,厚尾 data_uniform = rand(10000,1)*2 - 1; % 均匀分布,薄尾 kurt_normal = kurtosis(data_normal); % 应接近0 kurt_t = kurtosis(data_t); % 应显著大于0 kurt_uniform = kurtosis(data_uniform); % 应小于0 (约为-1.2)峰度对异常值极其敏感,甚至比偏度更敏感。一个远离群体的极端值会极大地增加峰度值。因此,在解读峰度时,必须结合图形(如直方图、Q-Q图)和稳健性考虑。有时,高峰度仅仅是由一两个异常值造成的,而非整个分布的本质特征。
6. MATLAB综合实战:一个完整的数据描述流程
现在,让我们把这些指标串起来,对一个真实场景的数据集进行完整的描述性分析。假设我们有一组来自某传感器测量的温度数据(单位:摄氏度),存储在变量temp_data中。
6.1 第一步:数据导入与初步观察
% 假设数据已从CSV文件导入,或直接定义 % temp_data = readmatrix('temperature_data.csv'); % 从文件读取 temp_data = [22.1, 22.3, 21.9, 22.0, 22.5, 22.2, 21.8, 22.4, 22.1, 22.6, ... 22.0, 22.2, 21.7, 22.3, 22.1, 100.0, 22.4, 22.0, 21.9, 22.5]; % 包含一个明显异常值100 % 快速查看数据维度和前几个值 fprintf('数据样本数: %d\n', length(temp_data)); fprintf('前5个数据点: '); disp(temp_data(1:5));6.2 第二步:计算核心描述性统计量
我们将计算包括稳健指标在内的全套统计量。
% 1. 中心趋势 mean_val = mean(temp_data); median_val = median(temp_data); trimmed_mean_10 = trimmean(temp_data, 10); % 5%截尾均值 % 2. 离散程度 std_val = std(temp_data); % 样本标准差 var_val = var(temp_data); iqr_val = iqr(temp_data); range_val = range(temp_data); cv_val = std_val / mean_val; % 变异系数 % 3. 分布形状 skew_val = skewness(temp_data); kurt_val = kurtosis(temp_data); % 超额峰度 % 将结果整理成表格显示 var_names = {'均值', '中位数', '10%截尾均值', '标准差', '方差', 'IQR', '极差', '变异系数', '偏度', '峰度'}; values = [mean_val, median_val, trimmed_mean_10, std_val, var_val, iqr_val, range_val, cv_val, skew_val, kurt_val]; fprintf('\n--- 描述性统计量汇总 ---\n'); for i = 1:length(var_names) fprintf('%s: %.4f\n', var_names{i}, values(i)); end运行这段代码,你会立刻发现矛盾:均值(约24.6°C)远高于中位数(22.15°C)和截尾均值(约22.2°C)。标准差极大(约17.4),变异系数高达0.71,偏度为3.6(强正偏),峰度高达13.5(极端厚尾)。所有这些异常都指向同一个罪魁祸首:那个100°C的异常值。
6.3 第三步:可视化诊断与异常值处理
计算只是开始,图形才能告诉我们完整的故事。
figure('Position', [100, 100, 1200, 400]); % 子图1:带核密度曲线的直方图 subplot(1,3,1); histogram(temp_data, 'Normalization', 'pdf', 'FaceColor', [0.2 0.6 0.8], 'EdgeColor', 'none'); hold on; [f, xi] = ksdensity(temp_data); plot(xi, f, 'r-', 'LineWidth', 2); xlabel('温度 (°C)'); ylabel('概率密度'); title('温度分布直方图与核密度估计'); grid on; legend('直方图', '核密度曲线'); % 子图2:箱线图 subplot(1,3,2); boxplot(temp_data, 'Labels', {'原始数据'}); ylabel('温度 (°C)'); title('箱线图(识别异常值)'); grid on; % 子图3:Q-Q图(与正态分布比较) subplot(1,3,3); qqplot(temp_data); title('Q-Q图(检验正态性)'); grid on;图形会清晰显示:直方图在100°C处有一个孤立的柱状;箱线图会明确将100°C标记为异常值(超出上须);Q-Q图上的点严重偏离参考直线,尤其是在上尾部。
基于IQR法则处理异常值:
Q1 = quantile(temp_data, 0.25); Q3 = quantile(temp_data, 0.75); IQR = Q3 - Q1; lower_bound = Q1 - 1.5 * IQR; upper_bound = Q3 + 1.5 * IQR; is_outlier = (temp_data < lower_bound) | (temp_data > upper_bound); clean_data = temp_data(~is_outlier); fprintf('\n异常值界限: [%.2f, %.2f]\n', lower_bound, upper_bound); fprintf('识别出的异常值索引: %s\n', mat2str(find(is_outlier))); fprintf('异常值: %.2f\n', temp_data(is_outlier)); fprintf('清洗后数据样本数: %d\n', length(clean_data));6.4 第四步:清洗后数据的再分析
对清洗后的数据重新计算统计量:
% 重新计算清洗后数据的统计量 mean_clean = mean(clean_data); median_clean = median(clean_data); std_clean = std(clean_data); skew_clean = skewness(clean_data); kurt_clean = kurtosis(clean_data); fprintf('\n--- 清洗后数据统计量 ---\n'); fprintf('均值: %.4f\n', mean_clean); fprintf('中位数: %.4f\n', median_clean); fprintf('标准差: %.4f\n', std_clean); fprintf('偏度: %.4f\n', skew_clean); fprintf('峰度: %.4f\n', kurt_clean); % 比较清洗前后 fprintf('\n--- 清洗前后对比 ---\n'); fprintf('均值变化: %.2f -> %.2f\n', mean_val, mean_clean); fprintf('标准差变化: %.2f -> %.2f\n', std_val, std_clean); fprintf('偏度变化: %.2f -> %.2f\n', skew_val, skew_clean); fprintf('峰度变化: %.2f -> %.2f\n', kurt_val, kurt_clean);现在,你会看到数据恢复了“正常”:均值和中位数非常接近(约22.2°C),标准差很小(约0.26),偏度和峰度都接近0。这表明清洗后的温度数据近似服从正态分布,传感器测量在正常情况下是稳定和精确的。那个100°C的读数很可能是传感器瞬态故障、数据传输错误或人为录入错误。
6.5 第五步:生成自动化描述报告
我们可以将上述步骤封装成一个函数,用于快速生成任何数据集的描述性分析报告。
function generate_descriptive_report(data, data_name) % 生成描述性统计报告 fprintf('\n========== 描述性分析报告: %s ==========\n', data_name); fprintf('样本数量: %d\n', length(data)); % 中心趋势 stats.mean = mean(data); stats.median = median(data); stats.trimmed_mean_10 = trimmean(data, 10); % 离散程度 stats.std = std(data); stats.iqr = iqr(data); stats.range = range(data); stats.cv = stats.std / stats.mean; % 分布形状 stats.skewness = skewness(data); stats.kurtosis = kurtosis(data); % 输出 fprintf('\n【中心趋势】\n'); fprintf(' 算术均值: %.4f\n', stats.mean); fprintf(' 中位数: %.4f\n', stats.median); fprintf(' 10%%截尾均值: %.4f\n', stats.trimmed_mean_10); fprintf('\n【离散程度】\n'); fprintf(' 标准差: %.4f\n', stats.std); fprintf(' 四分位距(IQR): %.4f\n', stats.iqr); fprintf(' 极差: %.4f\n', stats.range); fprintf(' 变异系数(CV): %.4f\n', stats.cv); fprintf('\n【分布形状】\n'); fprintf(' 偏度: %.4f', stats.skewness); if abs(stats.skewness) < 0.5 fprintf(' (近似对称)\n'); elseif stats.skewness > 0.5 fprintf(' (正偏/右偏)\n'); else fprintf(' (负偏/左偏)\n'); end fprintf(' 超额峰度: %.4f', stats.kurtosis); if abs(stats.kurtosis) < 0.5 fprintf(' (近似正态尾)\n'); elseif stats.kurtosis > 0.5 fprintf(' (尖峰厚尾)\n'); else fprintf(' (平峰薄尾)\n'); end % 异常值检测(基于IQR) Q1 = quantile(data, 0.25); Q3 = quantile(data, 0.75); IQR = Q3 - Q1; lower_bound = Q1 - 1.5 * IQR; upper_bound = Q3 + 1.5 * IQR; outliers = data(data < lower_bound | data > upper_bound); fprintf('\n【异常值筛查(基于IQR法则)】\n'); fprintf(' 正常值范围: [%.4f, %.4f]\n', lower_bound, upper_bound); fprintf(' 疑似异常值数量: %d\n', length(outliers)); if ~isempty(outliers) fprintf(' 异常值列表: '); fprintf('%.4f ', outliers); fprintf('\n'); end fprintf('\n【图形诊断建议】\n'); fprintf(' 1. 绘制 histogram(data) 或 ksdensity(data) 查看分布形态。\n'); fprintf(' 2. 绘制 boxplot(data) 可视化异常值。\n'); fprintf(' 3. 绘制 qqplot(data) 检验正态性假设。\n'); fprintf('==================================================\n'); end % 使用函数 generate_descriptive_report(temp_data, '原始温度数据'); generate_descriptive_report(clean_data, '清洗后温度数据');通过这样一个完整的流程,你不仅得到了几个干巴巴的数字,更完成了一次从数据诊断、问题定位到清洗验证的完整分析。记住,均值、标准差、偏度和峰度这四个指标是一个强大的组合拳。它们是你探索数据未知领域的第一把钥匙,能帮你快速建立直觉,发现潜在问题,并为后续更复杂的建模分析奠定坚实的基础。在MATLAB中熟练运用它们,结合可视化进行交叉验证,你的数据分析之旅就有了一个可靠而稳健的起点。