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

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

この記事を読むのに必要な時間:およそ 3 分

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

前回作成したデータファイルを読み込んで,移動平均をとり,またファイルに出力するプログラムを作りましょう。ファイルの入力と,計算結果の出力には,標準入力と標準出力を用い,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 < st_sample.csv > 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を自由に選択したり,コマンドライン一発で結果が得られたりするのはプログラムを作成して処理することで得られる大きな利点です。理屈は大変シンプルで,コードにするのも簡単です。それでありながら,効果的なツールとして使えますから,おぼえておいて損はないでしょう。

今回のまとめ

  • データの細かな変動を緩和する方法として,移動平均を紹介しました。

著者プロフィール

平田敦(ひらたあつし)

地方都市の公立工業高等学校教諭。趣味はプログラミングと日本の端っこ踏破旅行。2010年のLotYはRuby。結城浩氏のような仕事をしたいと妄想する30代後半♂。