はじめMath! Javaでコンピュータ数学

第58回統計の数学 移動平均

バスケットボールでは、相手のフェイントに惑わされぬよう腰に注目します。どんなに激しく動いても、腰が動く先が相手の動く先だからです。柔道では釣り手(相手の襟をつかんだ手)の感覚に注意を払います。釣り手を通して伝わって来る相手の体(たい)の動きが、相手の技の全てを表しているからです。

とかく細かな動きにとらわれると、本質をとらえることが難しくなってしまいます。今回学習するのは、データの細かな変動を緩和し、生のデータからでは得られなかった傾向をつかむための便利な手段です。

図58.1 子細に惑わず本質を感じよ!
図58.1 子細に惑わず本質を感じよ!

移動平均

移動平均[1]とは、細かな変動を含むデータをスムーズにする(平滑化する)方法です。

平均をとるというと、100個のデータの総計を、データの個数100で割って1つの値を得ることですが、これではデータ全体が増加傾向・減少傾向を持つのか、それともあるピークを持っている山形・谷型の分布を示すのかといった貴重な情報が失われてしまいます。

移動平均は、細かなギザギザを持つグラフになるデータを、なめらかな曲線のグラフになるデータに変換します。

ノイズの多い信号データを受け取った場合に、移動平均をとれば、ノイズの緩和されたデータに出来ます。

株価の細かな変動を平滑化して、ある区間での平均的な株価を得る場合にも利用されます。

図58.2に移動平均の取り方を示します。連続するデータのひとつDiに注目し、自分を含む周囲のデータの平均値をとります。この計算を次々と実行していった結果、グラフの形は凹凸の少ないものになります。

図58.2 移動平均とは
図58.2 移動平均とは

例えば時系列に並んだ100個のデータがあるとします。平均をとる範囲を5とすると、10番目のデータD10の値は、次の式で移動平均値に換算されます[2]⁠。

先頭から順番にデータを処理する場合には、D8やD9の値に「移動平均をとる以前の値」を用いるように注意してください。また、別法として、⁠注目するデータの位置を含む前方(過去)範囲の平均を取る」ことも出来ます。こちらの方がアルゴリズムが単純です。

問題 移動平均をとるプログラムを作りましょう

前回作成したデータファイルを読み込んで、移動平均をとり、またファイルに出力するプログラムを作りましょう。ファイルの入力と、計算結果の出力には、標準入力と標準出力を用い、UNIXやWindowsのコマンドラインのリダイレクト機能を活用しましょう。Windowsならば、コマンドラインから次のように実行しましょう。

c:\java> java Sample_MovingAverage < st_sample002.csv > result_ma.csv

この操作により、標準入力を使ってst_sample002.csvがプログラムSample_MovingAverageに読み込まれ、計算結果がresult_ma.csvに書き込まれます。

ソースコードの大枠を次に示しますので、コードの不足している関数CalcMovingAverageにコードを書き加えて完成させてください。

ソースコード:Sample MovingAverage.java
/*
 * Sample_MovingAverage.java
 * コンマで区切られた2つひと組の数値を
 * 標準入力から読み込んで、ArrayListにため込み、
 * 移動平均をとり、標準出力へ出力する。
 * usage : c:\java> java Sample_MovingAverage  result_ma.csv
 */

import java.util.*; // StringTokenizer
import java.io.*; // BufferedReader

public class Sample_MovingAverage {

  public static void main(String args[]) {

    //入力:カウント
    ArrayList a = new ArrayList();
    //入力:データ
    ArrayList b = new ArrayList();
    //出力:移動平均値
    ArrayList c = new ArrayList();

    int count = 0;
    BufferedReader d = new BufferedReader
        (new InputStreamReader(System.in));
    try {
      int tokens = 0;
      do {
        String str = d.readLine();
        if (str == null) break;
        StringTokenizer aSt = new StringTokenizer(str,",");
        if (aSt.countTokens() != 2) {
          System.out.print("Input Error\n");
          System.exit(1);
        }
        tokens = aSt.countTokens();
        ++count;
        a.add(Double.valueOf(aSt.nextToken()).doubleValue());
        b.add(Double.valueOf(aSt.nextToken()).doubleValue());
     } while (tokens ==2);
    }
    catch(IOException e) {
      System.out.println("IO Error");
      System.exit(1);
    }

    //データb の移動平均をとる。
    CalcMovingAverage(b,c,5);

    //移動平均の出力
    int i = 0;
    Iterator iterC = c.iterator();
    while(iterC.hasNext()) {
      System.out.println(i + "," + iterC.next());
      ++i;
    }

  }// end of main

  /*
   * 目的: 移動平均をとる
   * 引数: data   元データのArrayListへの参照
   *      result 移動平均値のArrayListへの参照
   *             移動平均の計算結果を格納する
   *      range  range個の平均をとる
   */
  static void CalcMovingAverage(ArrayList data,
                                ArrayList result,
                                int range) {

  //ここからコードを補充してください

  //ここまでコードを補充してください

  }// end of CalcMovingAverage


}// end of class

解説

問題 移動平均をとるプログラムを作りましょう

コードの補充が完了した関数を示します。注目するデータ位置に対して前方範囲の平均を取るシンプルなアルゴリズムです。

ソースコード:CalcMovingAverage関数
  /*
   * 目的: 移動平均をとる
   * 引数: data   元データのArrayListへの参照
   *      result 移動平均値のArrayListへの参照
   *             移動平均の計算結果を格納する
   *      range  range個の平均をとる
   */
  static void CalcMovingAverage(ArrayList<Double> data,
                                ArrayList<Double> result,
                                int range) {
    Double sum = 0.0;
    Double ma = 0.0;
    for (int i=0; i<range ; ++i) sum += data.get(i);
    ma = sum/range;
    for (int i=0; i<(range/2); ++i) result.add(0.0);
    result.add(ma);
    for (int i=range; i<data.size(); ++i){
      sum = sum - data.get(i-range)+data.get(i);
      ma = sum / range;
      result.add(ma);
    }
  }// end of CalcMovingAverage

引数rangeから導かれる、移動平均を計算できない先頭と末尾の部分には0を格納します。いちいち5個ずつの平均をとらずとも、先ほど合計した値から範囲を外れた値を引き、新しく範囲に入ったデータを加算して新しい合計を得れば、ずいぶん効率が良く計算できます。さて、今回もOpenOfficeのCalcで読み込んで、散布図を表示してみましょう。

図58.3 st_sample002.csvの散布図
図58.3 st_sample002.csvの散布図
図58.4 result_ma.csvの散布図
図58.4 result_ma.csvの散布図

図58.358.4を比較すると、特に図58.3で目立っていた横軸20ごとの突出はほぼ姿を消しました。そのかわりに広く分布していたデータが図58.4ではぎゅっと締まった印象があります。

今回のサンプルデータは、もともと線形で行儀の良い傾向を持っていましたから、ありがたみが薄く感じられますが、よりばらつきや細かな振動の多いデータでしたら、この移動平均が傾向をつかむために効果を発揮します。

今回はここまで

移動平均という、データのぶれをスムーズにするツールを紹介しました。

表計算ソフトウエアを利用しても良いのですが、rangeを自由に選択したり、コマンドライン一発で結果が得られたりするのはプログラムを作成して処理することで得られる大きな利点です。理屈は大変シンプルで、コードにするのも簡単です。それでありながら、効果的なツールとして使えますから、おぼえておいて損はないでしょう。

おすすめ記事

記事・ニュース一覧