簡易統計解析クラスを作ってみたよ

やあ子供たち。夏ももう終わりだね。今日はおじさんはstd::accumulateや、 tr1::bind の練習のためにVS2008SP1で、簡単な統計情報計算テンプレートを作成してみたよ。要素セットと、各要素の値の計算方法を指定すると、以下の統計情報、

を計算してくれるよ。しかも任意の要素を格納した、任意のコンテナで利用可能だ。

// SimpleStatistics (C) 2009 nursmaul
#include <iostream>
#include <algorithm>
#include <iterator>
#include <functional>
#include <numeric>
#include <vector>

namespace nurs {
class SimpleStatistics{ // stub & I/F
public:
  SimpleStatistics( const int nres ): _nres( nres ){}
  ~SimpleStatistics( void ){}
  virtual void DumpResults( void ) const =0;
   template< class Container, class Func >
  static SimpleStatistics* Create( const Container& cont, const int nres, Func& func )
  { return new Statistics1< Container, Func >( cont, nres, func );  }
public:
  std::vector< int > _histogram;
  int   _nres;
  float _sum, _avr, _dsp, _sd, _min, _max, _uni;
};
//
template< class Container, class Func >
class Statistics1: public SimpleStatistics
{  
protected:
  friend class SimpleStatistics;
  typedef typename Container::value_type Valtype;
  Statistics1( const Container& cont, const int nres, Func& func )
    :_pcont( &cont ) ,_func( func ), SimpleStatistics( nres )
  // concept: float Func::operator( const Valtype& )
  { this->Update(); }
  ~Statistics1( void ){}
  void DumpResults( void ) const{
    using namespace std;
    cout<< "sum: "<< _sum << endl;
    cout<< "average: "<< _avr << endl;
    cout<< "sd: " << _sd << endl;
    int nmax = *max_element( _histogram.begin(), _histogram.end() );
    for( int i=0; i<_nres; ++i ){
      cout.width( 8);
      cout<< _min + i*_uni <<"\t";
      for( int j=0; j<_histogram[i]*25/(float)nmax; ++j )
        cout<< "●";
      cout<< endl;
    }// i
    return;
  }
  void	Update(void ){
    using namespace std;
    using namespace std::tr1;
    using namespace std::tr1::placeholders;
    // calc sum, avr, dsp, sd, min, max
    struct FuncWrap
    {
      FuncWrap( Func& func ): _func( func ){}
      Func _func;
      bool Comp( const Valtype& a, const Valtype& b ){ 
        return _func(a) < _func(b); }
      // accumulate 用のPredは、sum += Pred( sum, y ) と使われる。
      float Disp( const float sum, const Valtype& ival ){	
        return sum+ _func( ival )*_func( ival); } 
      float Sum( const float sum, const Valtype& ival ){ 
        return sum+ _func( ival ); }
    };
    FuncWrap facc( _func );
    _sum = accumulate( _pcont->begin(), _pcont->end(), 0.0f, 
      bind( &FuncWrap::Sum, facc, _1, _2 ) );
    _avr = _sum / static_cast<float>(_pcont->size());
    _dsp = accumulate( _pcont->begin(), _pcont->end(), 0.0f, 
      bind( &FuncWrap::Disp, facc, _1, _2 ) );
    _dsp /= static_cast< float >( _pcont->size() );
    _sd = sqrt( _dsp );
    _min = _func( *min_element( _pcont->begin(), _pcont->end(),
      bind( &FuncWrap::Comp, facc, _1, _2 ) ) );
    _max = _func( *max_element( _pcont->begin(), _pcont->end(),
      bind( &FuncWrap::Comp, facc, _1, _2 ) ) );
    // calc histogram
    _uni = ( _max-_min )/static_cast< float >(_nres );
    _histogram.resize( _nres );
    std::fill( _histogram.begin(), _histogram.end(), 0 );
    for each( const Valtype& ival in *_pcont ){
      size_t sz = _histogram.size();
      for( size_t i=0; i<sz; ++i ){
        if( _func( ival ) < _min + _uni*(i+1) ){
          ++this->_histogram[i];
          break;
        }
      }// i
    }// it
  }
  const Container* _pcont;
  Func  _func;
};
}// namespace nurs


