ホーム » 「移動平均」タグがついた投稿

タグアーカイブ: 移動平均

2019年7月
« 6月    
 123456
78910111213
14151617181920
21222324252627
28293031  

最近の投稿(電子情報)

アーカイブ

カテゴリー

移動平均の処理

前回の授業で説明したようなA/D変換した数値データを読み取った場合、どのようなことが発生するか考える。

例えば、以下に示すような測定値があったとする。

  • 2018-06-05-wave.csv
    6/21のデータはファイルの行末文字の影響で Microsoft Visual Studio の scanf_s() を使うとデータが1件しか読み込めない問題がありました。現時点の上記ファイルは修正済みです。

このデータの一部をグラフ化してみると、次のような波形であった。

この波形をみると、大きく見ればsinカーブだが、細かい点を見るとデータにブレがある。

誤差の原因

このような測定結果が得られた場合、本来コンピュータで処理したいデータは何であろうか?

原因は様々なものが考えられるが、

  1. 回路のノイズ対策が不十分で、外部の電気的な影響が混入。
    オシロスコープで周期を図ると、60Hz なら、交流電源だったり…
  2. D/A 変換を行う場合には、量子化誤差かもしれない。

例えば、最初の波形が、加速度センサーの値であったとして、船の上で揺れているために、大きな周期で加速度が変化しているかもしれない。一方で、船自体がエンジンによる揺れで加速度が変化しているかもしれない。

船の中で波の揺れと、エンジンの揺れが観測されている加速度センサーの情報で、船の揺れの大きさ・揺れの周期を知りたい場合、どうすればいいだろうか?

移動平均

このデータを見ると、10個のデータまでの間で、波形が上下に変動している。船の揺れとエンジンの揺れが原因であれば、10個ぐらいのデータのゆらぎが、エンジンによる揺れと考えられる。では、この10個ぐらいの範囲で値が上下の影響を減らしたければ、どうすればいいか?一番簡単な方法は、前後10個のデータで平均を取ればいいだろう。増減する値を加えれば、プラスの部分とマイナスの部分の値が相殺されて0に近くはず。そこでは、Excel で前後データの平均をとってみよう。

Excelで前後11点の平均を求める式をセルに入れる

青線:元波形データ(B列)、赤線:前後11点の平均(C列)

このように、データの前後の決められた範囲の平均を平均する処理は、移動平均(単純移動平均)と呼ぶ。

時間tにおけるデータをとした場合、前後5点の移動平均は、以下のような式で表せるだろう。

移動平均のプログラム

Excel で計算と同じ処理をプログラムで行うと以下のようになるだろう。

// moving-average.c
#include <stdio.h>

#define WIDTH 5
double data[ 1000 ] ; // 元データ
double ans[ 1000 ] ;  // 平均後のデータ

int main() {
   int t , i , size ;
   // 最初に全部のデータを読み込む
   for( size = 0 ; size < 1000 ; size++ ) {
      int num ;
      // コンマ区切りのデータを読む
      // 2つのデータが読み込めない時は入力を終了
      if ( scanf( "%d,%lf" , &num , &data[size] ) != 2 )
         break ;
   }
   // 移動平均を求める
   for( t = WIDTH ; t < size - WIDTH ; t++ ) {
      // t番目のデータの前後WIDTH個の合計
      double sum = 0 ;
      for( i = -WIDTH ; i <= WIDTH ; i++ )
         sum += data[ t + i ] ;
      ans[ t ] = sum / (2*WIDTH + 1) ;
   }
   // 計算後のデータをコンマ区切りで出力
   for( t = 0 ; t < size ; t++ ) {
      printf( "%d, %10.6lf, %10.6lf\n" , t , data[ t ] , ans[ t ] ) ;
   }
   return 0 ;
}

このプログラムを動かすと、データ番号とデータ値をコンマ区切りで与えること。

入力リダイレクトと出力リダイレクト

上記のプログラムでは、キーボードからデータを入力しなくてはいけない。これでは入力が大変なので、保存したファイルを使ってプログラムにデータを与える。

