中心極限定理

  1. 頻度表と平均の統計
    1. 目的
      ここでは、まず、[0,1]一様乱数の頻度表と分散を計算し、乱数の検定を行います。また、k個の一様乱数の平均値をn個発生し、nが大きいとき「平均値は正規分布する」ことを証明した中心極限定理を確認します。

    2. 一様乱数の生成
      一様乱数は、数学関数ライブラリの [0..1]区間の一様乱数を生成する関数
       Math.random();
      を利用します。これをn個生成し、平均・分散の計算と頻度表の作成・表示を行います。理論的には平均値は 0.5、分散は 1/12=0..833 となります。

    3. 頻度表の作成
       ここでは24区画に分割した頻度表を作成します。発生した[0..1]区画の一様乱数 rv を24倍し、その値を整数 irv の位置の頻度表に1を加えます。ここで、注意が必要なことは、irvの値が24になる可能性があることです。この場合、irv-1の区画に1を加えます。
       これは、100点満点の成績から10点刻みの頻度表を作成する場合でも同じことが起こります。0-9を第一区画、10-19を第2区画とすると、最後の区画は90-99となります。問題は100点の扱いで、通常、最後の区画を 90-100 として対応します。
       プログラムは以下のようになります。rank[] が頻度表です。checkboxNormal.getState()で二種の乱数を切り替えています。
          for( i = 0; i < num;i++ ){
            if(checkboxNormal.getState())rv=NormRand();
            else rv=Math.random();
            sd = sd + (rv)*(rv);
            st=st + rv;
            //頻度表の更新
            irv=(int)(rv*24);
            if (irv>=ranum ) irv=ranum-1;
            //System.out.println("normal1:"+rv+" num: "+irv+" rank "+rank[irv]);
            rank[irv]++;//頻度を更新する
          }

    4. チェックボックス
       ここでは、「一様乱数」と「一様乱数の平均値」、を切り替えて使用します。切り替えは、チェックボックスで、マウスでチェックして選択します。しかし、普通に生成したチェックボックスは、両者を同時に選択できてしまいます。そこで、チェックボックスグループを利用します。チェックボックスグループを生成し、チェックボックスをこのグループに加えると、グループの中で一つだけチェック状態になります。
      具体的には、一様乱数のチェックボックス checkboxUni と、平均のチェックボックスcheckboxNormal に加えて、チェックボックスグループ checkboxGroup1 を生成します。

       private CheckboxGroup checkboxGroup1 = new CheckboxGroup();
       private Checkbox checkboxUni = new Checkbox();
       private Checkbox checkboxNormal = new Checkbox();

      これから、一様乱数のチェックボックスを次のように作成します。setCheckboxGroup( )で、グループ化を行います。

       checkboxUni.setLabel("一様");
       checkboxUni.setBounds(new Rectangle(103, 187, 54, 20));
       checkboxNormal.setCheckboxGroup(checkboxGroup1);

      チェックの状態は
       checkboxUni.getState()
      で、取得できます。結果は、true/false です。

  2. プログラム

    1. 画面レイアウト
       「一様」と「平均」を切り替えるチェックボックス、処理を行う「試行」ボタン、結果の平均と分散を表示する ラベル を用意します。

    2. 実行の流れ
       「試行」ボタンで呼び出される関数 button1_actionPerformed() で、乱数を生成し、頻度表の作成、平均・分散の計算を行います。repaint();で paint() を呼び出し、再描画を行います。

    3. 平均の計算:NormalRand()
       nField から平均する個数 avnum を読み取り、この数だけ乱数の平均を発生し、平均を返します。

    4. paint()
       頻度分布の表示を行います。棒グラフの棒の表示は
       g.drawRect(left,top,right-left,bottom-top);
      で、行います。left,top は棒の左上の座標、right-left が棒の幅、bottom-top が棒の長さです。この座標系は、yの値が大きくなると下に下がることに注意してください。

    5. ソース
       このプログラムのソースを示します。
      import java.awt.*;
      import java.awt.event.*;
      import java.applet.*;
      
      
      public class StOfAv extends Applet {
        boolean BultIn=true;
        long arp;
      
        int num=500;
        int avnum=12;
        int ranum=24;
        int rank[]=new int[ranum];
        boolean first=true,paintOK=false;
      
        private boolean isStandalone = false;
        private Button button1 = new Button();
        private CheckboxGroup checkboxGroup1 = new CheckboxGroup();
        private Checkbox checkboxUni = new Checkbox();
        private Checkbox checkboxNormal = new Checkbox();
        private Label labelAv = new Label();
        private Label labelAvVal = new Label();
        private Label labelSd = new Label();
        private Label labelSdVal = new Label();
        private TextField nField = new TextField();
        private Label label1 = new Label();
        //引数値の取得
        public String getParameter(String key, String def) {
          return isStandalone ? System.getProperty(key, def) :
            (getParameter(key) != null ? getParameter(key) : def);
        }
      
      
        //コンポーネントの初期化
        public void init()  {
          
          int i;
          for(i=0;i<ranum;i++) rank[i]=0;
            
          checkboxNormal.setState(false);
          checkboxUni.setState(true);
      
          button1.setLabel("試行");
          button1.setBounds(new Rectangle(16, 184, 63, 23));
          button1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(ActionEvent e) {
              button1_actionPerformed(e);
            }
          });
          this.setLayout(null);
          checkboxUni.setCheckboxGroup(checkboxGroup1);
          checkboxUni.setLabel("一様");
          checkboxUni.setBounds(new Rectangle(103, 187, 54, 20));
          checkboxNormal.setCheckboxGroup(checkboxGroup1);
          checkboxNormal.setLabel("平均の分布");
          checkboxNormal.setBounds(new Rectangle(173, 187, 81, 20));
          
          labelAv.setText("平均");
          labelAv.setBounds(new Rectangle(38, 159, 34, 20));
          labelAvVal.setText("0.0");
          labelAvVal.setBounds(new Rectangle(90, 159, 58, 20));
          labelSd.setText("分散");
          labelSd.setBounds(new Rectangle(166, 160, 33, 18));
          labelSdVal.setText("0.0");
          labelSdVal.setBounds(new Rectangle(206, 162, 46, 17));
          
          nField.setText("12");
          nField.setBounds(new Rectangle(277, 185, 38, 22));
          label1.setText("平均する数");
          label1.setBounds(new Rectangle(267, 159, 60, 20));
          
          this.add(labelSd, null);
          this.add(button1, null);
          this.add(checkboxUni, null);
          this.add(checkboxNormal, null);
          this.add(nField, null);
          this.add(labelSdVal, null);
          this.add(labelAvVal, null);
          this.add(labelAv, null);
          this.add(label1, null);
        }
      
         public void paint(Graphics g)
         {
           int rx=300,ry=100,i;
           int ox=10,oy=10,scale=3,sx;
           int bottom,top,left,right,se;
      
           //頻度分布表の表示
           sx=ox;bottom=ry+oy;
           for(i=0;i<ranum;i++){
             //TRACE("rank %d %d \n",i,rank[i]);
             top=ry-(int)(ry*scale*rank[i]/num)+oy;
             se=(int)((float)rx/ranum*(i+1));
             left=sx;
             right=ox+se;
             sx=right;
             g.drawRect(left,top,right-left,bottom-top);
             //System.out.println("paint:"+left+" "+top+" "+(-left+right)+" "+(bottom-top));
           }
        }
      
        void button1_actionPerformed(ActionEvent e) {
          //2種の乱数の頻度表作成と平均・分散の計算
          int i;
          double sd=0.0,st=0.0,rv;
          double av,sv;
          int irv;
      
          for(i=0;i<ranum;i++)
            rank[i]=0;
      
          for( i = 0; i < num;i++ ){
            if(checkboxNormal.getState())rv=NormRand();
            else rv=Math.random();
            sd = sd + (rv)*(rv);
            st=st + rv;
            //頻度表の更新
            irv=(int)(rv*24);
            if (irv>=ranum ) irv=ranum-1;
            //System.out.println("normal1:"+rv+" num: "+irv+" rank "+rank[irv]);
            rank[irv]++;//頻度を更新する
          }
          av=st/num;
          sv=sd/num-av*av;
      
          paintOK=true;
          repaint();
          labelAvVal.setText(Double.toString(av));
          labelSdVal.setText(Double.toString(sv));
        }
      
        double NormRand()
        {
          int i;
          //指定個数の一様乱数の平均
          avnum=0;
          double rt=0.0;
          avnum=Integer.parseInt(nField.getText());
          for( i = 0;i < avnum;i++ ){
            rt = Math.random() + rt;
          }
          return (rt/avnum);  //平均値を0にする
        }
      
      }//class

    6. 実行
       「一様」をチェックし「試行」ボタンを押すと、全区画に平均した一様乱数が生成されます。平均値は0.5、分散は 1/12 に近い値になります。「試行」ボタンを繰り返し、変化のようすを確認してください。
       「平均」をチェックし、「平均する数」に適当な数を設定し、「試行」ボタンを押すと、頻度グラフが 標準分布の形状になります。分散の理論値(中心極限定理)は、「平均する数」をnとすると、1/(12・n) です。
      マウスをボタンの枠から出さないと、グラフの右が表示されない場合があります。



  3. 演習
     
    1. 演習1
      プログラムのソースをファイルに保存し、コンパイル&実行をして下さい。また、計算される平均・分散の値と理論値を比較して下さい。

    2. 演習2
       ここでは、発生する乱数の数 n は500で固定ですが、100,1000 の場合、平均・分散にどのような変化があるか調べてください。