2012年 12月 1日掲載
「AWK,それは最後の今でもフロンティア」
AWK でいろいろする第4回である. 今回は,移動平均によるデータの平滑化を行ってみる.
まずは,サンプルデータ作りである. 振幅 2 のサインカーブのデータ data0.txt を作って, これに乱数で誤差を加え data1.txt を作る.
$ awk 'BEGIN{for(x=0;x<=6.0;x+=0.25)print 2*sin(x)}' > data0.txt
$ awk '{print $1+rand()-0.5}' data0.txt > data1.txt
さて,できあがったデータは,どんな風になっているだろうか. グラフを描いて見てみたい.
ということで,簡単なグラフを描く AWKスクリプトを作ってみた.
作成する画像ファイルは,PBMフォーマットとした. このフォーマットはアスキーファイルなので AWK でもなんとかできる. 1次元のデータを読みこんで, 320x240 のサイズを目いっぱい使って点をプロットする AWKスクリプトが, 以下の plot1pbm.awk である.
BEGIN{
width = 320
height = 240
for(i = 0; i < width * height; i++){
p[i] = 0;
}
}
{
d[NR] = $1
}
END{
if (NR <= 1){
printf "P1\n1 1\n0\n"
exit
}
dmin = dmax = d[1]
for(i in d){
if(d[i] < dmin) dmin = d[i]
if(d[i] > dmax) dmax = d[i]
}
if(dmin == dmax) dmax = dmin + 1
for(i in d){
ix = int((width - 1) * (i - 1) / (NR - 1))
iy = int((height - 1) * (d[i] - dmin) / (dmax - dmin))
p[ix + (height - 1 - iy) * width] = 1
}
print "P1"
print width, height
for(i = 0; i < width * height; i++){
printf "%d ", p[i]
}
printf "\n"
}
さっそく,data1.txt を図化してみる.
$ awk -f plot1pbm.awk data1.txt > data1.pbm

う〜ん,よく見るとたしかに点はプロットされてるけど,やっぱり見にくいなあ. ちょっとがんばって,データを線で結んでみよう (線分の描画には,ブレゼンハムのアルゴリズムを使う).
BEGIN{
width = 320
height = 240
for(i = 0; i < width * height; i++){
p[i] = 0;
}
}
{
d[NR] = $1
}
END{
if (NR <= 1){
printf "P1\n1 1\n0\n"
exit
}
dmin = dmax = d[1]
for(i in d){
if(d[i] < dmin) dmin = d[i]
if(d[i] > dmax) dmax = d[i]
}
if(dmin == dmax) dmax = dmin + 1
ix1 = 0
iy1 = int((height - 1) * (d[1] - dmin) / (dmax - dmin))
for(i = 2; i <= NR; i++){
ix2 = int((width - 1) * (i - 1) / (NR - 1))
iy2 = int((height - 1) * (d[i] - dmin) / (dmax - dmin))
line(ix1, iy1, ix2, iy2)
ix1 = ix2
iy1 = iy2
}
print "P1"
print width, height
for(i = 0; i < width * height; i++){
printf "%d ", p[i]
}
printf "\n"
}
function line(x1, y1, x2, y2)
{
dx = x2 - x1
dy = y2 - y1
sx = 1
sy = 1
if (dx < 0){
dx *= -1
sx = -1
}
if (dy < 0){
dy *= -1
sy = -1
}
p[x1 + (height - 1 - y1) * width] = 1
if (dx > dy){
de = dx / 2
for(j = dx; j > 0; j--){
x1 += sx
de += dy
if (de > dx){
de -= dx
y1 += sy
}
p[x1 + (height - 1 - y1) * width] = 1
}
}else{
de = dy / 2
for(j = dy; j > 0; j--){
y1 += sy
de += dx
if (de > dy){
de -= dy
x1 += sx
}
p[x1 + (height - 1 - y1) * width] = 1
}
}
}
$ awk -f plot1pbm.awk data1.txt > data1a.pbm

うん,これなら見やすくて,いいんじゃないかな. スクリプトは,ごちゃごちゃして読みづらくなっちゃったけど (なんか BASIC時代のプログラムみたい…).
さて,本題に入ろう. 上のばらつきのあるデータ data1.txt を,移動平均を使って平滑化してみる.
N個のデータ y1, y2, ... ,yN を,隣接する 3点を使って移動平均した値 y~ は,
である.これくらい,AWK で計算するのは簡単である. ささっと書いた AWKスクリプト moving_average.awk は以下のとおり.
NR==1{
y1 = $1
}
NR==2{
y2 = $1
}
NR>2{
y3 = $1
print (y1 + y2 + y3) / 3
y1 = y2
y2 = y3
}
これに,data1.txt をつっこむと,
$ awk -f moving_average.awk data1.txt > data2.txt $ awk -f plot1pbm.awk data2.txt > data2.pbm

もとの data1.txt と比べると,移動平均した data2.txt は, なめらかになっているのがわかる.
が,このスクリプトには大きな問題がある. データの数が減ってしまうのだ.
$ wc -l data1.txt data2.txt
25 data1.txt
23 data2.txt
48 total
data1.txt では 25個あったデータが,data2.txt では 23個に減っている. これは,y~j = ( yj-1 + yj + yj+1 ) / 3 では, 両端の y~1 と y~N を計算することができないからである. この,データ列の両端の値を求める方法は,次回に解説することにする.
今回の AWKスクリプト:
過去の記事:
[ KDO ホームにもどる]
(C) 2012 KDO