做指标权重计算的时候,很多人第一反应是熵权法。我也一样,直到有一次做企业综合评价,指标之间皮尔逊相关系数普遍在0.8以上,熵权法给出的权重排序和业务直觉对不上——它只看每个指标自己有多分散,完全没有处理指标之间重复信息的问题。那次之后,我把CRITIC法纳入常规工具箱,并在多个项目里用它计算指标权重。这篇内容就把我实际用Critic法计算指标权重并绘图展示的完整过程记录下来,公式、手算、Python代码、图表一套全带上,适合正在做评价指标体系、绩效评估、综合指数类任务的读者参考。
CRITIC法全称是Criteria Importance Through Intercriteria Correlation,核心逻辑很直白:一个指标的重要程度,由它自身的变异性乘上它与其他指标的冲突性决定。变异性用标准差衡量,冲突性用相关系数转换而来,两者相乘得到信息量,信息量占比就是权重。这个思路比熵权法多考虑了一层“指标间关系”,理解门槛不高,但落地时坑也不少,尤其是数据标准化和可视化细节。下面按我验证过的路径一步步来。
1. CRITIC在客观赋权里到底动了哪块奶酪:与熵权法、标准差法的差异
1.1 客观赋权的两路信息源:离散程度和指标间冲突
任何客观赋权方法,说到底都是从数据里挖掘“哪个指标更重要”的证据。数据能提供的有效证据只有两类:一类是指标内部的信息,也就是各个评价对象在这个指标上的差异有多大,差异越大,越能拉开差距,越值得给高权重;另一类是指标之间的信息,也就是这个指标和其他指标有没有重复,它是带来了新视角,还是在重复别人已经说过的话。
大多数客观赋权法只用了第一类。标准差法就是直接算每个指标的标准差或变异系数,谁波动大谁权重高;熵权法稍微换了个口径,用信息熵衡量离散程度,分布越“混乱”权重越高。它们对指标间的相关系数一概不关心。问题在于,指标体系里经常出现成对的高度相关指标,比如毛利率和净资产收益率、研发费用率和专利数,如果这些指标都被赋予较高权重,相当于把同一份信息翻倍统计。
CRITIC法把第二类信息也装了进去。它先算指标间的相关系数,再把相关系数转化成冲突性。指标之间相关性越强,冲突性越低——因为它们在说同一件事;相关性越弱甚至反向,冲突性越高——因为它们在从不同角度约束评价对象。这个“冲突性”和标准差相乘,才是最终的信息量。
1.2 三种客观赋权法的信息利用对比
拿一个具体例子来说明三种方法处理信息的差异。假设两个指标高度正相关,相关系数0.95,标准差也接近。标准差法会各给一半权重,熵权法也差不多,因为离散度相似。CRITIC呢?它的冲突性部分会压迫这两个指标的权重,因为0.95的相关系数意味着冲突贡献只有1-0.95=0.05,信息量几乎只靠标准差撑起来,权重自然就降下来了。
这个行为在实际评价中非常有意义。我用过一个包含12个指标的经营评价体系,里面两个指标本质都在测“赚钱效率”,高相关必然出现。熵权法算出它们合计权重接近三成,业务部门看到就质疑:“这不是重复打分吗?”换成CRITIC后,这个信息簇的总权重明显收缩,整体排名更符合大家的主观判断。
三种方法的差异可以归纳成下面这张表:
| 方法 | 主要信息源 | 是否考虑指标间相关 | 典型适用场景 |
|---|---|---|---|
| 标准差法 / 变异系数法 | 离散程度 | 否 | 指标相互独立或相关性较弱 |
| 熵权法 | 信息熵反映的离散程度 | 否 | 相关性低、分布形态差异明显 |
| CRITIC法 | 离散程度 + 指标间冲突 | 是 | 指标多、相关性结构复杂 |
1.3 适用场景判断:什么时候不用纠结,直接上CRITIC
我个人的选择习惯是:先花两分钟跑一个相关系数矩阵,看看有没有成群结队的高相关指标,只要有,哪怕只有一对相关系数超过0.85,就优先用CRITIC。因为这时候熵权法的“信息独立”假设已经不成立,用它对不懂统计的业务方解释权重会很费劲。
另外,CRITIC也非常适合指标数量较多的场景。指标数超过10个以后,指标间发生偶然高相关的概率急剧增加,不做冲突性处理的权重结果很容易“偏科”。反过来,如果指标之间相关性都低于0.3,CRITIC的结果和熵权法往往差异不大,那直接用熵权法更省事,毕竟熵权法的实现更普及,参考资料也多。
还有一种情况不建议用它:样本量太少。CRITIC依赖相关系数矩阵,而相关系数在小样本下非常不稳定,5个样本算出来的0.9和-0.9可能只是抽样噪声。这时要么老老实实加样本,要么用我后面会讲的Bootstrap方法给权重套一个区间,而不是拿一个精确到小数点的数字当真。
2. 手算5×4矩阵:把一个CRITIC周期从头推到尾
2.1 数据集设定:经营质量评价里的4个代表指标
原理说多了容易飘,还是上手算一遍最扎实。我设计了一个很小的案例,5家企业、4个指标,正好能覆盖正向和负向两种指标类型,足够复现CRITIC全过程,又不至于让人盯着表格头皮发麻。
表里四个指标分别是:毛利率、资产负债率、营业利润增长率、研发费用占比。其中资产负债率在经营质量评价里通常是负向指标,比率越高,财务风险越大,所以下面会单独处理。这五个企业的原始数据如下:
| 企业 | 毛利率(%) | 资产负债率(%) | 营业利润增长率(%) | 研发费用占比(%) |
|---|---|---|---|---|
| A | 22.5 | 58.0 | 8.2 | 4.1 |
| B | 18.9 | 45.0 | 12.5 | 3.2 |
| C | 30.1 | 62.0 | 5.6 | 5.8 |
| D | 15.2 | 39.5 | 15.2 | 2.9 |
| E | 25.8 | 50.5 | 10.7 | 4.6 |
2.2 极差标准化:正向和负向指标分别处理
CRITIC第一步是消除量纲,我这里用的是极差标准化,也叫Min-Max标准化。正向指标按常规方式处理:标准化值等于(当前值减最小值)除以(最大值减最小值),这样所有值落到0到1之间,越大越好。负向指标要反过来,用最大值减当前值再除以极差,让负向指标变成数值越大越好,方向统一了,后续综合打分才不会出乱子。
正向指标公式:z = (x - min) / (max - min)。
负向指标公式:z = (max - x) / (max - min)。
为什么不推荐用Z-score?因为CRITIC下一步要算标准差作为变异性指标,Z-score会把所有列的标准差强行变成1,变异性信息被抹掉,CRITIC就退化成“只看冲突性”的半残版本。极差标准化保持了各列之间相对变异大小的差异,这才是CRITIC需要的输入。
标准化后的矩阵长这样,注意资产负债率列已经完成方向转换:
| 企业 | 毛利率 | 资产负债率(转正) | 营业利润增长率 | 研发费用占比 |
|---|---|---|---|---|
| A | 0.490 | 0.178 | 0.271 | 0.414 |
| B | 0.248 | 0.756 | 0.719 | 0.103 |
| C | 1.000 | 0.000 | 0.000 | 1.000 |
| D | 0.000 | 1.000 | 1.000 | 0.000 |
| E | 0.711 | 0.511 | 0.531 | 0.586 |
2.3 标准差、相关系数与冲突性:两个“信息量”从哪来
先看变异性。对标准化矩阵的每一列求标准差,注意这里是总体标准差,也就是除以样本数n而不是n-1。算出来之后,四列的标准差分别是0.349、0.366、0.347、0.358。差别不算悬殊,说明这四个指标在离散程度上贡献接近,单靠离散度分不出高低。
再算指标间冲突性。冲突性定义是:某指标与其他所有指标相关系数之差的累计,也就是对每个指标计算(1 - 与其它指标的相关系数)的加和。相关系数矩阵我直接列出来,注意下半部分对称,省得看两遍:
| 指标 | 毛利率 | 资产负债率 | 营业利润增长率 | 研发费用占比 |
|---|---|---|---|---|
| 毛利率 | 1.000 | -0.877 | -0.903 | 0.986 |
| 资产负债率 | -0.877 | 1.000 | 0.994 | -0.882 |
| 营业利润增长率 | -0.903 | 0.994 | 1.000 | -0.907 |
| 研发费用占比 | 0.986 | -0.882 | -0.907 | 1.000 |
很多人会问:为什么负相关也算“冲突”?看公式就明白,1减去相关系数,不管正负相关都会产生正值:完全不相关时贡献1,完全正相关时贡献0,完全负相关时贡献2。正相关说明指标在讲同一件事,是重复信息;负相关说明指标从相反方向约束对象,互相牵制,同样构成冲突。这个细节特别容易被人误读成“只看绝对值”,实际操作中千万别改公式。
四个指标的冲突性计算如下:
- 毛利率:1.877 + 1.903 + 0.014 = 3.794
- 资产负债率:1.877 + 0.006 + 1.882 = 3.765
- 营业利润增长率:1.903 + 0.006 + 1.907 = 3.816
- 研发费用占比:0.014 + 1.882 + 1.907 = 3.803
看到没,研发费用占比和毛利率相关系数0.986,它们互相之间的冲突贡献只有0.014,这组指标几乎是重复信息。而毛利率和资产负债率是强负相关,冲突贡献就接近1.88,说明它们给评价对象施加的是两个方向的力量。
2.4 信息量合成与权重排序:为什么是乘法而不是加法
把标准差和冲突性相乘,就得到每个指标的信息量C。结果如下:
| 指标 | 标准差 | 冲突性 | 信息量C | 权重 |
|---|---|---|---|---|
| 毛利率 | 0.349 | 3.794 | 1.324 | 0.246 |
| 资产负债率 | 0.366 | 3.765 | 1.378 | 0.256 |
| 营业利润增长率 | 0.347 | 3.816 | 1.324 | 0.246 |
| 研发费用占比 | 0.358 | 3.803 | 1.361 | 0.253 |
权重就是每个指标的信息量除以总信息量。这里四个权重大致都在0.25附近,原因是这套教学数据里各个指标的变异和冲突结构比较均衡,这是刻意而为,方便大家看清计算全貌。真实项目里指标差异会大得多。
为什么合成用乘法而不是加法?因为乘法有“一票否决”的效果。一个指标如果标准差为0,说明所有对象在这个指标上没有差异,它对区分评价对象毫无贡献,信息量直接为0,权重就是0。如果一个指标和另一个指标完全正相关,冲突性贡献为0,那它在冲突面也没有存在价值。这种双通道校验比加法更符合直觉:一个指标必须有独立观点,也必须有自己的发声能力,两个条件缺一不可。
3. Python代码落地:把CRITIC封装成一个可复用函数
3.1 环境准备与数据构造
手算跑通之后,代码就是水到渠成的事。环境只需要pandas、numpy、matplotlib,后面画热力图还会用到seaborn,顺手装一下:
pip install pandas numpy matplotlib seaborn数据构造和前面的手算表格保持一致:
import pandas as pd df = pd.DataFrame({ '毛利率': [22.5, 18.9, 30.1, 15.2, 25.8], '资产负债率': [58.0, 45.0, 62.0, 39.5, 50.5], '营业利润增长率': [8.2, 12.5, 5.6, 15.2, 10.7], '研发费用占比': [4.1, 3.2, 5.8, 2.9, 4.6] }, index=['A', 'B', 'C', 'D', 'E'])建议先把数据类型检查一遍,确保全是数值型。我实际遇到过某列被读成字符串的情况,直接用info()看一眼dtypes就行。
3.2 标准化实现:向量化写法会省很多事
极差标准化涉及正向和负向两类指标,比全部正向指标稍微麻烦一点。最稳妥的写法是把两个方向分开处理,循环遍历每一列:
def minmax_normalize(df, positive_cols, negative_cols): data = df.copy() for col in positive_cols: min_val = data[col].min() max_val = data[col].max() data[col] = (data[col] - min_val) / (max_val - min_val) for col in negative_cols: min_val = data[col].min() max_val = data[col].max() data[col] = (max_val - data[col]) / (max_val - min_val) return data z_df = minmax_normalize( df, positive_cols=['毛利率', '营业利润增长率', '研发费用占比'], negative_cols=['资产负债率'] )这里有个细节:分母max减min理论上不能为0,也就是说一列数据如果全部相同,标准化会报除零错误。这其实是个信号——该指标没有区分度,后面算权重也应该是0,不如一开始就用代码把它剔掉。
除了循环,也可以用apply写向量化版本,但可读性不如循环。对于CRITIC这种强逻辑方法,我宁可代码啰嗦一点也要让人一眼看懂每一步在干什么。
3.3 相关系数矩阵到冲突性的转换
标准化矩阵出来后,先求标准差和相关系数矩阵,再把相关系数矩阵转换成冲突性:
import numpy as np # 标准差,注意ddof=0,与手算口径一致 std = z_df.std(axis=0, ddof=0) # 相关系数矩阵 corr = z_df.corr() print(corr)corr()方法默认算Pearson相关系数,这没问题,但要注意如果某列存在缺失值,corr()会返回NaN,后面全都会跟着报NaN。所以标准化之前最好先做缺失值处理,要么按行删,要么用均值填充,不能用带空值的表直接跑。
冲突性转换就是按行把相关系数矩阵里的值套进1减去它再求和:
# 冲突性向量 conflict = np.sum(1 - corr.values, axis=1)3.4 完整实现与运行结果
把上面几段拼起来,写成完整函数,顺便把信息量和权重一起算出来:
def critic_weight(df, positive_cols, negative_cols): # 1. 极差标准化 data = df.copy() for col in positive_cols: min_val = data[col].min() max_val = data[col].max() data[col] = (data[col] - min_val) / (max_val - min_val) for col in negative_cols: min_val = data[col].min() max_val = data[col].max() data[col] = (max_val - data[col]) / (max_val - min_val) # 2. 变异性:标准差 std = data.std(axis=0, ddof=0) # 3. 冲突性:相关系数矩阵转换 corr_mat = data.corr() conflict = np.sum(1 - corr_mat.values, axis=1) # 4. 信息量与权重 info = std.values * conflict weights = info / np.sum(info) return pd.Series(weights, index=data.columns, name='weight') weights = critic_weight( df, positive_cols=['毛利率', '营业利润增长率', '研发费用占比'], negative_cols=['资产负债率'] ) print(weights.round(4))运行结果和手算一致:
毛利率 0.2458 资产负债率 0.2558 营业利润增长率 0.2458 研发费用占比 0.2527 Name: weight, dtype: float64到这里,一个可复用的CRITIC权重计算函数就完成了。后面画图也是围绕这个weights对象展开。
4. 绘图展示的三板斧:从柱状图到组合图
4.1 排序柱状图:把权重结果第一眼讲清楚
权重计算出来后,最少也要画一张柱状图。我习惯画横向柱状图,因为指标名称通常是中文,横向排布不会互相挤在一起,排序后比垂直柱状图更容易阅读。代码可以直接在weights对象上操作:
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'sans-serif'] plt.rcParams['axes.unicode_minus'] = False w_sorted = weights.sort_values(ascending=True) fig, ax = plt.subplots(figsize=(8, 5)) bars = ax.barh(w_sorted.index, w_sorted.values, color='#4C72B0', edgecolor='black', linewidth=0.6) ax.bar_label(bars, fmt='%.4f', padding=3) ax.set_xlabel('权重') ax.set_title('CRITIC法指标权重分布') plt.show()ax.bar_label是matplotlib 3.4之后才有的便捷方法,旧版本需要手动循环加text。如果你还在用老环境,建议升级,或者用循环写,效果一样。图表标题我直接写“CRITIC法指标权重分布”,放到报告里别人一眼知道方法来源。
这种图表达的信息很直接:哪个指标权重最高、最低、差异有多大,全部用柱长说话。但注意,柱状图只适合展示结果,说不出“权重从哪来”的故事,所以我在后面还会叠加其他图。
4.2 雷达图:适合看指标体系的均衡性
雷达图适合展示指标体系整体形态,尤其当指标数量在3到8个之间时,封闭的多边形能直观反映权重结构是否均衡。如果图形接近正多边形,说明指标被平等对待;如果某个方向明显外凸,说明这个指标主导了评价结果。
import numpy as np labels = weights.index.tolist() values = weights.values.tolist() values += values[:1] angles = np.linspace(0, 2 * np.pi, len(labels), endpoint=False).tolist() angles += angles[:1] fig = plt.figure(figsize=(6, 6)) ax = plt.subplot(111, polar=True) ax.plot(angles, values, 'o-', linewidth=2, color='#C44E52') ax.fill(angles, values, alpha=0.25, color='#C44E52') ax.set_xticks(angles[:-1]) ax.set_xticklabels(labels) ax.set_ylim(0, max(weights.values) * 1.2) plt.show()雷达图的坑在于指标顺序会极大影响图形形状,换一种顺序可能从“三角形”变“五角星”。所以用它做汇报材料时,最好固定一套有业务逻辑的顺序,别每次画出来形状都不一样。
4.3 帕累托图:给决策者看“关键少数”
权重结果要拿去汇报的话,帕累托图是说服力很强的一种呈现方式。它的思路是把权重按从大到小排序,画柱状图,再用折线叠加累计权重曲线,最后在80%位置画条参考线。这张图的潜台词是:前几个指标已经贡献了大部分信息量,抓住重点就能抓住评价的主要矛盾。
w_desc = weights.sort_values(ascending=False) cumsum = w_desc.cumsum() fig, ax1 = plt.subplots(figsize=(9, 5)) ax1.bar(w_desc.index, w_desc.values, color='#55A868', edgecolor='black', linewidth=0.6) ax1.set_ylabel('权重') ax2 = ax1.twinx() ax2.plot(w_desc.index, cumsum.values, 'o-', color='#C44E52', linewidth=2) ax2.axhline(0.8, color='gray', linestyle='--') ax2.set_ylabel('累计权重') plt.title('指标权重帕累托分析') plt.show()这张图在项目验收时尤其好用。领导不看算法公式,但一看“累计权重80%在哪几个指标达到的”,马上就能理解你为什么要给某些指标更高权重。我每次做综合指数类项目,出报告的最后一页都会放这张图。
4.4 热力图+权重条组合图:让权重来源可解释
权重的差距如果被质疑,最佳回应是把“证据”画出来。CRITIC的权重来自标准差和冲突性,而冲突性又来自相关系数矩阵,所以把相关性热力图和权重条组成组合图,一眼就能看出权重和相关系数的联动关系。
import seaborn as sns fig, axes = plt.subplots(1, 2, figsize=(12, 5), gridspec_kw={'width_ratios': [2.2, 1]}) sns.heatmap(z_df.corr(), annot=True, fmt='.2f', cmap='RdBu_r', center=0, square=True, ax=axes[0], cbar_kws={'label': 'Pearson r'}) axes[0].set_title('指标相关系数矩阵') axes[1].barh(weights.index, weights.values, color='#4C72B0') axes[1].set_title('CRITIC权重') axes[1].set_xlabel('权重') plt.tight_layout() plt.show()仔细看这张图,能讲出一段完整的故事:研发费用占比和毛利率高度正相关,所以它在冲突性上被压分,但它的标准差偏大,最终权重仍排第二;资产负债率和营业利润增长率高度正相关,同样相互牵制。数据自己会解释权重为什么这样分配,这比任何口头说明都靠谱。
4.5 图表输出的通用设置:字体、DPI、配色
图表要直接进报告,输出设置就不能随便。字体方面,Windows环境用SimHei,macOS可以用Arial Unicode MS,最好在文件开头统一设置。DPI建议设成300,导出PNG或PDF再插入Word、PPT都不会糊。
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'sans-serif'] plt.rcParams['axes.unicode_minus'] = False plt.rcParams['figure.dpi'] = 300 plt.rcParams['savefig.dpi'] = 300 plt.rcParams['savefig.bbox'] = 'tight'配色不要用花里胡哨的默认色。我个人常用seaborn配色或Tableau 10色板,整体饱和度低、互相协调,打印出来也不刺眼。一个简单原则:正文图表不要超过三种主要颜色,重点序列用一种颜色,辅助参考线用灰色。
5. CRITIC落地的四个深坑:我在真实项目里逐个踩过
5.1 负向指标漏处理,权重方向直接反转
第一次用CRITIC时,我拿一套现成数据直接跑,根本没分辨指标方向。里面的“离职率”是个负向指标,结果标准化后还是“越高越好”的方向,导致后面的加权综合得分变成“离职率越高得分越高”,结论和业务逻辑完全相反。权重数字本身没算错,但方向错了,整个评价体系就废了。
现在的习惯是:进入CRITIC之前,先花十分钟对每个指标做方向标注,正向、负向分清楚。遇到适度指标,比如资产负债率不是越低越好,而是有一个合理区间,就要先做一步变换,把原始值转成“偏离适度值的程度”,再做负向标准化。这步不做,后面所有结论都没有业务解释力。
5.2 极端值挤压标准化区间,权重被带偏
极差标准化对极端值极其敏感。某一个企业某项指标出现异常值,比如毛利率因为一次性收益冲到45%,而其他企业都在10%到25%之间,max-min会被拉得很大,正常企业的标准化值全部挤在0.5以下,标准差被压缩,该指标的权重会被系统性低估。
我的解决办法是:标准化前先做缩尾处理,把上下5%分位以外的值替换成分位数值,再进CRITIC。缩尾代码很短:
def winsorize_series(s, lower=0.05, upper=0.05): lo = s.quantile(lower) hi = s.quantile(1 - upper) return s.clip(lo, hi)缩尾之后,极端值对极差的影响显著减小。要注意的是,缩尾属于对原始数据的修正,必须在文档里注明,否则后面复核数据的人会困惑为什么原始值不一样。
5.3 高度相关指标等于重复计权:先筛指标还是后调权重
CRITIC确实能降低高度正相关指标的权重,但它降的是“冲突性”部分,标准差部分还在正常计权。如果两个指标相关系数0.98,它们互相的冲突贡献只有0.02,信息量主要来自各自标准差,而两个指标合起来的总权重依然可观,本质上还是把同一份信息重复计入。
遇到成群结队的高相关指标,正确的做法是先做指标筛选,再跑CRITIC。操作路径大概是:先看相关系数热力图,圈出相关系数超过0.9的指标簇,在每个簇内保留业务含义最核心的一个指标,其余剔除或替换,然后再用CRITIC计算权重。这样CRITIC的“冲突性”机制才有真正的用武之地,而不是在一个已经高度冗余的数据集里做无谓挣扎。
5.4 小样本的相关矩阵不稳定:用Bootstrap给出区间
让我最难受的一次是只有8个样本的数据集,CRITIC算出来的权重很漂亮,但换掉其中一个样本,某个指标权重从0.31掉到0.12。原因就是相关系数在小样本下剧烈波动,而CRITIC把相关系数放进了核心分子里。
现在样本量不足时,我会用Bootstrap做稳定性验证:有放回地抽取与原样本一样大小的样本,重复1000次,每次都算一遍权重,最后给出每个指标的权重区间。区间越窄,结论越可信;区间宽到跨了10个百分点以上,说明数据本身撑不起这个结果,别硬上。
def critic_weight_bootstrap(df, positive_cols, negative_cols, n_boot=1000): results = [] for _ in range(n_boot): sample = df.sample(n=len(df), replace=True) try: w = critic_weight(sample, positive_cols, negative_cols).values results.append(w) except ZeroDivisionError: continue results = np.array(results) lower = np.percentile(results, 2.5, axis=0) upper = np.percentile(results, 97.5, axis=0) return pd.DataFrame({'lower': lower, 'upper': upper}, index=df.columns)输出结果直接在项目报告里写成“某指标权重95%置信区间为0.20到0.31”,比单点数字诚实得多,评审一般也更认这种方式。
5.5 算完之后必须做的一件事:拿加权得分与业务排名对拍
算法跑完不等于项目结束。我每次都把CRITIC权重乘以标准化后的数据得到综合得分,按得分给评价对象排序,再拿这个排名去和业务方的主观排名或者历史项目排名核对。如果差异过大,优先怀疑指标方向标错、极端值没处理,或者高相关指标簇没有预先筛选。
有一次我不死心,没有去做对拍就交了报告,结果排名里一家明显落后的企业被抬到了前列。后来一查,是那个负向指标方向标反了。修正之后重跑,排名才回归合理。从此对拍这步成了我的固定动作,也算给所有做客观赋权的人一个提醒:方法再严谨,都不如结果和业务共识对撞一次来得可靠。CRITIC的价值建立在数据质量之上,先把数据洗干净、方向理清楚,再谈权重和可视化,才不会让精致的方法给错误的数据背书。