CRITIC法详解:从原理到Python实现,科学计算指标权重
上个月做供应商评估的时候又双叒被“权重从哪来”难住了6个一级指标、12家备选供应商数据都是现成的可请专家打分凑不齐人就算凑齐了十个人打出来的权重也未必统一业务方还会追问“你凭什么给这个指标0.2”。后来我把之前用过几次的CRITIC法捡起来算了一版指标权重再用几张图把计算依据和结果一起展示出来管理层十分钟就看懂了。这篇文章就是那次完整复盘把CRITIC法的原理、手算过程、Python实现和绘图方法串起来讲一遍。CRITIC法适合那种“有客观数据、不方便做主观打分”的评价场景比如供应商评分、城市发展水平评估、招聘候选人筛选、实验室能力考核给指标算出一个可解释、可复现的客观权重。想直接拿代码的可以跳到第3节想弄懂底层逻辑的按顺序往下读就行。1. 为什么选CRITIC多指标评价里的权重困境1.1 主观赋权的痛客观赋权的快做过多指标综合评价的人都知道权重是整个评价模型里最敏感、也最容易吵架的部分。最传统的方式是专家打分或层次分析法请几个有经验的人坐在一起两两比较重要性最后算出一组权重。这套思路业务上认但实际操作很痛苦一是专家时间难约十个人能凑齐五个就不错二是主观判断不一致同一对指标有人觉得A比B重要两倍有人觉得反过来三是结果很难被复检半年后再请同一批专家打分权重大概率会漂移。当评价对象很多、指标很多时主观赋权的时间成本、沟通成本都会被放大。客观赋权法解决的就是这个痛点完全从数据本身出发不需要任何人拍脑袋。数据里哪个指标携带的信息多哪个指标在评价对象之间区分度大它就值得更大的权重。常见客观赋权法里除了CRITIC还有熵权法、变异系数法、主成分分析赋权等。CRITIC这个名字来自Criteria Importance Through Intercriteria Correlation直译就是“通过指标间相关性判断指标重要性”它的一大好处是把“指标自身的波动”和“指标之间的信息重复度”都纳入计算逻辑上比只看单维度的熵权法、变异系数法更完整。1.2 CRITIC的核心逻辑一个指标的信息量要看“反差冲突”CRITIC的基本思想可以用一句话概括一个指标越“能打”权重就应该越高。“能打”体现在两个维度。第一个维度叫对比强度通常用标准差表示。标准差大说明不同评价对象在这个指标上的差异大该指标能把人区分开如果大家得分都差不多比如都在95到96分之间那这个指标对最终排名的贡献自然很小。你可以把它理解成一个筛子筛孔越小能筛出来的差异越少信息量越低。第二个维度叫冲突性用的是指标之间的相关系数。这个思路很巧妙如果两个指标高度正相关比如“营业收入”和“营业成本”基本同步变化那么它们在评价时提供的信息严重重复等于一个人说了两遍相同的话第二遍就没多少新意反过来如果两个指标相关性很弱或者干脆负相关那每个指标都在贡献独立的信息各自的权重就该往上走。CRITIC把冲突性定义为指标与其余所有指标相关系数的距离之和相关系数越低冲突性越大指标越不可替代。最后把对比强度和冲突性乘起来得到每个指标的综合信息量再做归一化就是最终权重。这个乘法的含义很直观既要在单个指标上波动明显又要和其他指标不重复两者缺一不可。1.3 和熵权法、变异系数法放一起比一比我一直觉得选方法之前一定要知道其他方法在干什么。熵权法用信息熵来衡量指标的离散程度熵越小说明指标携带的信息量越大权重越高变异系数法则直接拿标准差除以均值用相对波动大小来定权。这两个方法都只关注“指标自身的变化”完全没管指标之间的相关性。方法核心依据是否考虑指标间相关性适用场景局限变异系数法标准差/均值否指标数量少、独立性较好忽略信息重叠熵权法信息熵否数据离散度差异明显对指标分布敏感忽略相关结构CRITIC法标准差相关系数是指标间可能存在关联的综合评价对相关系数稳定性有要求举例来说如果指标体系里有两个强相关的指标熵权法和变异系数法会各给它们一个不小的权重等于把“同一件事”算了两次综合得分会被带偏。CRITIC能通过冲突性把冗余信息的权重压下来所以在指标间关联度较高的场景下我一般优先选CRITIC。当然它不是万能的后面会讲到它对相关系数很敏感样本太少时容易出问题。2. CRITIC法计算全流程每个中间量都是什么含义2.1 指标正向化先消除“方向”干扰CRITIC计算权重之前必须先统一指标方向。这个步骤很多人会跳过但跳过的后果很严重。我们平时遇到的指标大约分三类正向指标越大越好比如收入、得分、覆盖率负向指标越小越好比如成本、能耗、投诉率还有适度指标比如温度、pH值落在某个区间最好。如果不做方向统一直接拿原始数据算相关系数就会出现一种尴尬情况某个负向指标和另一个正向指标在业务上是此消彼长的相关系数为负但这个负相关可能只是“方向相反”造成的并不代表两个指标真的在互相补充。方向统一之后所有指标都变成“越大越好”相关系数的符号才更有解释意义。负向指标的常用正向化变换是取补数x (max(x) - x) / (max(x) - min(x))。这样最小值变成0最大值变成1方向和正向指标一致了。适度指标的正向化稍微麻烦常见做法是先构造离最优值的距离比如x 1 / (1 |x - x_opt|)距离越小得分越高。如果一套指标体系里方向很杂最好先写一个映射表明确每个指标的类型再批量处理别在代码里一个个改容易漏。2.2 极差标准化把数据压到同一个尺度方向统一之后还要做无量纲化。CRITIC的标准差计算要求指标在同一量纲下进行否则“人均GDP”这种数值大的指标标准差天然巨大“万元产值能耗”这种数值小的指标标准差天然很小最后权重会被量纲带偏完全跑偏。极差标准化是最常用的方案正向指标用(x - min) / (max - min)把数据映射到[0, 1]区间负向指标在2.1步已经通过取补数变成正向同样用这个公式。这一步之后所有指标的最小值是0、最大值是1量纲影响消除了而且数据的相对排序不变。有个细节容易被忽略标准化用的是整个评价对象集合的min和max不是单个对象。比如我们有12家供应商的数据计算某家供应商的标准化值时分母必须是这12家供应商里该指标的最大最小值不能只拿自己的历史数据算否则不同批次的标准化结果不可比。2.3 对比强度标准差到底在表达什么在CRITIC法里对比强度一般取标准化后数据的标准差。标准差大表示指标在各个评价对象之间差异显著辨识度高能给最终排名提供更多信息标准差小表示大家在这个指标上都差不多拉不开差距权重就该小。这里必须提醒一个容易踩的坑标准差有总体标准差和样本标准差之分。pandas里的std()默认是样本标准差分母是n-1而评价对象通常就是我们的全量集合不是从总体里随机抽的样本所以理论上用总体标准差更符合CRITIC的原始设定也就是ddof0。两种算法在样本量大的时候差别很小但在只有4个、5个评价对象的时候权重结果会出现肉眼可见的差异。章节4里我会单独演示这个差异有多大。2.4 冲突性相关系数如何变成“不重复程度”冲突性是CRITIC的第二块拼图。先计算标准化后指标间的皮尔逊相关系数矩阵得到两两指标的相关程度然后用公式把相关系数转成冲突性。对于某一个指标j冲突性R_j Σ(1 - r_jk)其中k是除j以外的所有指标r_jk是j和k的相关系数。相关系数越接近11 - r_jk越小说明两个指标信息高度重叠冲突性低相关系数越接近0或者为负1 - r_jk越大冲突性高。把指标j和所有其他指标的冲突值加总就是它在这个指标体系里的总体不可替代程度。这个公式有个值得商榷的地方1 - r在遇到负相关时会变得很大比如r-0.8时冲突值是1.8而r0.3时只有0.7。也就是说两个指标越是反向变化冲突性越高。这在“评价信息互补”的语境下是合理的但有些研究者会觉得负相关其实也是一种强关联既然关系强信息冗余度也应该高所以主张用1 - |r|来算。两种思路都有道理后面会说我的处理习惯。2.5 信息量合成与权重归一化拿到对比强度σ_j和冲突性R_j之后合成信息量C_j σ_j × R_j。这个乘积就是指标j在评价体系里的总信息量然后做归一化w_j C_j / ΣC_j得到最终权重。整个流程跑下来你会发现CRITIC其实没有特别玄学的数学推导每一步都有业务解释标准差大说明指标“有区分度”相关系数低说明指标“不重复”两者都满足的指标自然是评价模型里最重要的。这种可解释性在向领导汇报时非常有用比一句“算法算出来的”强得多。2.6 手算示例4个城市、3个指标一步步算给你看光讲公式还是抽象我拿一个极简例子手算一遍。假设评价4个城市的发展水平选3个指标人均GDP万元正向、空气优良天数比率%正向、单位GDP能耗吨标准煤/万元负向。原始数据如下城市人均GDP优良天数比率单位GDP能耗城市112.878.50.92城市29.685.30.65城市315.272.11.10城市411.590.00.78先正向化并做极差标准化单位GDP能耗取补数处理得到城市人均GDP优良天数比率单位GDP能耗正向化城市10.5710.3580.400城市20.0000.7371.000城市31.0000.0000.000城市40.3391.0000.711然后计算每个指标的总体标准差以及两两之间的相关系数。相关系数矩阵大概是人均GDP和优良天数比率约-0.83人均GDP和单位GDP能耗约-0.99优良天数比率和单位GDP能耗约0.86。也就是说人均GDP和另外两个指标都是明显负相关冲突性会非常高。指标对比强度标准差冲突性信息量C权重人均GDP0.3643.831.390.47优良天数比率0.3791.970.750.26单位GDP能耗0.3712.130.790.27这个例子很典型人均GDP虽然标准差不是最大的但和另外两个指标都是强负相关冲突性远高于其他指标所以最后权重最大。如果我用的是一味强调“波动越大权越大”的变异系数法或熵权法三个指标权重会相对平均很难看出人均GDP的独特价值。这就是CRITIC法在多指标评价里最值得用的地方。3. Python实现权重计算与四张图一次搞定3.1 最简核心代码20行搞定权重直接用pandas和numpy就行不需要额外装复杂依赖。我把上面手算的例子写成完整代码注释写在关键行。import pandas as pd import numpy as np raw pd.DataFrame({ 人均GDP: [12.8, 9.6, 15.2, 11.5], 优良天数比率: [78.5, 85.3, 72.1, 90.0], 单位GDP能耗: [0.92, 0.65, 1.10, 0.78] }) def minmax_positive(series): return (series - series.min()) / (series.max() - series.min()) def minmax_negative(series): # 负向指标先取补数结果等于正向标准化 return (series.max() - series) / (series.max() - series.min()) df pd.DataFrame({ 人均GDP: minmax_positive(raw[人均GDP]), 优良天数比率: minmax_positive(raw[优良天数比率]), 单位GDP能耗: minmax_negative(raw[单位GDP能耗]) }) std df.std(axis0, ddof0) # 对比强度用总体标准差 corr df.corr() # 标准化后的相关系数矩阵 conflict np.sum(1 - corr, axis1) # 冲突性 info std * conflict # 综合信息量 weights info / info.sum() # 归一化权重 print(标准化数据) print(df.round(3)) print(对比强度) print(std.round(3)) print(相关系数矩阵) print(corr.round(3)) print(冲突性) print(conflict.round(3)) print(权重) print(weights.round(4))这段代码最大的优点是把每个中间量都单独拆出来打印了。我强烈建议你在自己的数据上也这样跑一遍先检查中间量是否符合直觉再决定要不要用结果千万别直接跳到权重那一步。3.2 权重条形图一眼看出谁重要权重算完第一张图一定画水平条形图。竖向条形图在指标名比较长时会互相挤压水平条形图最适合展示排名信息。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False weights_sorted weights.sort_values() fig, ax plt.subplots(figsize(8, 5)) colors plt.cm.Blues(np.linspace(0.4, 0.9, len(weights_sorted))) bars ax.barh(weights_sorted.index, weights_sorted.values, colorcolors) ax.set_xlabel(权重) ax.set_title(CRITIC法计算的指标权重) for bar, value in zip(bars, weights_sorted.values): ax.text(bar.get_width() 0.01, bar.get_y() bar.get_height() / 2, f{value:.3f}, vacenter) plt.tight_layout() plt.show()看这张图第一个要确认的点是“权重排序是否符合业务直觉”。如果某个公认不重要的指标权重特别高通常不是方法错了而是数据本身有问题比如该指标存在明显异常值或者标准化逻辑写错了。3.3 相关系数热力图解释权重为什么这么分权重图只能告诉大家结果不能解释原因。CRITIC的权重有一半来自相关性所以必须把相关系数矩阵画出来汇报时才好说清楚。比如前面例子里人均GDP权重最高就是因为它和优良天数比率、单位GDP能耗都是负相关热力图往那一放结论不言自明。import seaborn as sns fig, ax plt.subplots(figsize(6, 5)) sns.heatmap(corr, annotTrue, cmapcoolwarm, center0, vmin-1, vmax1, fmt.2f, xticklabelscorr.columns, yticklabelscorr.columns, axax) ax.set_title(标准化指标间的相关系数) plt.tight_layout() plt.show()热力图的配色我用的是coolwarm以0为中心正相关红色、负相关蓝色。vmin-1, vmax1必须写死否则seaborn会按当前数据的最值自动缩放色带比如实际相关系数范围是-0.9到0.8色带就会把-0.9当成最深红误导看图的人。3.4 对比强度-冲突性气泡图找到权重背后的驱动因素权重是综合信息量归一化后的结果可它不是中间变量没法直观看出一个指标权重高到底是因为标准差大还是因为相关性低。气泡图能解决这个问题横轴是对比强度纵轴是冲突性气泡大小是权重。哪个维度贡献大一看便知。fig, ax plt.subplots(figsize(8, 6)) scatter ax.scatter(std.values, conflict.values, sweights.values * 800, alpha0.6, edgecolorsw) for name, x, y in zip(std.index, std.values, conflict.values): ax.annotate(name, (x, y), xytext(6, 4), textcoordsoffset points) ax.set_xlabel(对比强度标准差) ax.set_ylabel(冲突性) ax.set_title(对比强度与冲突性气泡图气泡大小权重) plt.tight_layout() plt.show()如果某指标在右上角说明它同时具备“区分度大”和“信息独立”两种属性是体系里的核心指标如果某指标在左上方说明冲突性高但自身波动小权重大部分是靠和其他指标的低相关撑起来的这种指标要留意后续数据更新后权重是否稳定。3.5 雷达图把三个维度压缩到一张图最后这张雷达图更适合多指标场景能把对比强度、冲突性、权重三个维度都放到一张图上对比。因为对比强度、冲突性和权重不在同一量纲先做一次min-max归一化再画雷达图。from math import pi def normalize(series): return (series - series.min()) / (series.max() - series.min()) std_n normalize(std) conflict_n normalize(conflict) weights_n normalize(weights) labels df.columns.tolist() angles [i / len(labels) * 2 * pi for i in range(len(labels))] angles angles[:1] fig, ax plt.subplots(figsize(7, 7), subplot_kw{projection: polar}) for values, label in [(std_n.values, 对比强度), (conflict_n.values, 冲突性), (weights_n.values, 权重)]: data list(values) [values[0]] ax.plot(angles, data, labellabel) ax.fill(angles, data, alpha0.15) ax.set_xticks(angles[:-1]) ax.set_xticklabels(labels) ax.set_title(CRITIC三维特征雷达图) ax.legend(locupper right) plt.show()雷达图的技巧是三个维度分开归一化不然冲突性动辄接近2、对比强度只有0.36画出来对比强度那条线会糊成一条直线没有观察价值。归一化之后注意图里只能看形状趋势不能读绝对数值。4. 常见问题与避坑记录4.1 负相关系数会让权重扭曲吗这是CRITIC法被问得最多的一个问题。原始公式1 - r在负相关时会放大冲突值比如r-0.9时冲突贡献1.9而r0.1时只有0.9这等于把“反向变化”看得比“弱相关”还要珍稀。如果你的指标都是正向化之后的负相关往往意味着指标之间存在某种此消彼长的业务关系比如“成本控制”和“服务质量”天然就是负相关放大冲突性是合理的。但如果你不想让负相关被过度放大可以用1 - |r|来计算冲突性。这时正相关0.9和负相关-0.9的冲突贡献都是0.1核心思想变成“只要是强线性相关信息冗余就大不管同向还是反向”。我自己的习惯是两种版本都算一遍看权重排序是否发生本质变化。如果排序很稳说明结果对相关系数处理方式不敏感可以直接用如果排序剧烈波动说明指标体系里存在强相关的核心变量这时要回到业务层面重新审视指标设计而不是纠结公式。4.2 标准差用总体还是样本结果差多少前面提过pandas的std()默认ddof1是样本标准差而CRITIC原始文献用的是总体标准差。评价对象一般是全量数据不是抽样所以用ddof0更讲得通。拿前面的例子实际算一下样本标准差下三个人均GDP的标准差是0.420总体标准差是0.364最终权重算出来差别很小人均GDP还是0.47左右因为所有指标的标准差都被同步放大了归一化之后影响被部分抵消。但这不是偷懒的理由。样本量只有四五个的时候两种标准差的差异会更明显而且一旦某个指标恰好有一两个极端值标准差差异会被放大。我建议固定写ddof0并且注释里写明这是总体标准差后面复算的人不会迷惑。4.3 标准化方法不同权重会翻车吗极差标准化是CRITIC里最常用的预处理但它对异常值很敏感原始数据里突然冒出一个极大值会直接把其他正常值压到0.2甚至更低标准差和相关系数都会被带跑。如果你的数据存在明显离群点优先考虑截断处理比如把超过99%分位数的值截断到99%分位数的水平再做极差标准化或者改用z-score标准化让数据围绕0波动离群值影响会缓和一些。注意z-score标准化的结果存在负值计算冲突性时不影响但解释上不如[0,1]区间直观。标准化的选择会改变相关系数矩阵进而改变权重所以写分析报告时一定要写明预处理方案否则结果不可复现。4.4 样本量太小相关系数不可靠CRITIC对相关系数的依赖很高而皮尔逊相关系数在小样本下波动剧烈。我个人的底线是评价对象数量至少大于指标数量如果指标有6个评价对象最好在30个以上只有十几个样本时算出来的相关矩阵方差很大今天权重和明天权重可能完全两样。如果样本量实在不够可以做稳定性验证用bootstrap抽样每次从样本里重抽80%的对象计算权重重复200次看每个指标权重的分布范围。如果某个指标权重的置信区间横跨很多个位次那它基本不能用于决策需要在报告里明确说明。4.5 中文绘图的三个老坑中文字体问题是中文绘图里最常见的翻车现场。第一不设置中文字体图上全是方格解决办法是在导入matplotlib之后立刻加上plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei]。第二负号显示异常坐标轴上的负号会变成小方块必须设置plt.rcParams[axes.unicode_minus] False。第三字体家族名在不同系统里不一样Windows常见宋体黑体macOS上往往要换成Arial Unicode MS或PingFang SC建议写成列表matplotlib会自动匹配第一个可用的字体。另外提醒一句如果是在Jupyter里用seaborn画热力图seaborn有自己的字体设置最好在画图前也执行一遍同样的设置别只设在matplotlib上。4.6 权重结果怎么交付才不是一堆数字最后这条是我自己的交付习惯。纯权重表格在业务侧很容易被挑战我会额外做三件事第一把权重图和气泡图拼到一张A4报告页里旁边标注每个指标的方向和业务含义第二用权重对原始数据加权计算综合得分把排名结果和只用熵权法、变异系数法得到的排名做个对比差异大的地方给出业务解释第三保留全部中间计算表包括标准化数据、标准差、相关系数矩阵、冲突性、信息量随时准备应对各种“为什么这个指标权重这么高”的追问。CRITIC的好处就在这里每一步都有明确的业务解释不需要黑盒。我在实际项目中还发现一个特别好用的扩展算完权重后可以继续用k-means或者TOPSIS跑综合评价排名把CRITIC的权重作为输入这样权重计算和最终评价就串成了一条完整链路。只要前期数据清洗和标准化做得扎实后面出结论的速度会快非常多。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →