AWK で十分ですよ - その2 - 分散,標準偏差,度数分布

2011年 8月 1日 掲載


「AWK で十分ですよ,わかってくださいよ」

の,第2回である. 今回も基本的な統計量をあつかう.


まずは,サンプルデータ作りである. 今回は 正規乱数列を作る. Box-Muller法で n個の正規乱数列を作るスクリプト normal_random_numbers.awk は,以下のように書ける.

BEGIN{
  pi = atan2(0, -1)

  while(i < n){
    x1 = rand()
    x2 = rand()

    if(x1 != 0){
      print sqrt(-2 * log(x1)) * cos(2 * pi * x2)
      i++

      if(i >= n) break

      print sqrt(-2 * log(x1)) * sin(2 * pi * x2)
      i++
    }
  }
}

このスクリプトを実行するときは, -vオプションで,乱数の個数 n を指定する必要がある. 今回は,100万個の乱数列 nrand.txt を作る.

$ awk -f normal_random_numbers.awk -v n=1000000 > nrand.txt

前回作ったスクリプトで,平均値,最大値,最小値を出してみよう.

$ awk -f average.awk nrand.txt
-0.00055316
$ awk -f max.awk nrand.txt
4.91464
$ awk -f min.awk nrand.txt
-4.56578

まずは,nrand.txt の分散を計算してみよう. 分散の公式は,

だから,

{
  sum_x += $1
  sum_x2 += $1 ^ 2
}
END{
  if(NR == 0) exit

  print sum_x2 / NR - (sum_x / NR) ^ 2
}

と書けば,だいたいの場合は OK だが, このスクリプトでは,計算誤差が蓄積して負の値が出てきたりすることがある. めんどうでも,一度平均値を計算してから,差の 2乗の平均を求めた方が安全である.

分散を計算するスクリプト variance.awk は,以下のように書ける.

{
  x[NR] = $1
}
END{
  if(NR == 0) exit

  for(i in x){
    sum_x += x[i]
  }

  m_x = sum_x / NR

  for(i in x){
    sum_dx2 += (x[i] - m_x) ^ 2
  }

  print sum_dx2 / NR
}

nrand.txt の分散を計算してみよう.

$ awk -f variance.awk nrand.txt
1.00036

分散の平方根を計算すれば,標準偏差である.

標準偏差を計算するスクリプト standard_deviation.awk は,

{
  x[NR] = $1
}
END{
  if(NR == 0) exit

  for(i in x){
    sum_x += x[i]
  }

  m_x = sum_x / NR

  for(i in x){
    sum_dx2 += (x[i] - m_x) ^ 2
  }

  print sqrt(sum_dx2 / NR)
}

となる.

nrand.txt の標準偏差は,

$ awk -f standard_deviation.awk nrand.txt
1.00018

となる.

nrand.txt が,平均 0, 標準偏差 1 の分布に,ほぼ従っていることが確認できた.


nrand.txt の度数分布を確認しておきたい. 度数分布を計算するスクリプト frequency_distribution.awk は,以下のように書ける.

{
  if(binsize <= 0) exit

  if($1 < 0){
    frequency[int($1 / binsize) - 1] ++
  }else{
    frequency[int($1 / binsize)] ++
  }
}
END{
  for(i in frequency){
    print i * binsize, frequency[i] | "sort -n"
  }
}

実行時に,-vオプションで,階級の幅 binsize を指定する. binsize=0.2 の場合,以下のようになった.

$ awk -f frequency_distribution.awk -v binsize=0.2 nrand.txt
-4.6 5
-4.4 7
-4.2 30
-4 42
-3.8 103
-3.6 165
-3.4 387
-3.2 685
-3 1253
-2.8 2103
-2.6 3455
-2.4 5651
-2.2 8977
-2 12990
-1.8 18893
-1.6 26096
-1.4 34499
-1.2 43422
-1 53128
-0.8 62540
-0.6 70494
-0.4 75926
-0.2 79247
0 79214
0.2 76073
0.4 70484
0.6 62322
0.8 53359
1 43806
1.2 33966
1.4 25834
1.6 18859
1.8 13196
2 8900
2.2 5767
2.4 3538
2.6 2126
2.8 1155
3 652
3.2 324
3.4 161
3.6 87
3.8 42
4 21
4.2 9
4.4 4
4.6 2
4.8 1

これだけでも,分布の雰囲気はだいたいわかるが, できればヒストグラムにして見てみたい. そこで,1次元のデータを簡単にプロットするスクリプト plot1.awk を作ってみた.

BEGIN{
  width = 50
}
{
  x[NR] = $1
}
END{
  xmin = xmax = x[1]

  for(i in x){
    if(x[i] < xmin) xmin = x[i]
    if(x[i] > xmax) xmax = x[i]
  }

  if(xmin == xmax) xmax = xmin + 1

  for(i = 1; i <= NR; i++){
    ix = int(width * (x[i] - xmin) / (xmax - xmin))

    for(j = 0; j < ix; j++) printf " "

    printf "*\n"
  }
}

width の値は、環境にあわせて適当に変更してほしい。

nrand.txt のヒストグラムは,以下のようにして描ける.

$ awk -f frequency_distribution.awk -v binsize=0.2 nrand.txt | awk '{print $2}' | awk -f plot1.awk
*
*
*
*
*
*
*
*
*
 *
  *
   *
     *
        *
           *
                *
                     *
                           *
                                 *
                                       *
                                            *
                                               *
                                                  *
                                                 *
                                               *
                                            *
                                       *
                                 *
                           *
                     *
                *
           *
        *
     *
   *
  *
 *
*
*
*
*
*
*
*
*
*
*
*

きれいな釣鐘カーブになった.


今回の AWKスクリプト:


[ KDO ホームにもどる ]


(C) 2011 KDO