上記のプログラムを、パソコンの Z:¥課題¥moving-average.c に保存したとする。このプログラムを「コンパイル&実行」すれば、Z:¥課題¥moving-average.exe という実行プログラムが作られ、プログラムが起動する。このままでは、キーボードからデータを入力する必要がある。

(1) ファイルから入力した値を使って処理を行うのであれば、コマンドを起動。

タスクバー左側の検索バーに、cmd.exe と入力すれば、命令入力画面が表示される。

(2) コマンド画面で、以下のように入力し、moving-average.exe があるか確認する。

  C:¥WINDOWS¥System32> Z:   青は表示される部分、赤が入力
  Z:¥> cd Z:¥課題
  Z:¥課題¥> dir *.exe
  06/21/2019 12:30PM  12345 moving-average.exe

(3) 最初のデータの記録されたCSVファイルを Z:¥課題 に保存する。

(4) コマンド画面で、以下のようにプログラム名の後ろに “< ファイル名” をつけて起動すると、キーボード入力の代わりに、ファイルから読み込んでプログラムが動く。このような起動は、入力リダイレクトと呼ぶ。

 Z:¥課題¥> moving-average.exe < 2018-06-05-wave.csv
 xx, xxx.xxxx, xxx.xxxx ←結果が画面に表示される

(5) これでは、結果がよく分からないので、ファイルに保存し Excel でグラフ化する。コマンド画面で、以下のようにプログラム名の後ろに“> ファイル名” をつけて起動すると、結果を画面に出力する代わりに、ファイルに結果を保存してくれる。このような起動は、出力リダイレクトと呼ぶ。

 Z:¥課題¥> moving-average.exe < 2018-06-05-wave.csv > out.csv

出力された out.csv は、データがコンマ区切りなので、Excel でひらけば、結果を表として簡単に読み込める。後は、グラフ化したい範囲を、マウスでドラッグ(もしくはシフトキーを押しながらカーソル移動)し、[挿入]-[グラフ]-[散布図]-[折れ線グラフ] でグラフ化すればいい。

自宅学習の課題

表計算ソフトで、移動平均を計算させてみよう。  

  • 元波形
  • 前後5点で移動平均
  • 前後11点で移動平均
  • 前後51点で移動平均

をとるような表計算の式を書き込んで、その結果の波形がどんなグラフになるのか確認しておくこと。

移動平均のプログラム

移動平均のプログラム(ダサっ)

#include <stdio.h>
#define WIDTH 5
double xt[1000] ; // 元波形データ
double yt[1000] ; // 移動平均処理後のデータ
int main() {
    int    i ; 
    double t , x ;
    // 全データを読み込む(入力はコンマ区切りの2データ)
    for( i = 0 ; scanf( "%lf,%lf" , &t , &x ) == 2 ; i++ )
        xt[ i ] = x ;
    // データ xt[*] の移動平均を yt[*] に求める。
    for( i = WIDTH ; i < 1000 - WIDTH ; i++ ) {
        int j ;
        double s = 0.0 ; // 合計
        // 前後の値の合計を求める
        for( j = -WIDTH ; j <= WIDTH ; j++ )
            s = s + xt[ i + j ] ;
        // 合計をデータ数で割る
        yt[ i ] = s / (WIDTH * 2 + 1) ;
    }
    // 処理結果を出力する。
    for( i = 0 ; i < 1000 ; i++ )
        printf( "%d,%lf,%lf\n" , i , xt[i] , yt[i] ) ;
    return 0 ;
}

でも、このプログラムは、以下の点で問題がある。

  1. 範囲のデータを加算しているけど、加算の繰り返しが多い。
  2. 配列にデータを最初に全て読み込んでいるけど、長時間のデータならば大量のメモリが必要。
  3. 測定しながら移動平均を計算する場合、データはどうする?

移動平均のプログラム(ちょっと改良)

