ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CRITIC法计算指标权重:原理、Python实现与可视化详解

CRITIC法计算指标权重:原理、Python实现与可视化详解 做指标权重计算的时候很多人第一反应是熵权法。我也一样直到有一次做企业综合评价指标之间皮尔逊相关系数普遍在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.950.05信息量几乎只靠标准差撑起来权重自然就降下来了。这个行为在实际评价中非常有意义。我用过一个包含12个指标的经营评价体系里面两个指标本质都在测“赚钱效率”高相关必然出现。熵权法算出它们合计权重接近三成业务部门看到就质疑“这不是重复打分吗”换成CRITIC后这个信息簇的总权重明显收缩整体排名更符合大家的主观判断。三种方法的差异可以归纳成下面这张表方法主要信息源是否考虑指标间相关典型适用场景标准差法 / 变异系数法离散程度否指标相互独立或相关性较弱熵权法信息熵反映的离散程度否相关性低、分布形态差异明显CRITIC法离散程度 指标间冲突是指标多、相关性结构复杂1.3 适用场景判断什么时候不用纠结直接上CRITIC我个人的选择习惯是先花两分钟跑一个相关系数矩阵看看有没有成群结队的高相关指标只要有哪怕只有一对相关系数超过0.85就优先用CRITIC。因为这时候熵权法的“信息独立”假设已经不成立用它对不懂统计的业务方解释权重会很费劲。另外CRITIC也非常适合指标数量较多的场景。指标数超过10个以后指标间发生偶然高相关的概率急剧增加不做冲突性处理的权重结果很容易“偏科”。反过来如果指标之间相关性都低于0.3CRITIC的结果和熵权法往往差异不大那直接用熵权法更省事毕竟熵权法的实现更普及参考资料也多。还有一种情况不建议用它样本量太少。CRITIC依赖相关系数矩阵而相关系数在小样本下非常不稳定5个样本算出来的0.9和-0.9可能只是抽样噪声。这时要么老老实实加样本要么用我后面会讲的Bootstrap方法给权重套一个区间而不是拿一个精确到小数点的数字当真。2. 手算5×4矩阵把一个CRITIC周期从头推到尾2.1 数据集设定经营质量评价里的4个代表指标原理说多了容易飘还是上手算一遍最扎实。我设计了一个很小的案例5家企业、4个指标正好能覆盖正向和负向两种指标类型足够复现CRITIC全过程又不至于让人盯着表格头皮发麻。表里四个指标分别是毛利率、资产负债率、营业利润增长率、研发费用占比。其中资产负债率在经营质量评价里通常是负向指标比率越高财务风险越大所以下面会单独处理。这五个企业的原始数据如下企业毛利率(%)资产负债率(%)营业利润增长率(%)研发费用占比(%)A22.558.08.24.1B18.945.012.53.2C30.162.05.65.8D15.239.515.22.9E25.850.510.74.62.2 极差标准化正向和负向指标分别处理CRITIC第一步是消除量纲我这里用的是极差标准化也叫Min-Max标准化。正向指标按常规方式处理标准化值等于当前值减最小值除以最大值减最小值这样所有值落到0到1之间越大越好。负向指标要反过来用最大值减当前值再除以极差让负向指标变成数值越大越好方向统一了后续综合打分才不会出乱子。正向指标公式z (x - min) / (max - min)。负向指标公式z (max - x) / (max - min)。为什么不推荐用Z-score因为CRITIC下一步要算标准差作为变异性指标Z-score会把所有列的标准差强行变成1变异性信息被抹掉CRITIC就退化成“只看冲突性”的半残版本。极差标准化保持了各列之间相对变异大小的差异这才是CRITIC需要的输入。标准化后的矩阵长这样注意资产负债率列已经完成方向转换企业毛利率资产负债率(转正)营业利润增长率研发费用占比A0.4900.1780.2710.414B0.2480.7560.7190.103C1.0000.0000.0001.000D0.0001.0001.0000.000E0.7110.5110.5310.5862.3 标准差、相关系数与冲突性两个“信息量”从哪来先看变异性。对标准化矩阵的每一列求标准差注意这里是总体标准差也就是除以样本数n而不是n-1。算出来之后四列的标准差分别是0.349、0.366、0.347、0.358。差别不算悬殊说明这四个指标在离散程度上贡献接近单靠离散度分不出高低。再算指标间冲突性。冲突性定义是某指标与其他所有指标相关系数之差的累计也就是对每个指标计算1 - 与其它指标的相关系数的加和。相关系数矩阵我直接列出来注意下半部分对称省得看两遍指标毛利率资产负债率营业利润增长率研发费用占比毛利率1.000-0.877-0.9030.986资产负债率-0.8771.0000.994-0.882营业利润增长率-0.9030.9941.000-0.907研发费用占比0.986-0.882-0.9071.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.3493.7941.3240.246资产负债率0.3663.7651.3780.256营业利润增长率0.3473.8161.3240.246研发费用占比0.3583.8031.3610.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 # 标准差注意ddof0与手算口径一致 std z_df.std(axis0, ddof0) # 相关系数矩阵 corr z_df.corr() print(corr)corr()方法默认算Pearson相关系数这没问题但要注意如果某列存在缺失值corr()会返回NaN后面全都会跟着报NaN。所以标准化之前最好先做缺失值处理要么按行删要么用均值填充不能用带空值的表直接跑。冲突性转换就是按行把相关系数矩阵里的值套进1减去它再求和# 冲突性向量 conflict np.sum(1 - corr.values, axis1)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(axis0, ddof0) # 3. 冲突性相关系数矩阵转换 corr_mat data.corr() conflict np.sum(1 - corr_mat.values, axis1) # 4. 信息量与权重 info std.values * conflict weights info / np.sum(info) return pd.Series(weights, indexdata.columns, nameweight) 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(ascendingTrue) fig, ax plt.subplots(figsize(8, 5)) bars ax.barh(w_sorted.index, w_sorted.values, color#4C72B0, edgecolorblack, linewidth0.6) ax.bar_label(bars, fmt%.4f, padding3) 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), endpointFalse).tolist() angles angles[:1] fig plt.figure(figsize(6, 6)) ax plt.subplot(111, polarTrue) ax.plot(angles, values, o-, linewidth2, color#C44E52) ax.fill(angles, values, alpha0.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(ascendingFalse) cumsum w_desc.cumsum() fig, ax1 plt.subplots(figsize(9, 5)) ax1.bar(w_desc.index, w_desc.values, color#55A868, edgecolorblack, linewidth0.6) ax1.set_ylabel(权重) ax2 ax1.twinx() ax2.plot(w_desc.index, cumsum.values, o-, color#C44E52, linewidth2) ax2.axhline(0.8, colorgray, 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(), annotTrue, fmt.2f, cmapRdBu_r, center0, squareTrue, axaxes[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环境用SimHeimacOS可以用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, lower0.05, upper0.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_boot1000): results [] for _ in range(n_boot): sample df.sample(nlen(df), replaceTrue) 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, axis0) upper np.percentile(results, 97.5, axis0) return pd.DataFrame({lower: lower, upper: upper}, indexdf.columns)输出结果直接在项目报告里写成“某指标权重95%置信区间为0.20到0.31”比单点数字诚实得多评审一般也更认这种方式。5.5 算完之后必须做的一件事拿加权得分与业务排名对拍算法跑完不等于项目结束。我每次都把CRITIC权重乘以标准化后的数据得到综合得分按得分给评价对象排序再拿这个排名去和业务方的主观排名或者历史项目排名核对。如果差异过大优先怀疑指标方向标错、极端值没处理或者高相关指标簇没有预先筛选。有一次我不死心没有去做对拍就交了报告结果排名里一家明显落后的企业被抬到了前列。后来一查是那个负向指标方向标反了。修正之后重跑排名才回归合理。从此对拍这步成了我的固定动作也算给所有做客观赋权的人一个提醒方法再严谨都不如结果和业务共识对撞一次来得可靠。CRITIC的价值建立在数据质量之上先把数据洗干净、方向理清楚再谈权重和可视化才不会让精致的方法给错误的数据背书。
返回列表