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