全部のデータを覚えるのはメモリの無駄なので、移動平均する直近のデータだけを覚えるように改良する。
しかし、データを保存する度に、配列をずらす処理も無駄なので、データを保存する場所(以下の例ではbp)を保存したら次の場所を示すように記述してみる。

#include <stdio.h>

#define WIDTH 10
double buff[ WIDTH ] ; // 直近のWIDTH個だけ保存
int    bp = 0 ;        // 最新データの場所
double bs = 0.0 ;      // 直近のWIDTH個の合計

int main() {
    int i ;
    double t , x ;
    for( i = 0 ; scanf( "%lf,%lf" , &t , &x ) == 2 ; i++ ) {
        // WIDTH個前のデータを捨てるために合計から引く
        bs = bs - buff[ bp ] ;

        buff[ bp ] = x ;      // 最新データを保存
        bs = bs + x ;         // 最新のデータで合計
        // 直近のデータを覚える場所を移動
        bp++ ;
        if ( bp >= WIDTH )
            bp = 0 ;
        // 移動平均を出力
        printf( "%d %lf\n" , i , bs / WIDTH ) ;
    }
    return 0 ;
}

移動平均の処理

前回の授業で説明したようなA/D変換した数値データを読み取った場合、どのようなことが発生するか考える。

例えば、以下に示すような測定値があったとする。

このデータの一部をグラフ化してみると、次のような波形であった。

この波形をみると、大きく見ればsinカーブだが、細かい点を見るとデータにブレがある。

誤差の原因

このような測定結果が得られた場合、本来コンピュータで処理したいデータは何であろうか?

原因は様々なものが考えられるが、
(1) 回路のノイズ対策が不十分で、外部の電気的な影響が混入してしまうこともある。
(2) 一方で、D/A 変換を行う場合には、量子化誤差もある。

青線:元波形、赤線:4段階で量子化

この例は、-1〜1の範囲を、4段階に量子化することで、本来の波形とは異なった値になっている。

例えば、最初の波形が、加速度センサーの値であったとして、船の上で揺れているために、大きな周期で加速度が変化しているかもしれない。一方で、船自体がエンジンによる揺れで加速度が変化しているかもしれない。

船の中で波の揺れと、エンジンの揺れが観測されている加速度センサーの情報で、船の揺れの大きさ・揺れの周期を知りたい場合、どうすればいいだろうか?単純なsinカーブの波形であれば、波形の最大値・最小値・0との交点の場所を探せば、振幅や周期が求めることができるが、このようなノイズが入った波形では、正しく振幅・周期が求まらない。

移動平均

このデータを見ると、10個のデータまでの間で、波形が上下に変動している。船の揺れとエンジンの揺れが原因であれば、10個ぐらいのデータのゆらぎが、エンジンによる揺れと考えられる。では、この10個ぐらいの範囲で値が上下の影響を減らしたければ、どうすればいいか?一番簡単な方法は、前後10個のデータで平均を取ればいいだろう。まずは、Excel で前後データの平均をとってみよう。

Excelで前後11点の平均を求める式をセルに入れる

青線:元波形データ(B列)、赤線:前後11点の平均(C列)

このように、データの前後の決められた範囲の平均を平均する処理は、移動平均(単純移動平均)と呼ぶ。

時間tにおけるデータをとした場合、前後5点の移動平均は、以下のような式で表せるだろう。

自宅学習の課題(レポート提出は不要)

表計算ソフトで、移動平均を計算させてみよう。  

  • 元波形
  • 前後5点で移動平均
  • 前後11点で移動平均
  • 前後51点で移動平均

をとるような表計算の式を書き込んで、その結果の波形がどんなグラフになるのか確認しておくこと。

移動平均の応用

例えば、以下のような心電図のデータがあったとする。心電図では、波の高さで心臓が正常か判断するが、この青のグラフでは、大きな周期の変動が含まれるため、波の高さを正確に測れない。この波形から診断するときに、移動平均が使えないだろうか?

このような場合には、測定値の波形の移動平均をとり心拍データの変動を取り除き、測定値から移動平均の値を引くことで、心拍データだけを取り出すことが可能となる。