第一次把DEMATEL和ISM放在一起做分析的人,大概率会经历两个阶段:先是被它们能解决的问题吸引,再是被一堆公式和矩阵运算劝退。DEMATEL能告诉你哪些因素起决定性影响、哪些因素只是被动接受影响;ISM能把十几个甚至几十个因素整理成有层次的结构图。两个方法配合起来,既能量化影响强度,又能看清系统骨架,在供应商评价、安全风险分析、物流管理、教育质量评估这些主题里都很常见。可惜大部分资料只给公式,或者截一段根本跑不通的代码。这篇文章把我自己复现DEMATEL-ISM模型的完整过程记录下来,从原理拆解到Python代码实现,最后用一个可复现的小例子跑通全流程,希望能给正在做相关课题研究或者项目分析的朋友省点时间。
1. DEMATEL与ISM的原理梳理
1.1 DEMATEL:量化因素间的相互影响
DEMATEL全称Decision Making Trial and Evaluation Laboratory,决策试验与评价实验室法,上世纪70年代起源于Battelle研究所。它的核心任务是回答一个问题:在一个系统里,因素A到底是通过什么路径、以什么样的强度影响因素B的?
DEMATEL的输入是直接影响矩阵A,元素a[i][j]表示因素i对因素j的直接影响力,通常按0到3或者0到4打分,0代表没有影响,数值越大代表影响越强。实际操作中一般找领域专家逐对打分,回收问卷后取平均,得到最终的矩阵。对于n个因素,A就是一个n行n列的矩阵,行代表发出影响的因素,列代表接收影响的因素,对角线一般设为0,不讨论因素对自身的直接影响。
有了直接影响矩阵之后,DEMATEL会做一次归一化处理。把矩阵A的每一行求和,找到所有行和中的最大值,然后用这个最大值去除矩阵中的全部元素,得到规范化矩阵X。这种归一化方式保证了X的每一行和都不超过1,数学上为后续矩阵级数的收敛创造了条件。接着计算综合影响矩阵T:
T = X(I-X)^(-1)
这里I是单位矩阵。T的每一项代表因素i对因素j的综合影响程度,包括直接路径和各级间接路径。理解这个公式有一个特别直观的角度:如果把X看作一步直接影响的传递矩阵,那么X²就是两步间接影响,X³就是三步间接影响,把所有路径的贡献全部加起来就是综合影响矩阵,也就是等比级数X+X²+X³+...。只要行和小于1,这个级数就一定收敛,用逆矩阵一步算出来比循环几百次迭代要可靠得多。
有了T矩阵以后,分别计算行和与列和。行和D称为影响度,表示该因素对外输出影响的总量;列和R称为被影响度,表示该因素接收外部影响的总量。两个指标相加得到中心度D+R,因素在系统里的活跃程度和重要程度越高,中心度往往越大;相减得到原因度D-R,大于0是原因因素,也就是影响别人的因素,小于0是结果因素,也就是更依赖别人的因素。
1.2 ISM:把模糊关系变成层次结构
ISM全称Interpretive Structural Modeling,解释结构模型,由Warfield在1974年提出。它做的事情和DEMATEL完全不同:DEMATEL关心“影响有多强”,ISM关心“谁是源头、谁是终端、系统一层一层长什么样”。
ISM的输入通常是可达矩阵,一个由0和1构成的n阶布尔矩阵。矩阵里某一行i、某一列j位置为1,表示从因素i出发可以通过直接或间接路径到达因素j,通常认为因素自身可达自身,所以对角线一般置1。
从可达矩阵出发,ISM定义一个关键变量:可达集和先行集。对于任意因素i,可达集R(Si)是所有被i影响的因素集合,包括自身;先行集Q(Si)是所有能影响i的因素集合,也包括自身;两者取交集得到C(Si)。如果一个因素的C(Si)恰好等于R(Si),说明它能够影响到的所有因素都能反过来追溯到它,它就处在当前子系统的顶层,也就是结果端。把这一层因素抽出来,在矩阵中删除它们对应的行和列,对剩余因素重复同样步骤,就能逐层剥出整个系统的递阶结构。
这个过程有点像一个团队里理清上下级关系:先找到那些只受别人安排、不再往下派活的成员,把他们排为第一层,然后从团队名单里去掉,在剩下的人里继续找“不作为上游”的人,循环往复,最终把整条汇报链路还原出来。
1.3 两个方法为什么要结合着用
单独用DEMATEL,你得到的是每个因素的影响度、被影响度、中心度、原因度,信息很全,但只能停留在“谁强谁弱”的层面,没法回答“系统是怎么组织的”这个问题。单独用ISM,你可以画出一张清晰的层次结构图,但输入的可达矩阵通常来自专家直接打分的0/1关系,比较粗糙,而且专家打分时经常遇到“到底算直接关系还是间接关系”的困惑。
DEMATEL-ISM组合方式正是为了取长补短。先用DEMATEL求出综合影响矩阵T,这个矩阵已经把直接和间接影响都整合进去了,再通过设定阈值,把T矩阵中的连续数值转换成0/1布尔矩阵,作为ISM的输入做层级划分。这样一来,既保留了DEMATEL的信息整合能力,又能输出ISM那套直观的层次结构,学术论文和实际咨询报告里经常见到这条技术路线。
2. 模型整体流程设计与核心思路
2.1 一条主线:从直接影响矩阵到系统层次图
整个DEMATEL-ISM建模过程可以拆成七个环节。
- 确定因素体系,设计专家问卷,收集专家打分,得到直接影响矩阵A。
- 对A做行和最大值归一化,得到矩阵X。
- 计算综合影响矩阵T = X(I-X)^(-1)。
- 计算影响度D、被影响度R、中心度D+R、原因度D-R,初步判断因素重要性。
- 设定阈值λ,将T矩阵中大于等于λ的元素置1,其余置0,得到初始可达矩阵,并把对角线置1。
- 对可达矩阵做传递闭包运算,或者直接用布尔化结果,交给ISM做层级抽取。
- 输出分层结果,结合原因度、中心度一起解读,绘制递阶结构图。
步骤5是整个流程里最需要经验的地方。阈值λ选得小,可达矩阵里1的数量就多,分层结果可能只剩一两层;阈值λ选得大,1的数量就少,因素之间关系稀疏,分层会特别碎,甚至出现大量孤立因素。建议先用T矩阵的均值作为基准阈值跑一遍,再尝试均值加0.5倍标准差、均值加1倍标准差,比较不同阈值下的层级稳定性。如果层级结构在多个阈值下保持一致,说明模型结论比较稳健。
2.2 阈值λ的设定思路为什么关键
阈值设定的本质是在“保留有用关系”和“过滤噪声关系”之间找平衡。综合影响矩阵T里的元素是小数,比如0.03可能来自一条传播了很多步的间接路径,在现实中虽然有影响但已经弱到可以忽略。如果把这些微弱关系全部保留,ISM分层会把几乎所有因素都识别成互相可达,层次自然出不来。
反过来,如果阈值设得太激进,连重要关系都过滤掉了,因素之间被切得七零八落,同样得不到有意义的层次结构。
我给一个操作经验:第一次跑的时候,先打印T矩阵的均值、标准差、最大值、最小值,心里有个数。均值比较小的时候,比如0.1左右,可以先从均值开始;如果T矩阵整体数值偏大,比如均值超过0.2,可以尝试均值加标准差。最终选定的阈值一定要在论文或者报告里写清楚,因为不同阈值会直接影响可达矩阵和层级划分,这是方法本身的人为选择,不是模型缺陷。
2.3 与纯ISM相比,这条路线有什么取舍
如果完全按照ISM的原始定义,专家需要直接给出因素之间的布尔关系,也就是0/1判断。实际填问卷的时候,专家经常会在“有影响”和“没有影响”之间犹豫,而且0/1判断丢掉了很多影响强度的信息。DEMATEL-ISM组合避开了这个问题,专家只需要按0到3或者0到4打分,强度信息更丰富,后续还能顺便算中心度和原因度。
代价也很明显:从连续阈值到布尔矩阵的转换过程引入了一个主观参数λ,不同的人可能选不同的阈值,结果也会不同。所以复现时不要只跑一个阈值就下结论,最好多试几个值,把结果差异控制在可解释范围内。
3. Python环境准备与数据组织
3.1 环境配置:只需要numpy和pandas
这个模型对Python版本没有特别苛刻的要求,Python 3.8以上版本都可以顺畅运行,核心依赖只有三个:numpy负责矩阵运算,pandas负责结果展示,如果后面想画原因度-中心度散点图,再装一个matplotlib就行。
命令行里执行下面的安装命令即可:
pip install numpy pandas matplotlib如果电脑里还没有Python环境,建议直接去Python官网下载安装包,安装时勾选Add Python to PATH,然后用VS Code或者PyCharm写脚本。第一次接触Python的读者建议用Anaconda,自带numpy、pandas、matplotlib等常用库,可以省掉不少配置的麻烦。
装完之后可以跑一行简单代码验证环境是否正常:
import numpy as np import pandas as pd print(np.__version__) print(pd.__version__)能正常输出版本号就说明环境没有问题。
3.2 输入数据的结构设计
复现模型之前,先把输入数据结构搞清楚。直接影响矩阵A用一个二维列表或者numpy数组表示,同时准备一个因素名称列表labels,顺序和矩阵的行列顺序保持一致。
比如有四个因素,分别叫“信息系统建设”“管理制度完善”“人员技能水平”“绩效结果”,那么labels就是["信息系统建设", "管理制度完善", "人员技能水平", "绩效结果"],A就是一个4×4矩阵。
收集数据方面要特别留意一点:问卷打分一定要定义清楚方向。a[i][j]是第i个因素对第j个因素的影响,行是施动方,列是受动方。实际处理问卷时经常看到有人把行和列方向搞反,导致最后算出来的中心度和原因度完全错位,这一点怎么强调都不为过。
4. 代码复现:用Python一步步实现DEMATEL-ISM
4.1 DEMATEL计算模块
先写DEMATEL的核心计算函数。输入是直接影响矩阵A和因素名称列表labels,输出是综合影响矩阵T和分析指标DataFrame。
import numpy as np import pandas as pd def dematel_compute(A, labels): A = np.array(A, dtype=float) n = A.shape[0] # 1. 归一化:每一行求和,找到最大值,用最大值去除所有元素 row_sums = A.sum(axis=1) max_row_sum = np.max(row_sums) X = A / max_row_sum print("行和最大值:", max_row_sum) # 2. 综合影响矩阵 T = X(I-X)^{-1} I = np.eye(n) try: inv = np.linalg.inv(I - X) except np.linalg.LinAlgError: raise ValueError("I-X 不可逆,请检查直接影响矩阵的数值设置") T = X @ inv # 3. 影响度D、被影响度R、中心度D+R、原因度D-R D = T.sum(axis=1) R = T.sum(axis=0) center = D + R cause = D - R # 4. 汇总成DataFrame方便查看 df = pd.DataFrame({ "因素": labels, "影响度D": D, "被影响度R": R, "中心度D+R": center, "原因度D-R": cause }) return T, df这段代码里最核心的是归一化方式。用所有行和的最大值去除每一个元素,而不是用每行自己的和去除,是为了让整个矩阵共享同一个缩放尺度,这样计算出来的综合影响矩阵才具有可比性。
4.2 ISM层级划分模块
接下来实现ISM的核心逻辑。先实现一个布尔矩阵乘法,用于传递闭包运算,然后实现层级抽取函数。
def bool_mul(A, B): """布尔矩阵乘法:结果中1表示存在至少一条通路""" return (A @ B > 0).astype(int) def reachability_matrix(T, threshold, labels=None, add_identity=True, transitive_closure=True): """根据阈值把综合影响矩阵T转为可达矩阵K""" n = len(T) # 大于等于阈值的置1,其余置0 B = np.where(T >= threshold, 1, 0) if add_identity: np.fill_diagonal(B, 1) # 可选:做传递闭包,让间接可达关系也体现出来 if transitive_closure: for _ in range(n): C = bool_mul(B, B) if np.array_equal(C, B): break B = C return B.astype(int)层级抽取函数是整个模型里最容易写错的地方。核心逻辑不再重复阐述,直接看代码:
def ism_levels(reach_mat, labels): n = len(reach_mat) remaining = list(range(n)) level_list = [] while remaining: current_level = [] for i in remaining: # 可达集:从i出发能够到达的因素(只考虑尚未分层的因素) reachable = {j for j in remaining if reach_mat[i, j] == 1} # 先行集:能够到达i的因素(只考虑尚未分层的因素) antecedent = {j for j in remaining if reach_mat[j, i] == 1} inter = reachable & antecedent # 如果交集等于可达集,说明i没有能力触达更上层因素,属于当前最顶层 if inter == reachable: current_level.append(i) if not current_level: print("警告:本轮没有抽取到任何因素,请检查可达矩阵或调整阈值") break level_list.append(current_level) for i in current_level: remaining.remove(i) # 把索引转换为因素名称 result = [] for level in level_list: result.append([labels[i] for i in level]) return result这里有一个特别容易踩的坑:每一轮计算可达集和先行集时,要暂时忽略已经被抽走的因素。有些实现直接在原始可达矩阵上计算集合,不剔除已经分层的因素,导致后续层级的判定出现偏差。我上面用remaining这个列表做了过滤,可以避免这个问题。
4.3 完整可运行源码
把两个模块串起来,一个最小可复现的脚本就出来了。我用一个四因素小案例演示。
import numpy as np import pandas as pd def dematel_compute(A, labels): A = np.array(A, dtype=float) n = A.shape[0] row_sums = A.sum(axis=1) max_row_sum = np.max(row_sums) X = A / max_row_sum print("行和最大值:", max_row_sum) I = np.eye(n) try: inv = np.linalg.inv(I - X) except np.linalg.LinAlgError: raise ValueError("I-X 不可逆,请检查直接影响矩阵的数值设置") T = X @ inv D = T.sum(axis=1) R = T.sum(axis=0) center = D + R cause = D - R df = pd.DataFrame({ "因素": labels, "影响度D": D, "被影响度R": R, "中心度D+R": center, "原因度D-R": cause }) return T, df def bool_mul(A, B): return (A @ B > 0).astype(int) def reachability_matrix(T, threshold, add_identity=True, transitive_closure=True): n = len(T) B = np.where(T >= threshold, 1, 0) if add_identity: np.fill_diagonal(B, 1) if transitive_closure: for _ in range(n): C = bool_mul(B, B) if np.array_equal(C, B): break B = C return B.astype(int) def ism_levels(reach_mat, labels): n = len(reach_mat) remaining = list(range(n)) level_list = [] while remaining: current_level = [] for i in remaining: reachable = {j for j in remaining if reach_mat[i, j] == 1} antecedent = {j for j in remaining if reach_mat[j, i] == 1} inter = reachable & antecedent if inter == reachable: current_level.append(i) if not current_level: print("警告:本轮没有抽取到任何因素,请检查可达矩阵或调整阈值") break level_list.append(current_level) for i in current_level: remaining.remove(i) result = [] for level in level_list: result.append([labels[i] for i in level]) return result if __name__ == "__main__": labels = ["信息系统建设", "管理制度完善", "人员技能水平", "绩效结果"] A = np.array([ [0, 2, 3, 1], [1, 0, 2, 3], [0, 1, 0, 2], [0, 0, 1, 0] ], dtype=float) T, df = dematel_compute(A, labels) print("\n综合影响矩阵 T:") print(pd.DataFrame(T, index=labels, columns=labels).round(4)) print("\n因素指标汇总:") print(df.round(4)) # 阈值根据实际情况调整,这里为了演示手动手设为0.3 threshold = 0.3 print("\n阈值λ:", threshold) K = reachability_matrix(T, threshold, add_identity=True, transitive_closure=True) print("\n可达矩阵 K:") print(pd.DataFrame(K, index=labels, columns=labels)) levels = ism_levels(K, labels) print("\nISM层次划分结果:") for idx, level in enumerate(levels, 1): print(f"第{idx}层: {level}")这个脚本直接复制到Python文件里运行就能出结果。numpy和pandas版本只要不是太老,一般不会报错。如果运行时报中文编码相关的错误,在文件第一行加上# -*- coding: utf-8 -*-,或者把文件保存为UTF-8编码即可。
4.4 代码输出格式设计
我把输出设计成三个部分:综合影响矩阵、因素指标汇总、ISM层级结果。综合影响矩阵方便核对中间计算,因素指标汇总给论文分析用,层级结果给结构化建模用。
实际做项目的时候,建议把因素指标表输出成CSV,方便后续处理:
df.to_csv("dematel_result.csv", index=False, encoding="utf-8-sig")用utf-8-sig编码是为了让Excel直接打开时中文不乱码,这个小细节经常被忽略。
5. 案例演示:一个四因素小案例
5.1 指标设定与直接影响矩阵
案例背景简单一点:某家公司想梳理影响绩效结果的核心因素,定义了四个因素:
- F1:信息系统建设
- F2:管理制度完善
- F3:人员技能水平
- F4:绩效结果
专家打分按照0到3分制,0代表无影响,1代表弱影响,2代表中等影响,3代表强影响。最终取平均后得到直接影响矩阵:
F1 F2 F3 F4 F1 0 2 3 1 F2 1 0 2 3 F3 0 1 0 2 F4 0 0 1 0这个矩阵的含义是:信息系统建设对管理制度完善有中等影响,对人员技能水平有强影响,对绩效结果有弱影响;管理制度完善对人员技能水平有中等影响,对绩效结果有强影响;人员技能水平对绩效结果有中等影响。
5.2 运行结果与层级输出
运行脚本后,代码会依次输出综合影响矩阵、因素指标汇总、可达矩阵和层级结果。这里重点看层级划分,在我的一次运行中(阈值λ=0.3),ISM分层如下:
- 第1层:绩效结果
- 第2层:人员技能水平
- 第3层:信息系统建设、管理制度完善
这个结果和业务逻辑高度吻合。绩效结果处于系统最顶层,是被生产出来的结果变量;人员技能水平在中间层,扮演传导角色;信息系统建设和管理制度完善处在底层,作为根源因素互相促进,共同驱动整个系统运转。
5.3 结果解读与决策价值
结合DEMATEL计算的中心度和原因度来看,这套结果可以给出很明确的决策建议。信息系统建设和管理制度完善如果原因度为正且中心度较高,就说明它们是系统的“引擎”,做管理优化的资源应该优先投入到这两个方面。人员技能水平作为中间传导层,需要靠制度和信息系统来带动;绩效结果作为结果变量,反应最敏感但却是被动的,直接考核绩效而不改善上游因素,效果非常有限。
实际项目里因素数量通常在10个以上,比如供应链风险分析动辄15个指标,ISM分层会分出3到5层,解读逻辑完全一致:底层因素需要优先治理,顶层因素更适合作为衡量指标和阶段性目标。
6. 常见问题与排查技巧实录
6.1 矩阵不可逆怎么办
计算T矩阵时如果报LinAlgError,说明I-X接近奇异矩阵,最常见的原因是直接影响矩阵A里出现了异常大的数值,导致归一化后X的谱半径接近1。解决办法是重新检查原始打分,看是不是有人填了极端值,或者打分等级制不一致。另一种情况是因素之间有重复的完全拟合关系,比如两个因素其实描述的是同一件事,矩阵里会出现多重共线性,这时需要合并因素。
6.2 ISM分层结果不理想怎么调阈值
分层结果一般有三种不理想的情况:全是同一层、层数太多太碎、出现大量孤立因素。
| 表现 | 阈值方向 | 处理建议 |
|---|---|---|
| 全部因素集中在同一层 | 阈值偏小 | 增大阈值,过滤弱影响关系 |
| 每个因素单独一层 | 阈值偏大 | 减小阈值,保留更多合理关系 |
| 大量因素孤立无联系 | 阈值过大或者矩阵本身稀疏 | 减小阈值,或者检查原始矩阵是否有误 |
建议多跑几个阈值,把层级结构稳定作为选择标准,而不是只看单次运行结果。
6.3 ISM分层出现死循环怎么办
我在代码里加了保护逻辑:如果某轮一个因素都抽不出来,就自动终止并打印警告。出现这种情况几乎都是可达矩阵的问题,最常见的是所有因素之间都没有强连通关系,导致没有任何因素满足“交集等于可达集”的条件。把阈值调小一点,多保留一些可达关系,通常就能解决。
6.4 计算结果和Excel对不上
Excel里计算综合影响矩阵时,很多人用MMULT函数手写矩阵乘法,容易出现转置错误。Python代码里T.sum(axis=1)是沿行加和,得到影响度D;T.sum(axis=0)是沿列加和,得到被影响度R。Excel里如果行和列看反了,中心度和原因度符号都会反转,检查时优先核对D和R的方向。
6.5 打印中文乱码
Windows终端下运行Python经常出现中文乱码。最稳妥的办法是代码文件用UTF-8编码保存,然后在打印前加一句:
import sys sys.stdout.reconfigure(encoding="utf-8")如果还是乱码,把结果用df.to_csv输出到文件里看,不依赖终端界面展示。
6.6 因素数量多会不会跑很慢
我测试过20个因素的矩阵,DEMATEL逆矩阵计算和ISM层级划分都是毫秒级别,完全不用担心性能。真正耗时的是专家打分和数据整理,Python计算部分基本可以忽略不计。
最后再分享一个实际操作中的心得:跑完结果不要急着画图,先把中心度、原因度和ISM层级放一张表里对照着看。原因度为负但处于ISM底层,或者原因度为正但被划分到顶层,这些“矛盾”情况往往提示因素体系设计有问题,要么是因素定义有重叠,要么是专家打分逻辑不一致。模型复现只是第一步,能用业务逻辑把计算结果解释通,才是真正把DEMATEL-ISM用起来了。