…これを使って、ランダムに発生させた球の半径の分布や、表面積値の分布、体積値の分布、さらには体積値の対数値の分布などが簡単に計算できるサンプルを作ってみたよ。

#include <list>
#include <cmath>
#include <set>
#include <string>

int main( void )
{
  using namespace std;
  using namespace nurs; 
  // 球を作成
  class Sphere{
  public:
    Sphere( const float f ): _radi( f ){}
    const float Radi( void ) const { return _radi; }
    const float Area( void ) const { return 4*3.141592*_radi*_radi; }
    const float Volume( void ) const { 
      return 4*3.141592/3.0*_radi*_radi*_radi; }
    float _radi;
  };
  vector< Sphere* > cv;
  for( int i=0; i<123; ++i ){
    cv.push_back( new Sphere( 10*rand()/(float)RAND_MAX ) );
  }// i
  {
    // ヒストグラムの計算&結果表示
    const int nres=12;// ヒストグラムの細かさ(棒の本数)
    string title;
    SimpleStatistics* s;
    {
     // 円の面積の分布を計算&表示
      cout<< "        --- Distribution of Area ---" <<endl;
      s = SimpleStatistics::Create( cv, nres, mem_fun( &Sphere::Area ) );
      s->DumpResults();
      delete s;
      // 円の体積の分布を計算&表示
      cout<< "        --- Distribution of Volume ---" <<endl;
      s = SimpleStatistics::Create( cv, nres, mem_fun( &Sphere::Volume ) );
      s->DumpResults();
      delete s;
      // 円の体積の対数値の分布を計算&表示
      struct Func{
        float operator()( const Sphere* sph ){ return log( sph->Volume() ); }
      };
      cout<< "        --- Distribution of log-Volume ---" <<endl;
      s = SimpleStatistics::Create( cv, nres, Func() );
      s->DumpResults();
      delete s;    
    }
    //
  }
  return 0;
}


以下は出力結果だよ。

        --- Distribution of Area ---
sum: 50833.5
average: 413.281
sd: 555.449
0.00196745      ●●●●●●●●●●●●●●●●●●●●●●●●●
 104.658        ●●●●●●●●●●
 209.313        ●●●●●●●●
 313.969        ●●●●●●●
 418.625        ●●●●●●●
  523.28        ●●●●●
 627.936        ●●●●●●●
 732.592        ●●●●
 837.247        ●●●●●●
 941.903        ●●●
 1046.56        ●●●●
 1151.21        ●●●
        --- Distribution of Volume ---
sum: 126414
average: 1027.76
sd: 1561.15
8.20597e-006    ●●●●●●●●●●●●●●●●●●●●●●●●●
 348.746        ●●●●●●●●
 697.493        ●●●●●●
 1046.24        ●●●
 1394.99        ●●●●●
 1743.73        ●●
 2092.48        ●●●
 2441.22        ●●●
 2789.97        ●●
 3138.723487.46        ●●
 3836.21        ●●
        --- Distribution of log-Volume ---
sum: 637.302
average: 5.18132
sd: 6.20375
-11.7106        ●
-10.0398
  -8.369        ●
-6.69817        ●
-5.02735        ●
-3.35652        ●
 -1.6857        ●
-0.0148735      ●●
 1.65595        ●●●●●●●
 3.32678        ●●●●●●●
  4.9976        ●●●●●●●●●●●●●●●●
 6.66843        ●●●●●●●●●●●●●●●●●●●●●●●●●


上記の計算から一見小さい球が多いみたいだけど、LOGスケールにしてみると超絶小さい球がいくつかいる他は、皆だいたい大きい球がそろっているということがわかるね。
このように3番目の引数次第で、つまり要素の値の計算方法を指定するのに指定するファンクタをどう作るかでいろんな計算量の分布を簡単に知ることができるね。何に使うのだろうね。
そうだな、おじさんだったら、やけに細いポリゴンがいないかのチェックに使ったり、分子動力学計算とか各種シミュレーション計算の結果の解析に使ったりという感じかな。ユーザーログ解析とかで、各種情報から計算されるポイント別でユーザー数の分布を見てみたりするのも楽しそうだ。