简介:这份资源面向需要在多属性决策分析中客观确定指标权重的Matlab用户,提供CRITIC赋权法的完整实现代码,适合具备一定Matlab基础、从事评价建模或科研计算的学习者。压缩包共1个文件,为单个.m脚本,体积约586B,代码结构围绕数据预处理、属性归一化、一致性指标、相关性指标、强度指标与权重计算等环节组织,并包含对象打分与示例数据生成部分,便于替换为实际问题数据后直接运行。已有1658人学习下载,说明该方法在项目选择、供应商评估、产品设计等场景中具有较稳定的参考需求。读者可据此理解CRITIC准则的计算流程,掌握从决策矩阵到权重与综合评分的完整实现思路,并在此基础上扩展可视化或与其他赋权方法组合使用。
1. 从一张“权重表”说起:CRITIC赋权法到底解决什么问题
做综合评价时,最容易被忽略的一步不是算得分,而是定权重。指标一多,主观打分就变成玄学:谁嗓门大谁权重高,换个人评一遍结果全变。CRITIC赋权法就是来治这个病的——它同时看两件事,一是指标自身的波动大小(对比强度,用标准差衡量),二是指标之间的冲突程度(用相关系数衡量)。波动大、又跟别人不重复的指标,权重自然高。这份 Matlab 实现把整套流程封装成可直接调用的脚本,输入一个数据矩阵,输出权重向量和综合得分,适合做课程设计、论文复现、多指标评价原型的开发者直接拿去改。下面按“能干什么、怎么跑、怎么改”拆开讲。
2. 项目结构与技术选型:一个纯 Matlab 脚本工程长什么样
2.1 目录组织与文件职责
这类赋权法实现通常不依赖任何工具箱,纯基础 Matlab 语法就能跑,所以工程结构很轻。拿到源码包后,先别急着点运行,按下面的职责划分过一遍文件,能省掉后面一半的排查时间。
| 文件/目录 | 职责 | 是否需改动 |
|---|---|---|
main.m | 入口脚本,读数据、调函数、出结果 | 改数据路径 |
critic_weight.m | 核心赋权函数,输入矩阵输出权重 | 一般不动 |
normalize_data.m | 指标正向化与归一化 | 按指标类型改 |
data.xlsx/data.mat | 原始指标数据 | 换成自己的 |
result/ | 权重表、得分排序输出 | 自动生成 |
主脚本负责“串流程”,核心函数负责“算数学”,归一化单独拆出来是因为不同指标方向不一样——效益型越大越好,成本型越小越好,混在一起算标准差会翻车。这种拆法看着朴素,但二次开发时最舒服:换数据只动main.m,换归一化策略只动normalize_data.m,核心算法基本不用碰。
2.2 为什么选 CRITIC 而不是熵权法
很多人第一反应是熵权法,毕竟名气大。但熵权法只看指标内部的离散程度,完全不考虑指标之间的相关性。举个典型场景:你选了“GDP”和“人均GDP”两个指标,它们高度相关,信息其实是重复的,熵权法照样给两个都分高权重,最后等于同一个维度被算了两次。CRITIC 的相关系数项就是专门压这种重复的——相关性越高,冲突性越低,权重被拉下来。
用公式说清楚,第 j 个指标的信息量:
% C_j = sigma_j * sum(1 - r_ij), i = 1..n, i ~= j % sigma_j : 第 j 列的标准差(对比强度) % r_ij : 指标 i 与 j 的相关系数(冲突性) % 1 - r_ij 越大,说明两指标越不重复,该指标信息量越高这里有个细节容易写错:求和时要把i == j那一项排除,因为自己跟自己的相关系数是 1,1 - 1 = 0,留着不影响结果但逻辑上不干净;更常见的错误是忘了取绝对值,负相关其实也是“有信息”,1 - r在 r 为负时会大于 1,反而放大权重,这符合 CRITIC 的原意,但如果你先对相关系数取了绝对值再算,结果会偏。我一般保留原始相关系数直接算,跟主流文献口径一致。
2.3 运行环境与最小可跑示例
环境要求很低:Matlab R2016b 及以上即可,不需要 Statistics Toolbox(标准差和相关系数用基础函数std、corrcoef就够)。跑通的最小步骤如下。
第一步,把数据整理成一个数值矩阵,行是样本、列是指标,存成data.xlsx放在工程根目录。第二步,确认每个指标的方向,在main.m顶部维护一个方向向量。
% main.m 片段:数据加载与方向配置 raw = xlsread('data.xlsx'); % 读原始数据,行=样本,列=指标 direction = [1 1 -1 1 -1]; % 1=效益型(越大越好), -1=成本型(越小越好) X = normalize_data(raw, direction);% 正向化 + 归一化 w = critic_weight(X); % 核心:算权重 score = X * w; % 加权求和得综合得分 [~, idx] = sort(score, 'descend'); % 降序排名 disp(table((1:numel(idx))', score(idx), 'VariableNames', {'Rank','Score'}));xlsread在新版 Matlab 里会提示推荐readmatrix,功能一样,老版本没有readmatrix就继续用xlsread。direction的长度必须等于指标列数,这是最常见的报错来源——少写一个-1,成本型指标没被翻转,权重和排名全歪。score = X * w这一步是线性加权,如果你的评价模型要求非线性聚合,这里就是改造点。
3. 核心算法拆解:从原始矩阵到权重向量的四步链路
3.1 第一步:指标正向化与归一化
原始数据里量纲五花八门,有的指标是百分比,有的是绝对数值,直接算标准差没有可比性。所以第一步必须统一。效益型指标(越大越好)和成本型指标(越小越好)处理方式不同,区间型指标还要单独处理,这里先覆盖前两类,够绝大多数场景用。
function Xn = normalize_data(X, direction) % X: 原始矩阵 m×n;direction: 1×n,1 效益型,-1 成本型 [m, n] = size(X); Xn = zeros(m, n); for j = 1:n col = X(:, j); if direction(j) == 1 % 效益型:极差归一化,(x - min) / (max - min) Xn(:, j) = (col - min(col)) / (max(col) - min(col)); else % 成本型:翻转,(max - x) / (max - min) Xn(:, j) = (max(col) - col) / (max(col) - min(col)); end end end逻辑说明:极差归一化把每列压到[0,1],成本型用max - x翻转方向,翻转后所有指标都变成“越大越好”。参数上要注意max(col) - min(col)不能为 0,如果某列所有样本值完全相同,这里会除零得到NaN,后面标准差全是NaN,权重直接崩。稳妥做法是加一行判断,遇到常数列直接跳过或给零权重。
提示:如果数据里有负值或极端离群点,极差归一化会被离群点主导,这时可以换成 z-score 标准化,但要注意 z-score 后可能出现负值,标准差计算不受影响,加权求和时得留意符号含义。
3.2 第二步:对比强度——标准差的计算口径
归一化之后,每个指标的波动程度就用标准差衡量。这里有个口径选择:用样本标准差(除以n-1)还是总体标准差(除以n)。Matlab 的std默认是样本标准差,std(X, 1)才是总体标准差。CRITIC 文献里两种都有,差别在小样本时比较明显。
% 对比强度:按列求标准差,默认样本标准差(n-1) sigma = std(Xn, 0, 1); % 0 = 样本标准差, 1 = 总体标准差 % 若样本量很小(<10),建议统一用总体标准差,避免权重被放大std(Xn, 0, 1)三个参数分别是:输入矩阵、权重标志(0 表示按 n-1 归一)、维度(1 表示按列算)。如果你的样本只有五六个,样本标准差会让波动被高估,权重分布更极端,这时候换成std(Xn, 1, 1)更稳。这个细节论文里经常一笔带过,但复现时结果对不上,八成就是这里口径不一致。
3.3 第三步:冲突性——相关系数矩阵与信息量合成
冲突性是 CRITIC 区别于熵权法的灵魂。先算指标间的相关系数矩阵,再对每个指标求sum(1 - r_ij),最后乘上标准差得到信息量C_j,归一化就是权重。
function w = critic_weight(Xn) [~, n] = size(Xn); sigma = std(Xn, 0, 1); % 1×n 对比强度 R = corrcoef(Xn); % n×n 相关系数矩阵 conflict = zeros(1, n); for j = 1:n % 排除自身,累加 (1 - r_ij) others = [1:j-1, j+1:n]; conflict(j) = sum(1 - R(j, others)); end C = sigma .* conflict; % 信息量 w = C / sum(C); % 归一化为权重 end逐段看:corrcoef(Xn)返回对称矩阵,对角线上是自己跟自己的相关系数 1。others用索引拼接排掉第 j 个,避免把1 - 1 = 0算进去(虽然不影响和,但语义清楚)。C = sigma .* conflict是逐元素相乘,得到每个指标的信息量。最后除以总和归一化,权重之和为 1。
参数上要盯住两点:一是corrcoef对常数列会返回NaN,所以 3.1 里的常数列判断必须做;二是当指标数n = 1时,others为空,conflict为 0,权重算不出来——单指标场景没有赋权意义,代码里加个n < 2的断言更友好。
3.4 第四步:综合得分与排序输出
权重拿到后,综合得分就是归一化矩阵乘权重向量。这一步简单,但输出格式值得花点心思,方便直接贴进报告。
score = Xn * w'; % m×1 综合得分 [sortScore, idx] = sort(score, 'descend'); resultTable = table((1:numel(idx))', idx, sortScore, ... 'VariableNames', {'Rank','SampleIndex','Score'}); writetable(resultTable, fullfile('result','ranking.csv')); fprintf('权重向量: %s\n', mat2str(round(w, 4)));Xn * w'里w是行向量,转置后做矩阵乘,得到每个样本的加权得分。sort的'descend'保证第一名在前,idx保留原始样本序号,方便回查。writetable输出 CSV,路径用fullfile拼,跨平台不会因为斜杠翻车。round(w, 4)只是打印好看,实际权重别截断,后面还要用。
4. 避坑与排查:五个真实会踩的坑
4.1 权重全是 NaN
现象:跑完w全是NaN,得分排序无意义。原因:某列数据全相同,max - min = 0导致归一化除零,或者corrcoef遇到常数列返回NaN并污染整行。解决:在normalize_data里加if max(col) == min(col)分支,给该列全零或直接剔除该指标,并在日志里打印被剔除的列号。
4.2 排名和参考文献对不上
现象:同样的数据,自己算的排名和论文里的不一致。原因:九成是归一化口径不同——论文可能用了 z-score 或向量归一化,而你用了极差归一化;也可能是标准差用了总体而非样本。解决:先确认论文的标准化方法,再对齐std的第二个参数,最后核对相关系数是取原始值还是绝对值。
4.3 成本型指标方向搞反
现象:某个“越小越好”的指标(比如污染排放)权重异常高,排名明显不合理。原因:direction向量里对应位置写成了1,成本型没被翻转,归一化后数值大的反而得分高。解决:把direction和指标名做成一张对照表放在脚本顶部,逐列核对,别凭记忆填。
4.4 相关系数矩阵报错维度不匹配
现象:corrcoef报错或返回空。原因:输入矩阵里有NaN或Inf,常见于原始数据缺失值没处理。解决:加载数据后先跑一遍sum(isnan(raw(:)))和sum(isinf(raw(:))),缺失值用列均值填补或整行剔除,别让脏数据流进算法。
4.5 新版 Matlab 读 Excel 报错
现象:xlsread提示不推荐或直接失败。原因:新版 Matlab 对xlsread支持收紧,且需要 Excel 组件。解决:换成readmatrix('data.xlsx'),返回纯数值矩阵,不依赖 Excel 安装;老版本没有这个函数就退回xlsread,或者把数据另存为.mat用load读,最省事。
5. 二次开发:把赋权脚本改成可复用的评价模块
5.1 从脚本到函数的封装思路
原始工程是脚本式写法,跑一次改一次数据,复用性差。二次开发第一步是把main.m里的流程收进一个函数,输入数据矩阵和方向向量,输出权重、得分、排名三件套,这样别的项目直接addpath调用就行。
function [w, score, rank] = evaluate_critic(X, direction, varargin) % 可复用评价入口 p = inputParser; addParameter(p, 'StdFlag', 0); % 0样本标准差, 1总体标准差 addParameter(p, 'NormMethod', 'range'); % range / zscore parse(p, varargin{:}); Xn = normalize_data(X, direction, p.Results.NormMethod); w = critic_weight(Xn, p.Results.StdFlag); score = Xn * w'; [~, rank] = sort(score, 'descend'); end用inputParser把标准差口径和归一化方法做成可选参数,默认值保持和原脚本一致,老调用不受影响。varargin的好处是不改函数签名就能加新选项,后面想接熵权法做对比,再加一个'Method'参数即可。这种封装方式在 Matlab 里很常见,比全局变量干净得多。
5.2 加一个权重敏感性检查
CRITIC 权重对数据扰动敏感,样本一换权重可能大变。上线前我习惯加一段扰动检查:对每个指标加 5% 随机噪声,重算权重,看排序是否稳定。
base_w = critic_weight(Xn); perturb = zeros(100, numel(base_w)); for k = 1:100 Xp = Xn .* (1 + 0.05 * randn(size(Xn))); perturb(k, :) = critic_weight(Xp); end w_std = std(perturb, 0, 1); % 每个指标权重的波动w_std大的指标说明权重不稳,报告里要注明“该指标权重对数据敏感,结论需谨慎”。这一步不改变算法,但能提前发现“换个样本排名就翻”的隐患,比事后被质疑强。
5.3 和熵权法做交叉验证
单一赋权法容易被挑刺,稳妥做法是同时跑熵权法和 CRITIC,看两者权重排序的 Spearman 相关系数。如果两种方法给出的权重高度一致,结论可信度就高;差异大的指标单独拎出来分析原因。实现上把critic_weight旁边加一个entropy_weight,共用归一化后的矩阵,主流程里并行调用,输出一张对比表。这个改造量不大,但对报告的说服力提升明显。
5.4 一个我踩过的坑
早些年我把这套脚本直接塞进一个批量评价流程,数据是循环读进来的,结果某批数据里有一列全零,corrcoef返回NaN,整批权重全废,但脚本没报错,静默输出了NaN表格,直到人工核对才发现。从那以后我每次封装评价函数,都在入口强制加一道数据体检:assert(all(isfinite(X(:)))),再补一个常数列检测,宁可提前报错也不让脏数据静默流过。这个习惯帮我省了不止一次返工。希望这套拆解能帮你把 CRITIC 赋权快速跑起来,也少走我走过的弯路。
本文还有配套的精品资源,点击获取