본 예제는 2023년 연구 중 동료 연구자들에게 AHP에 대한 소개를 하고자
마련되었습니다.
혹시 참고자료가 필요하시거나 내용 설명을 원하시는 연구자들은 tk_kim@khu.ac.kr으로 연락 주시기 바랍니다. - 김태경
교수, 빅데이터 응용학과, 경희대학교
본 예제는 주로 키노시타 에이조 & 오오야 타카오 (저); 권재현 (역), 전략적 의사결정기법 AHP (청람)을 참고하였습니다.
본 예제는 다음의 라이브러리를 필요로 합니다. 미리 설치를 부탁드립니다. 설치가 되어 있다면 아래 코드를 실행하여 라이브러리를 탑재해 주시기 바랍니다.
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(tidyr)
library(purrr)
library(ahpsurvey)
library(writexl)
library(readxl)
문제의 요소를 1)최종목표, 2)평가기준, 3)대안으로 정리합니다.
계층구조를 만들고, 평가기준의 중요도를 정하고 각 평가기준에 대하여
대안의 중요성을 평가합니다. 마지막으로 전체적으로 대안의 평가점수를
획득합니다.
AHP는 한 가지나 두 가지 평가기준을 가지고 대안을 평가하거나 단순히 평가기준의 질적 특성을 무시하고 점수를 합산하여 평가하는 방식의 불합리성을 극복하고자 만들어진 의사결정 이론입니다.
복잡하게 생각할 것 없이 예제를 통해서 살펴봅시다.
AHP는 고유벡터를 가중치로 삼습니다. 또한 고유치에서 일관성 지수도
계산합니다. 그럼 고유벡터, 고유치가 무엇인지부터 알아야 합니다.
0이 아닌 어떤 벡터 \(v \in V\)가 어떤
스칼라 \(\lambda \in F\)에 대해 \(Av=\lambda v\)를 만족할 때, \(v\)를 \(A\)의 고유벡터(eigenvector)라고 하고 \(\lambda\)를 고유치(eigenvalue)라고 합니다.
이때, 방정식은 \((\lambda I - A)v =
0\)가 되기 때문에 비가역 행렬 \(\lambda
I - A\)는 행렬식이 0입니다. 따라서 해당 행렬 혹은 선형사상 \(A\)의 특성 다항식(characteristic
polynomial)은 \(\chi_{A}(x) = det(x I -
A)\)를 의미합니다.
고유벡터는 방향은 변화하지 않고 크기만 변화합니다. 정방행렬에서 각각의 내용을 비교했을 때 나올 수 있는 정방행렬 \(A\)의 사상에서 방향이 변화하지 않는 고유벡터를 계산하면 그것이 \(A\)를 구성하는 각각의 요소의 대표값으로 기능합니다. 이때 문제의 특성 상 \(A\)는 행과 열의 특성이 같기 때문입니다. 따라서 고유벡터를 이루는 각 고유치는 이에 대응되는 요소의 특성(characteristic, 독일어로 eigen)을 보여줍니다.
AHP에서 고유치를 구하는 방법은 멱승법입니다. 고유치를 구하는 다양한 알고리즘이 있습니다. AHP에서 사용되는 멱승법은 다음과 같습니다.
고유 벡터는 곧 특성을 의미하고 이것은 각 요소의 중요도를 의미합니다(\(\sum{W_{i}}\)를 취했기 때문).
A는 다음과 같습니다. \[ A = \begin{pmatrix} 1 & 3 \\ 1/3 & 1 \end{pmatrix} \] 이를 R로 표현하면,
A <- matrix(c(1,1/3,3,1),nrow=2)
print(A)
## [,1] [,2]
## [1,] 1.0000000 3
## [2,] 0.3333333 1
1번 열의 값은 다음과 같습니다.
\[ \begin{pmatrix} 1 \\ 1/3 \end{pmatrix} \]
이것이 \(W^{0}\)이고 다시 \(W^{1}\)를 계산하기 위해 다음과 같은 계산식을 따릅니다.
\[ \begin{pmatrix} 1 & 3 \\ 1/3 & 1 \end{pmatrix} \begin{pmatrix} 1 \\ 1/3 \end{pmatrix} \] 이를 R로 계산하면,
A %*% A[,1]
## [,1]
## [1,] 2.0000000
## [2,] 0.6666667
이를 표준화하여 W로 정해 다음 계산을 이어갑시다. 즉,
W <- A %*% A[,1]
W <- W / sum(W)
print(W)
## [,1]
## [1,] 0.75
## [2,] 0.25
따라서 다음 계산은
\[ \begin{pmatrix} 1 & 3 \\ 1/3 & 1 \end{pmatrix} \begin{pmatrix} 0.75 \\ 0.25 \end{pmatrix} \] 이고, 이를 다시 R로 계산하면,
W <- A %*% W
W <- W / sum(W)
print(W)
## [,1]
## [1,] 0.75
## [2,] 0.25
가 되어 이전 결과와 같습니다. 따라서 고유치는 \(0.75\)와 \(0.25\)가 되고 이는 정방행렬을 구성한 각각의 특성 f1, f2의 상대적 가중치 혹은 중요도로 해석됩니다.
본 문제를 정리하면 다음과 같습니다.
문제는 이것이죠, 평가기준을 줄테니 대안 A1, A2, A3를 잘 선택해서
최종목표를 달성해야 합니다.
일반적으로 전문가에게 물어볼 때 어떤 평가기준을 가장 중요하게 생각하는지
명확하게 물어보지 않으면 모르겠죠. 그냥 선택하시면 (점지하시면) 결과를
받아들여야 하잖아요. 그렇게 하지말고 정확히 어떤 대안이 어떤 이유로
선택이 되었고 각각의 대안들은 얼마나 중요도에서 차이가 나는지 숫자로
계산해서 봅시다. 그리고 이러한 대안선택의 과정도 consistency index를
통해 계산해봅시다.
나중에 ahpsurvey라는 패키지에서 제공하는 편리한 함수들을 쓰겠지만,
일단 창시자인 Saaty (1971)의 방법을 충실히 따라가 봅시다.
편리성(C1) 하나만 해보죠.
먼제 각각의 기준에 따라 대안들의 매력도를 평가하도록 해야
합니다.
편리성 기준부터 합시다.
데이터는 행렬로 넣는데요, 행쪽에 기준 대안을, 열쪽에 비교 대안을 배치합니다. 예를 들어, 1행 2열은 A1 대안이 A2에 비해 얼마나 중요한가? 이 질문에 대한 답을 적습니다. 그럼 2행 3열은 뭘까요? A2대안이 A3에 비해 얼마나 중요한가?에 대한 답입니다. 답을 5라고 하면 높은 수준이구요, 1/5라고 하면 A2가 A3에 비해 1/5밖에 매력적이지 않다. 즉 A3가 5배는 A2보다 중요하다는 뜻이죠.
c1 <- matrix(
c(1,5,9,
1/5,1,2,
1/9,1/2,1),
nrow=3,
byrow=TRUE
)
c1.lambda <- c1 %>%
eigen(symmetric = TRUE) %>%
.$values
c1.lambda.all <- sum(c1.lambda)
#C.I. = (lambda-n)/(n-1)
n <- nrow(c1)
c1.ci <- round((c1.lambda.all-n)/(n-1),3)
c1.lambda.ind <- Re(c1.lambda)/c1.lambda.all
원칙적으로 이런 행렬 표현으로 구하려면 일단 데이터를 너무 많이 입력해야 합니다. 즉 정방행렬의 상방과 하방 삼각행렬은 사실상 역원의 관계에 있는데 하나만 표현해 주면 안될까요? 그리고 사실 비교한 데이터를 입력하기도 까다롭습니다. 따라서 만약 A1, A2, A3를 비교한다면 A1가 A2보다 얼마나 중요한지, 또한 A1이 A3보다 얼마나 중요한지, 그리고 A2가 A3보다 얼마나 중요한지만 알면 됩니다. 이를 _ 로 표현하면 각각
가 되어 9번 써야 할 것이 3번으로 줄어듭니다.
이상의 내용을 보다 편리하게 구현하기 위해 함수로 정의하면 다음과 같습니다.
c.names <- c("A1","A2", "A3")
c1.df <- data.frame(
A1_A2 = 5,
A1_A3 = 9,
A2_A3 = 2
)
{ahpsurvey} 패키지 안의 ahp.mat()은 이와
같은 형식의 데이터를 AHP 계산에 필요한 정방행렬로 바꿉니다.
ahp.mat_c1 <- ahp.mat(c1.df,atts=c.names)
고유벡터를 계산합시다. ahp.aggpref() 함수는 위의 값을 입력값으로 취해 고유벡터 하나를 만들어냅니다.
lambda_c1 <- ahp.aggpref(ahp.mat_c1, c.names)
lambda_c1
## A1 A2 A3
## 0.76078869 0.15759649 0.08161482
쉽게 고유벡터가 만들어졌습니다. 데이터 입력을 좀더 편리하게 하려면 Excel을 사용하는 편이 좋습니다.
make_template()함수를 만들어서 입력에 필요한 입력 템플릿을 만들어
저장해보겠습니다. 사용법은 아래를 참고하십시오.
파일의 이름은 template.xlsx이라고 합시다.
#파일을 만들기 위한 함수
make_template <- function(fname, attrs) {
write_xlsx({
x=combn(attrs,2) %>% apply(2,paste0,collapse='_')
y=data.frame(t(numeric(length(x))))
names(y)=x
y
},fname)
}
#파일 만들기
make_template("template.xlsx",c.names)
엑셀 파일을 열어보면 다음과 같습니다.
앞서 설명했듯 기준_비교대상 이런식의 이름입니다. 예를
들어 A1_A2이면 A1안이 A2 안에 비해 얼마나 더 중요한가를 물어보는
것입니다. 값이 클수록 A1 안이 더 중요합니다. 쌍대 비교치의 선택 기준과
각각의 값은 다음과 같습니다.
| 선택 | 평가치 |
|---|---|
| 극히 중요 | 9 |
| 매우 중요 | 7 |
| 상당히 중요 | 5 |
| 다소 중요 | 3 |
| 비슷함 | 1 |
입력을 다음과 같이 한다.
음수는 상대적으로 중요성이 떨어짐을 의미합니다.
첫번째 A1_A2에서 값이 -7인데 이는 A1이 A2보다 상대적으로 덜 중요한, 즉
A2가 A1보다 7 정도 더 중요하다는 의미입니다. 현재의 평가기준인 C2는
환경성이므로 환경성 측면에서는 A2가 더 더 중요하고 그
크기는 7입니다.
데이터를 data_c2.xlsx 파일에 저장하고, R로 불러와서
사용해봅시다.
c2.df <- readxl::read_excel("data_c2.xlsx")
ahp.mat_c2 <- ahp.mat(c2.df,atts=c.names)
이제 C2의 고유벡터를 계산해보자.
lambda_c2 <- ahp.aggpref(ahp.mat_c2, c.names)
lambda_c2
## A1 A2 A3
## 0.08794621 0.66941687 0.24263692
마지막으로 C3의 데이터는 다음과 같다.
계산을 해보면 다음과 같다.
c3.df <- readxl::read_excel("data_c3.xlsx")
ahp.mat_c3 <- ahp.mat(c3.df,atts=c.names)
lambda_c3 <- ahp.aggpref(ahp.mat_c3, c.names)
lambda_c3
## A1 A2 A3
## 0.07505673 0.33321587 0.59172740
마지막으로 평가 기준에 대해서 평가해 봅니다.
ca.df <- readxl::read_excel("data_criteria.xlsx")
ahp.mat_ca <- ahp.mat(ca.df,atts=c("C1","C2","C3"))
lambda_ca <- ahp.aggpref(ahp.mat_ca, c("C1","C2","C3"))
lambda_ca
## C1 C2 C3
## 0.4664699 0.4330323 0.1004979
평가 기준 \(W\)를 가중치로 하여
다음과 같이 구성된 행렬과 곱해줍니다. C1 편리성: \((0.76,0.16, 0.08)\)
C2 환경성: \((0.08,0.70, 0.24)\)
C3 비용성: \((0.08,0.33, 0.59)\)
즉,
\[ R = \begin{pmatrix} 0.76 & 0.16 & 0.08 \\ 0.08 & 0.70 & 0.24 \\ 0.08 & 0.33 & 0.59 \end{pmatrix} \]
에 대해 기준치의 고유치 행렬인 \[ W = \begin{pmatrix} 0.46 \\ 0.43 \\ 0.1 \end{pmatrix} \] 에 대해
\[ Q = W^{t} \cdot R \] 을 계산한다.
우선도를 계산합시다.
prep.mat = rbind(lambda_c1,lambda_c2,lambda_c3)
t(lambda_ca) %*% prep.mat
## A1 A2 A3
## [1,] 0.4005116 0.3968806 0.2026078
의사결정의 우선도를 계산해보니, 1등은 A1, 2등은 A2입니다. 그러나 그 차이는 근소합니다. 결론적으로 A1이나 A2 대안은 비슷한 수준에서 중요하다고 볼 수 있고 둘 다 A3보다는 더 중요하게 생각되며 그 비율은 약 \(0.4/0.2 = 2\) 즉, 2배입니다.
이제 각 결정들의 consistency index (CI) 를 계산합시다. 계산에는 ahp.cr() 함수가 사용됩니다. 이 함수는 다음과 같은 방정식을 따릅니다. 이때 \(n\)은 파라미터의 개수입니다.
\[ C.I = \frac{\left( \sum{\lambda_{i}}-n \right)}{\left( n - 1 \right)} \]
cat("C1\t\t",ahp.cr(ahp.mat_c1,c("A1","A2","A3")),'\n')
cat("C2\t\t",ahp.cr(ahp.mat_c2,c("A1","A2","A3")),'\n')
cat("C3\t\t",ahp.cr(ahp.mat_c3,c("A1","A2","A3")),'\n')
cat("importance\t",ahp.cr(ahp.mat_ca,c("C1","C2","C3")),'\n')
Saty (1971)의 기준에 따르면 0.1 이하여야 문제가 없습니다. 그렇지만 혹시 CI가 높을 경우가 있으므로 사후 분석을 통해 어떤 기준의 쌍대 비교 데이터를 다시 얻을 필요가 있는지를 판단하면 좋습니다.
가장 이상적인 형태에서 모순되는 부분을 비교 발견하는 방식입니다. 이를 자동적으로 처리해주는 ahp.error() 함수를 사용해봅시다.
gm_mean <- function(x,na.rm=TRUE) {
exp(sum(log(x[x>0]),na.rm=na.rm)/length(x))
}
이제 이상치에 비해 얼마나 차이가 있는지를 확인합시다. 차이가 커질수록 모순이 심합니다. CI를 개선할 필요가 있다면 모순이 큰 것 순서대로 생각하면 됩니다.
응답자가 여러명일 경우를 가정하여 3차원 array 를 생성한 뒤에 각각의 열에 대한 기하평균을 적용하여 결과를 확인합니다. 이상치일 경우 1의 값을 가지기 때문에 차이를 나타내기 위해 마지막에 1을 빼줍니다.
test <- ahp.error(ahp.mat_c1,c.names,reciprocal = TRUE)
test %>%
unlist() %>%
as.numeric() %>%
array(dim=c(
length(c.names),
length(c.names),
1 #서베이 아이템의 개수를 입력한다.
)) %>%
apply(c(1,2),gm_mean) - 1
## [,1] [,2] [,3]
## [1,] -1.110223e-16 0.03574417 0.03574417
## [2,] 0.000000e+00 0.00000000 0.03574417
## [3,] 0.000000e+00 0.00000000 0.00000000
0.1을 넘는 것은 아무것도 없고 수정할 것도 없습니다.
해외 프로젝트 3개, 국내 프로젝트 3개가 있을 때 어떤 프로젝트를 가장 중요하게 생각해야 하는지, 무엇부터 고려해야 할 것인지를 결정합시다.
순서를 정리하면,
데이터를 입력해봅시다.
## 해외 프로젝트트
#장래성
foreign.c1 <- ahp.mat(
df=data.frame(
P1_P2=5,P1_P3=7,P2_P3=5
),
atts=c('P1','P2','P3')
)
#수익성
foreign.c2 <- ahp.mat(
df=data.frame(
P1_P2=1,P1_P3=-3,P2_P3=-5
),
atts=c('P1','P2','P3')
)
#정보성
foreign.c3 <- ahp.mat(
df=data.frame(
P1_P2=1,P1_P3=-5,P2_P3=-7
),
atts=c('P1','P2','P3')
)
국내 프로젝트의 경우에도 각각 데이터를 입력합니다.
## 국내 프로젝트트
#장래성
domestic.c1 <- ahp.mat(
df=data.frame(
P1_P2=5,P1_P3=3,P2_P3=-3
),
atts=c('P1','P2','P3')
)
#수익성
domestic.c2 <- ahp.mat(
df=data.frame(
P1_P2=5,P1_P3=-3,P2_P3=-7
),
atts=c('P1','P2','P3')
)
#정보성
domestic.c3 <- ahp.mat(
df=data.frame(
P1_P2=5,P1_P3=3,P2_P3=-3
),
atts=c('P1','P2','P3')
)
이제 각 프로젝트의 평가치를 정리합니다.
preference.foreign <- cbind(
ahp.aggpref(foreign.c1,atts=c('P1','P2','P3')),
ahp.aggpref(foreign.c2,atts=c('P1','P2','P3')),
ahp.aggpref(foreign.c3,atts=c('P1','P2','P3'))
)
colnames(preference.foreign) <- c("C1","C2","C3")
cat("해외 프로젝트:\n\n")
## 해외 프로젝트:
preference.foreign
## C1 C2 C3
## P1 0.71470956 0.1851740 0.1335586
## P2 0.21849437 0.1561818 0.1193885
## P3 0.06679607 0.6586442 0.7470528
preference.domestic <- cbind(
ahp.aggpref(domestic.c1,atts=c('P1','P2','P3')),
ahp.aggpref(domestic.c2,atts=c('P1','P2','P3')),
ahp.aggpref(domestic.c3,atts=c('P1','P2','P3'))
)
colnames(preference.domestic) <- c("C1","C2","C3")
cat("국내 프로젝트:\n\n")
## 국내 프로젝트:
preference.domestic
## C1 C2 C3
## P1 0.6369856 0.27895457 0.6369856
## P2 0.1047294 0.07192743 0.1047294
## P3 0.2582850 0.64911800 0.2582850
각각의 경우에 대한 consistency index를 계산합시다.
0.1을 넘지 않으면 됩니다.
data.frame(
description=c("해외-C1","해외-C2","해외-C3","국내-C1","국내-C2","국내-C3"),
consistency_index=c(
ahp.cr(foreign.c1,atts=c('P1','P2','P3')),
ahp.cr(foreign.c2,atts=c('P1','P2','P3')),
ahp.cr(foreign.c3,atts=c('P1','P2','P3')),
ahp.cr(domestic.c1,atts=c('P1','P2','P3')),
ahp.cr(domestic.c2,atts=c('P1','P2','P3')),
ahp.cr(domestic.c3,atts=c('P1','P2','P3'))
)
) %>%
mutate(good_to_go=consistency_index<0.1)
해외 C1의 경우에 문제가 됨을 알 수 있습니다.
#해외
foreign.ca <- ahp.mat(
df=data.frame(
C1_C2=-4,C1_C3=1,C2_C3=6
),
atts=c('C1','C2','C3')
)
#국내
domestic.ca <- ahp.mat(
df=data.frame(
C1_C2=5,C1_C3=2,C2_C3=-3
),
atts=c('C1','C2','C3')
)
weight.foreign <- ahp.aggpref(foreign.ca,atts=c('C1','C2','C3'))
weight.domestic <- ahp.aggpref(domestic.ca,atts=c('C1','C2','C3'))
cat("해외 프로젝트의 평가 가중치:\n")
## 해외 프로젝트의 평가 가중치:
print(weight.foreign)
## C1 C2 C3
## 0.1549802 0.7096321 0.1353877
cat("\n\n")
cat("국내 프로젝트의 평가 가중치:\n")
## 국내 프로젝트의 평가 가중치:
print(weight.domestic)
## C1 C2 C3
## 0.5815521 0.1094523 0.3089956
#해외 프로젝트의 우선도
preference.foreign %*% weight.foreign
## [,1]
## P1 0.2602534
## P2 0.1608577
## P3 0.5788889
#국내 프로젝트의 우선도
preference.domestic %*% weight.domestic
## [,1]
## P1 0.5977983
## P2 0.1011392
## P3 0.3010626
프로젝트 그룹의 중요도는 해외가 0.6, 국내가 0.4로 알려져 있다고 합시다. 따라서,
result <- cbind(
preference.foreign %*% weight.foreign * 0.6,
preference.domestic %*% weight.domestic * 0.4
)
print(result)
## [,1] [,2]
## P1 0.1561521 0.23911930
## P2 0.0965146 0.04045567
## P3 0.3473333 0.12042502
이상의 결과를 바탕으로 등수를 계산해봅시다.
nrow(result) * ncol(result) + 1 - rank(result) %>%
matrix(ncol=2)
## [,1] [,2]
## [1,] 3 2
## [2,] 5 6
## [3,] 1 4
해외 3번째가 1등입니다. 국내 1번 프로젝트가 2등입니다. 다음으로 해외
1번째가 3등입니다.
이러한 순서로 프로젝트를 고려하면 좋겠습니다.
서베이 데이터를 입력했을 때의 코딩 방식도 다를 것이 별로
없습니다.
예를 들어 데이터를 다음과 같이 입력했다고 했을 때(data_survey.xlsx
파일)
응답자가 4명입니다. 이들에 대한 preference 가중치를 계산해봅니다.
survey.test <- ahp.mat(
df=readxl::read_excel("data_survey.xlsx"),
atts=c("C1","C2","C3")
)
survey.test.pref <- ahp.aggpref(
survey.test,
atts=c("C1","C2","C3")
)
survey.test.pref
## C1 C2 C3
## 0.48592152 0.40722200 0.09851576