目次
いくつかある代替案の中から一つの案に絞り込む場合、いろいろな評価基準の下で各代替案のウェイトを決め、それらに評価基準の重みを付けて各代替案の総合ウェイトを決める、という方法が考えられる。代替案を X, Y, Z、評価基準を A, B, C とし、評価基準 u の下で代替案のウェイト比を wuX : wuY : wuZ とする。また、評価基準のウェイト比を wA : wB : wC としたとき、代替案 X の総合ウェイトは wA wAX + wB wBX + wC wCX によって与えられるので、そのウェイトが最も大きな代替案を選択する、というものである。
評価基準によっては、それだけでは代替案のウェイト比を求めることが難しいという場合は、その評価基準をさらに細かな基準に分け、細分化された評価基準の下での代替案のウェイト比を決めてそれらを細分化された評価基準のウェイト比で重みを付けて最初の評価基準の下での代替案のウェイト比とする。このように、評価基準を階層化してより正確なウェイト比を求めることができるので、納得のいく意思決定が可能になった。このような意思決定の方法を階層的意思決定法という。
この意思決定が有効であるために、ウェイト比の正確な算出が欠かせない。特に評価基準のウェイトについては、全く質の違う幾つかの評価基準を一列に並べてウェイトを付けるのは信頼度が低くなる可能性が大きい。それに加えて、ウェイトを数値で表すことにも困難が伴う。しかし、2つだけを取り出してどちらを重視するか、その程度はどれくらいか、という質問であれば、回答可能であろう。
そこで、各段階でウェイトを決めるために一対比較を繰り返し、それらの結果に適合するウェイトを計算する、という方法を考える。一対比較はどちらをどれくらい(とても、かなり、やや、のように)重要視するのか、という感覚的なもので構わない。
階層的意思決定法で代替案のウェイトを決めるおおよその手順は以下のとおり:
評価基準のウェイトを決めることがむづかしい場合は、2つの評価基準「だけ」(A, B としよう)を比べて、どちらをどの程度重要視するか決める、という一対比較を利用する。重要視する度合いを数値で表現するのは困難である場合に備えて、言葉で、「とても」重要、「かなり」重要、(普通に)重要、「やや」重要のように表現したものを選んでもらうようにすると答えやすい。
R で処理する場合、これらのデータを文字で入力するのは煩わしいので、選択肢方式で、その番号を入力させるようにしたのが次のプログラム例である。scan
関数は実行時にキーボードからデータを入力するために使われている。n= オプションは入力するデータの個数を指定している。実行例は、車の選択を想定して、「経済性」「機能」「環境(にやさしい)」の3つの評価基準にウェイト付けを行っている。
pairComparison = function(n, crt) { pcrt = matrix(0, n*(n-1)/2, 3) k = 0 for(i in 1:(M-1)) for(j in (i+1):M) { k = k+1 pcrt[k,1] = crt[i] pcrt[k,2] = crt[j] } cat("A 対 B の比較:AはBに比べてとても重要(1)、かなり重要(2)、重要(3)、やや重要(4)、同程度(5)") cat("\n :BはAに比べてとても重要(9)、かなり重要(8)、重要(7)、やや重要(6)\n") for(i in 1:(n*(n-1)/2)) { cat(pcrt[i,1], "対", pcrt[i,2], " : ") pcrt[i,3] = scan(n=1) } return(pcrt) }
# 実行例
> crt = c("経済性", "機能", ",環境") > M = length(crt) > (pcrt = pairComparison(3,crt)) A 対 B の比較:AはBに比べてとても重要(1)、かなり重要(2)、重要(3)、やや重要(4)、同程度(5) :BはAに比べてとても重要(9)、かなり重要(8)、重要(7)、やや重要(6) 経済性 対 機能 : 1: 6 Read 1 item 経済性 対 環境 : 1: 1 Read 1 item 機能 対 環境 : 1: 3 Read 1 item [,1] [,2] [,3] [1,] "経済性" "機能" "6" [2,] "経済性" "環境" "1" [3,] "機能" "環境" "3"
ウェイトを計算するために、一対比較データを一対比較行列と呼ばれる行列にまとめる。i 番目の評価基準の j 番目の評価基準に対するウェイト比を i 行 j 列の要素とする行列を一対比較行列という。j 行 i 列の要素は i 行 j 列要素の逆数になっていることに注意する。また、対角要素は 1 とする。
上で入力した一対比較のデータを一対比較行列にまとめるプログラムの例を下に示す。一対比較の言葉による9段階の評価を 9, 7, 5 3, 1, 1/3, 1/5, 1/7, 1/9 に置き換えている。as.numeric
関数は文字データを数値に置き換える。rownames, colnames
関数は行列の行、列に名前を付与する。
pairComMat = function(n, crt, pc) { PCM = matrix(1, n, n) q = as.numeric(pc[,3]) for(k in 1:nrow(pc)) { i = which(crt == pc[k,1]) j = which(crt == pc[k,2]) m = c(9,7,5,3,1,1/3,1/5,1/7,1/9)[q[k]] PCM[i,j] = m PCM[j,i] = 1 / m } rownames(PCM) = colnames(PCM) = crt return(PCM) }
# 実行例
> (w = pairComMat(M, crt, pcrt)) 経済性 機能 環境 経済性 1.0 0.333 5 機能 3.0 1.000 9 環境 0.2 0.111 1
もし評価基準の絶対的なウェイトがわかっているならば、一対比較行列の各要素は、それらのウェイト比になっているはずである。すなわち、i 番目の評価基準の絶対的なウェイトを wi としたとき、一対比較行列(P としよう)の i,j 要素は wi / wj に等しい。P に右からウェイトベクトル w = (wi)を掛けると、w の行列の次数(n としよう)倍になる。すなわち、ウェイトベクトルは行列 P の固有値 n に対する固有ベクトルになっており、n は P の最大固有値であることがわかる(ペロンフロベニウスの定理)。
現実の問題ではこのような理想的なウェイト比は期待できないにしても、同じような判断を下すにちがいないと考え、一対比較行列の最大固有値に対する固有ベクトルを求めることで未知のウェイトベクトルの代わりにしよう、と考えるのは十分に合理的である。さらに、その代替ベクトルの信頼性を測る尺度として、最大固有値と n の差を使うことももっともらしい。ただし、その差は行列の大さに依存すると思われるので、最大固有値を λmax として (λmax - n) / (n-1) を理想的一対比較との差の評価値とし、一対比較(行列)の整合度と呼ぶ。
R では eigen
関数で固有値と固有ベクトルを計算することができる。eigen$values
は固有値のベクトルで、先頭が最大固有値になっている。eigen$vectors
は列固有ベクトルを並べて行列にしたもので、1列目が最大固有値に対する固有ベクトルになっている。次のプログラム例は、固有値と固有ベクトルを使ってウェイトと整合度の値を計算するものである。最大固有値は実数で、それに対応する固有ベクトルも実数になるが、eigen
関数の計算は複素数で実行されるので、場合によっては結果に +0i
という虚数部のついた複素数で返される。間違いではないが、煩わしいので、as.numeric
関数を使って実数部だけを取り出す。整合度は大きくても 0.15 以下という経験則があるので、それを超える場合は、注意喚起のメッセージを表示させるようにしている。有効桁の数字をあまり多く表示しても意味がないので、事前に options(digits=3)
というような命令を実行しておくと良い。
weightV = function(PCM, crt) { eig = eigen(PCM) # 固有値、固有ベクトルの計算 w = as.numeric(eig$vectors[,1]) wt = w / sum(w) # ウェイトの計算 names(wt) = crt # 名前をつける CI = as.numeric((eig$value[1] - nrow(PCM)) / (nrow(PCM) - 1)) # 整合度の計算 if(CI > 0.15) { cat("整合度 =", CI, "一対比較がおかしい??\n") } else if(CI > 0.10) { cat("整合度 =", CI, "一対比較があやしい?\n") } return(list(ウェイト=wt, 整合度=CI)) }
# 実行例> options(digits=3)
> (W = weightV(Q, crt)) $ウェイト 経済性 機能 環境 0.2654 0.6716 0.0629 $整合度 [1] 0.0145
固有値、固有ベクトルが簡単に計算できない場合、それらを使わないで簡易的に計算する方法として幾何平均を使う方法がよく用いられる。評価基準の数が3の場合は、両者の結果は等しく、基準数が増えた場合でも、一対比較行列の各行の幾何平均を計算したものは最大固有値に対する固有ベクトルの近似として使える、ということから考えられたものである。行ごとに幾何平均を計算するためには、幾何平均の関数を定義して apply
関数を使えば良い。整合度の計算のための一対比較行列と「理想的一対比較行列」の要素ごとの比の合計は、一対比較行列にウェイトベクトルの逆数を左から、ウェイトベクトルを右から掛けることで求めることができる。
weightVg = function(PCM, crt) { w = apply(PCM, 1, function(x) prod(x)^(1/nrow(PCM))) wt = w / sum(w) # ウェイトの計算 names(wt) = crt n = length(wt) CI = ((1/wt) %*% PCM %*% wt / n^2 - 1) * n / (n - 1) # 整合度の計算 if(CI > 0.15) { cat("整合度 =", CI, "一対比較がおかしい??\n") } else if(CI > 0.10) { cat("整合度 =", CI, "一対比較があやしい?\n") } return(list(ウェイト=wt, 整合度=CI)) }
# 実行例
> weightVg(Q, crt)
$ウェイト 経済性 機能 環境 0.2654 0.6716 0.0629 $整合度 [1] 0.0145
幾何平均を使った場合の近似精度については、実験的に確かめることができる。次のプログラム例は、評価基準のウェイトがわかっているという条件で理想的な一対比較行列を作り、それにノイズを入れたもの(v 倍から 1/v 倍までの値をランダムに選んで掛けたものをウェイト比とする)を一対比較行列として、固有値法と幾何平均法でウェイトを計算し、その差をユークリッド距離で測る。その時の整合度指標も同時に計算する。upper.tri(D)
は行列 D の上三角行列要素を TRUE、それ以外を FALSE とする論理値行列を与える。t(D)
は行列 D の天地行列を与える。apply(B, 1, f)
は行列 B の各行に関数 f を適用した結果を返す。この場合は、各行の幾何平均値を計算している。
geomeantest = function(n = 4, v = 2) { # ノイズ倍率行列([1/v,v]) D = matrix(1, n, n) D[upper.tri(D)] = runif(n*(n-1)/2, 1/v, v) D[lower.tri(D)] = t(1/D)[lower.tri(t(1/D))] # 理想的一対比較行列+ノイズ wt = sample(1:n, n, replace=T) B = (wt %*% t(1/wt)) * D # 固有値法 eigb = eigen(B) b = as.numeric(eigb$vectors[,1]) # 幾何平均近似 c = apply(B, 1, function(x) prod(x)^(1/n)) # 誤差評価(ユークリッド距離)+整合度指標 return(c(sqrt(sum((b/sum(b) - c/sum(c))^2)), (eigb$values[1]-n)/(n-1))) }
# 実行例
> geomeantest(4,3) [1] 0.017716499+0i 0.095649557+0i
> zz = sapply(rep(5,100), geomeantest, v=3) > plot(zz[1,], zz[2,]) > abline(h=0.15) > abline(h=0.1, lty=2)
誤差と整合度を散布図にしたのが次の図である。項目数が5の場合でも、整合度が 0.15 以下ならば、十分な近似精度があることを示している。
以上のプロセスを一つの関数にまとめたのが次のプログラム例である。
calWeight = function(crt) { M = length(crt) pcrt = pairComparison(M, crt) # 一対比較表 Q = pairComMat(M, crt, pcrt) # 一対比較行列 return(weightV(Q, crt)) # ウェイトと整合度計算 }
# 実行例
> crt = c("経済性", "機能", "環境") > calWeight(crt) A 対 B の比較:AはBに比べてとても重要(1)、かなり重要(2)、重要(3)、やや重要(4)、同程度(5) :BはAに比べてとても重要(9)、かなり重要(8)、重要(7)、やや重要(6) 経済性 対 機能 : 1: 6 Read 1 item 経済性 対 環境 : 1: 1 Read 1 item 機能 対 環境 : 1: 3 Read 1 item 一対比較がおかしい?? $ウェイト 経済性 機能 環境 0.34424 0.58865 0.06711 $整合度 [1] 0.1622 # もう一つの実行例 > calWeight(crt) A 対 B の比較:AはBに比べてとても重要(1)、かなり重要(2)、重要(3)、やや重要(4)、同程度(5) :BはAに比べてとても重要(9)、かなり重要(8)、重要(7)、やや重要(6) 経済性 対 機能 : 1: 4 Read 1 item 経済性 対 環境 : 1: 1 Read 1 item 機能 対 環境 : 1: 3 Read 1 item $ウェイト 経済性 機能 環境 0.67163 0.26543 0.06294 $整合度 [1] 0.01453
評価基準のウェイト算出ができれば、あとは各評価基準ごとに代替案のウェイト付けを行い、それらから総合ウェイトを計算すれば良い。各評価基準ごとに代替案をウェイト付けする手順は、上で説明した評価基準のウェイト付けと全く同じである。従って、一対比較データからウェイト計算までの手順が関数化されていれば、それらを組み合わせれば基礎データが揃うので、総合ウェイトの計算は容易である。 二つの工夫を取り入れる。
一番目は、整合度指標に関するものである。整合度指標が大きな値になる場合は、その先を計算しても意味がないので、一対比較から再入力させることにする。これは、上のプログラム calWeight
にちょっと手を加えるだけで実現できる。repeat ... if break
構文は、if 文の条件が成り立たない限りフィードバックを繰り返す。戻り値はウェイトベクトルだけにする。
calWeight = function(crt) { M = length(crt) repeat { pcrt = pairComparison(M, crt) # 一対比較表 Q = pairComMat(M, crt, pcrt) # 一対比較行列 w = weightV(Q, crt) # ウェイトと整合度計算 if(w$整合度 < 0.15) break # 0.15 を超えていなければ受け入れる } return(as.numeric(w$ウェイト)) }
二番目は一対比較の作業量軽減である。一対比較を繰り返すのはかなりの作業量になる。評価基準によっては、代替案のウェイト比は直接評価可能かもしれないので、そのような選択肢を付けて入力を簡略化する。「1:2:3」のようにコロン区切りで比を入力してもらい、それをウェイト比に置き換える。scan
関数の what="character"
オプションは、入力を文字列として受け取るという指定である。strsplit
関数は文字列を(この場合は ":"
で)分解する。結果は list
構造になるので、それを unlist
関数で文字列ベクトルに直し、as.numeric
関数で数値データに置き換える。
calWeighta = function(crt) { cat("1: 直接ウェイト入力、2: 一対比較?") # 入力方法選択 m = scan(n=1) if(m == 1) { cat("ウェイト比入力(1:2:3のように):", crt) # ウェイト比直接入力 x = scan(n=1, what="character") xx = as.numeric(unlist(strsplit(x, ":"))) return(xx / sum(xx)) } calWeight(crt) # 一対比較実施 }
これらの工夫を取り入れた総合評価値算出のプログラム例を次に示す。ここでは評価基準は1階層の場合を扱う。
AHPa = function(crt, altern) { cat("評価基準のウェイト付け:", crt, "\n") wcrt = calWeighta(crt) walt = matrix(1, length(crt), length(altern)) for(i in 1:length(crt)) { cat(crt[i], "基準の下での代替案のウェイト付け:", altern, "\n") walt[i,] = calWeighta(altern) } wei = as.vector(wcrt %*% walt) names(wei) = altern names(wcrt) = crt rownames(walt) = crt colnames(walt) = altern return(list(評価値=wei, 評価基準=wcrt, 代替案=walt)) }
実行例を載せる。
> options(digits=4) > crt = c("経済性", "機能", "環境") > altern = c("トヨタ", "ニッサン", "ベンツ")
> ahp = AHPa(crt, altern)
評価基準のウェイト付け: 経済性 機能 環境1: 直接ウェイト入力、2: 一対比較? 1: 2 Read 1 item A 対 B の比較:AはBに比べてとても重要(1)、かなり重要(2)、重要(3)、やや重要(4)、同程度(5) :BはAに比べてとても重要(9)、かなり重要(8)、重要(7)、やや重要(6) 経済性 対 機能 : 1: 3 Read 1 item 経済性 対 環境 : 1: 1 Read 1 item 機能 対 環境 : 1: 4 Read 1 item 経済性 基準の下での代替案のウェイト付け: トヨタ ニッサン ベンツ 1: 直接ウェイト入力、2: 一対比較? 1: 1 Read 1 item ウェイト比入力(1:2:3のように): トヨタ ニッサン ベンツ 1: 4:5:1 Read 1 item 機能 基準の下での代替案のウェイト付け: トヨタ ニッサン ベンツ 1: 直接ウェイト入力、2: 一対比較? 1: 1 Read 1 item ウェイト比入力(1:2:3のように): トヨタ ニッサン ベンツ 1: 3:2:4 Read 1 item 環境 基準の下での代替案のウェイト付け: トヨタ ニッサン ベンツ 1: 直接ウェイト入力、2: 一対比較? 1: 1 Read 1 item ウェイト比入力(1:2:3のように): トヨタ ニッサン ベンツ 1: 5:3:1 Read 1 item
>ahp
$評価値 トヨタ ニッサン ベンツ 0.3991 0.4388 0.1622 $評価基準 経済性 機能 環境 0.75140 0.17818 0.07042 $代替案 トヨタ ニッサン ベンツ 経済性 0.4000 0.5000 0.1000 機能 0.3333 0.2222 0.4444 環境 0.5556 0.3333 0.1111
階層的意思決定法を適用して最適な代替案が見つかったとしても、その算定根拠は質的な一対比較や、アバウトなウェイト比に基づいており、その曖昧さがどの程度最適案に影響を与えているのかを知りたいところである。一対比較の評価の逆転はないとしても、「かなり重要」が「やや重要」に変わる程度のことはありうるかもしれない。そうだとしても最終的な最適案に影響を与えないとすれば、この「あいまいな」意思決定でも説得力があると言えるのではないか。
このように、意思決定に影響を与える要因の変化が最適解に与える影響を調べることを感度分析という。ここでは、ある評価基準に着目して、他の評価基準の相対的評価は変わらない、という条件の下で着目した評価基準のウェイトが増減した時に最適代替案がどのように変わるかを調べてみよう。
評価基準が m 個、代替案が n 個、評価基準の階層は作らない、という単純なケースを考える。評価基準を C1, C2, ..., Cm、代替案を A1, A2, ..., An、階層的意思決定法による評価基準のウェイトを w1, w2, ..., wm、評価基準 Ci の下で代替案 Aj のウェイトを Vij とする。このとき、{Vij} と、評価基準 C1 を除いた他の評価基準のウェイト比 w2 : ... : wm は変わらないまま、w1:1- w1 を変えたら最適案はどうなるのか? というのが問題である。
代替案 Aj の総合評価値(ウェイト、Qj と書く)は Qj = Σi wi Vij = w1 V1j +Σi≠1 wi Wij で与えられるが、右辺第2項を 1- w1 で割ったものは w1 と無関係な定数(C と書く)になることから、
Qj = w1 V1j + C(1- w1)
となり、w1 に関する一次関数になる。もし、w1 をちょっと変えただけでは Q1, Q2, ..., Qn の相対順位が変わらなければ、w1 の決定にそれほど気を使う必要はない、ということが言える。
上の数値例で、この考え方を適用し、感度分析を実施する。w1 の関数(一次式)として、Q1, Q2, Q3 のグラフを同じ座標軸で表示すると変化がわかりやすい。
> w = ahp$評価値 > V = ahp$代替案 > i = 1 > (C = w[-i] %*% V[-i,] / (1 - w[i])) トヨタ ニッサン ベンツ [1,] 0.3933 0.2522 0.3545 > plot(0, 0, xlim=c(0,1), ylim=c(0,0.8), type="n", xlab=paste("w",i), ylab="総合ウェイト", + main=paste(crt[i],"基準の感度分析")) > lines(c(0, 1), c(C[1], V[i,1])) > lines(c(0, 1), c(C[2], V[i,2]), col=2) > lines(c(0, 1), c(C[3], V[i,3]), col=3) > legend(0.5, 0.8, altern, lty=1, col=1:3)
実行例を載せる。この場合は、経済性基準のウェイトがおおよそ 0.6 を下回らない限り、ニッサンの優位は変わらない、ということがわかる。他の2つの基準についても、上のプログラム例の i=1 を i=2, i=3 に変えるだけで実施できる。
評価基準が3個ならば、2つの基準のウェイトを同時に変える感度分析も可能である。代替案の総合ウェイトは、評価基準のウェイトの合計が1になることから Qj = Σi wi Vij = w1(V1j - V3j)+w2(V2j - V3j)+V3j で与えられる。そこで、 w1 - w2 座標平面に Q1 = Q2, Q1 = Q3, Q2 = Q3 という直線を引くと、例えば 代替案 A1 が最適となる w1, w2 の領域を知ることができる。
abline
関数は、y 切片と傾きを引数として直線を書く低水準作図関数である。srt=
オプションはテキストの傾きを指定し、pos=3
は指定座標より上に描くことを指定する。
> plot(c(0,1), c(1,0), type="l", lwd=2, asp=1, xlab=crt[1], ylab=crt[2]) > abline(v=0); abline(h=0) > border = function(i, j, m) { + c = V[2,i] - V[3,i] - (V[2,j] - V[3,j]) + a = (V[3,j] - V[3,i]) / c + b = -(V[1,i] - V[3,i] - V[1,j] + V[3,j]) / c + abline(a, b, lty=m) + eq = if(c > 0) ">" else "<" + text(0.6, a+0.6*b, paste(altern[i],eq,altern[j]), srt=atan(b)/pi*180, pos=3) + } > border(1, 2, 1); border(1, 3, 2); border(2, 3, 3) > points(ahp$評価基準[1],ahp$評価基準[2], pch=16)
実行例を載せる。経済性基準と機能基準のウェイトの合計は1以下なので、ウェイトを計算すると、図の太線の下の三角形の領域のどこかに収まる。黒丸が現在の評価で、「ニッサン>トヨタ>ベンツ」の領域にあり、ニッサンが最適解になっているが、経済性基準の評価が下がると徐々に「トヨタ>ニッサン>ベンツ」の領域に移行し、最適解がトヨタに変わる。しかし、機能基準が絶対的に大きくならない限り、ベンツの選択はあり得ない、ということがわかる。
一対比較の判断の整合性をチェックするための一つとして、全くでたらめな一対比較に基づいて計算された整合度(の平均値)よりどれだけ小さいかを調べる方法がある。
一対比較行列の上半分を 9, 8, ..., 1, 1/2, ..., 1/9 からランダムに抽出した数で埋め、その逆数を対角線に対象な要素に代入したものをランダム一対比較行列という。その整合度(ランダム整合度という)は、一対比較行列の最大固有値から計算できる。次は、ランダム整合度を計算する関数の例である。
> randomCI = function(n = 5, M = 9) { + Y = matrix(sample(c(1:M, 1/(2:M)), n*n, replace = T), n) + Y[lower.tri(Y)] = t(1 / Y)[lower.tri(Y)] + diag(Y) = 1 + return(list(CI=as.numeric((eigen(Y)$values[1] - n) / (n-1)), matrix=Y)) + } > randomCI() $CI [1] 1.689681 $matrix [,1] [,2] [,3] [,4] [,5] [1,] 1 0.2000000 0.500 0.3333333 0.1111111 [2,] 5 1.0000000 9.000 0.1250000 0.1111111 [3,] 2 0.1111111 1.000 0.1428571 8.0000000 [4,] 3 8.0000000 7.000 1.0000000 0.3333333 [5,] 9 9.0000000 0.125 3.0000000 1.0000000
lower.tri
関数は、行列の下三角行列の要素を TRUE それ以外を FALSE とする、引数行列と同じサイズの論理値行列である。t
関数は引数行列の転置行列を返す。 diag
関数は引数行列の対角要素ベクトルを返す。eigen
関数は引数行列の固有値と固有ベクトルを返す。固有値は $values
で取り出すことができ、大きさの順に並んでいるので、最大固有値は添え字1で参照できる。
この計算を多数回繰り返し、ランダム整合度の期待値を推定することができる。
> N = 10000 > z = sapply(rep(5, N), randomCI) > c(mean(unlist(z[1,])), sd(unlist(z[1,]))/sqrt(N)) [1] 1.114994954 0.005104764
基準の数を3から8まで動かして、それぞれの場合のランダム整合度の推定値を求める。
基準の数 | 3 | 4 | 5 | 6 | 7 | 8 |
ランダム整合度 | 0.527 | 0.884 | 1.108 | 1.249 | 1.341 | 1.405 |