距離行列が与えられている時に、その行列を内積の値に変換する。
そもそも距離行列とは
a b c d e
a 0,4,6,4,4
b 4,0,5,3,3
c 6,5,0,3,1
d 4,3,3,0,3
e 4,3,1,3,0
というような形で与えられ、上の例ではa~eのお互いの距離が記された行列である。
この距離行列が用いられる場面では、ここから多次元尺度構成法などで、二次元にプロットなどが行われる。実際にプロットを行うためには、この距離行列を内積の値に変換し、固有ベクトルを求めることで座標を得る必要があるが、シェルスクリプトで内積の値への変換を行ってみる。
内積への変換は、まず、距離行列のそれぞれの成分を2乗して2で割ったものをそれぞれの成分に置き換え、それを新たな距離行列とし、距離行列の名前をmatrix、各行の平均をそれぞれ、ave_1,ave_2,ave_3....、matrix全ての値の平均をall_ave、matrixの成分をmatrix[1,2]などとした時に、各行列の成分を以下のように変換することでなされる。
なお、以下の "=" は代入
matrix[x,y]=ave_x + ave_y - all_ave - matrix[x,y]
標準入力として、以下のようなカンマ区切りの行列を想定して、内積値への変換スクリプトを書いてみる。
0,4,6,4,4
4,0,5,3,3
6,5,0,3,1
4,3,3,0,3
4,3,1,3,0
# !/bin/bash
# 標準入力を変数へ
data=$(cat -)
# 全値の平均を変数へ、各行の平均を配列へ
all=$(echo "$data" | tr ',' '\n' | awk '{sum+=$1*$1/2}END{print sum/NR}')
temp=$(echo "$data" | awk -F, '{sum=0;for(i=1;i<=NF;i++){sum+=$i*$i/2};print sum/NF}' | sed '/^$/d' | grep -n ".")
# 上記の値を用いてデータを変更
row=1
col=1
echo "$data" |
while read line_1;do
echo "$line_1" |
tr ',' '\n' |
sed '/^$/d' |
while read line_2;do
m1=$(echo "$temp" | awk -F : -v num="$row" '$1==num{print $2}')
m2=$(echo "$temp" | awk -F : -v num="$col" '$1==num{print $2}')
val=$(echo "$m1 + $m2 - $all - ${line_2} * ${line_2} / 2" | bc)
echo "$val"
col=$(($col + 1))
done | tr '\n' ','
echo ""
row=$(($row + 1))
done | sed 's/,$//g' | sed -e 's/-\./-0\./g' -e 's/^\./0\./g' -e 's/,\./,0\./g'
シェルスクリプトでやる意味があるのか?(無いと思う)
そもそもこのシェルスクリプト、無駄が多くね?(かなり適当に書いてる)
awkで計算ってことはでかい数字だと計算ズレね?(だよねー!)