ニ項分布の平均と分散

  1. 平均と標準偏差

    1. 目的
      二項分布の平均と分散(標準偏差)は n と p から理論式から簡単に計算できますが、これを、実際に計算することで確認しましょう。また、ニ項分布の確率分布をグラフ表示します。

    2. 計算式
      二項分布は

      P(X=i) = Ci pi (1-p)n-i

      で与えられます。nを与えて、その、確率分布と平均、分散を計算します。

    3. 倍精度少数計算
       これまで、階乗や組み合わせは整数で計算してきました。しかし、nが大きくなると整数(4バイト)では記録できなくなります。整数4バイトで記録できる数は10桁程度です。これに対し、倍精度少数は 少数(6バイト)*10n (n:2バイト)の形式で記録するため、有効桁数は制限されますが、大きな桁数まで記録ができます。例えば、階乗の計算は以下のようになります。
        double fact(int num){
          double rs=1.0;
          for (int i=1;i<=num;i++){
            rs = rs*i;
          }
          return rs;
        }

    4. 二項分布の計算
       与えられたnに対し、二項分布の確率を求め、配列 prb[] に記録します。組み合わせ Ci は関数 comb(n,i) で求めます。pi は Math.pow(p,i) で求められます。

          for(k=0;k<=nnum;k++){
            cb=comb(nnum,k);
            prob=cb*Math.pow(pb,k)*Math.pow(1.0-pb,nnum-k);
            prb[k]=prob;
            //System.out.println("k:"+k+" prb:"+prb[k]);
          }

    5. 平均(期待値)
       二項分布の平均は、

       μ = Σ(k=i..n) k・P{ x=k }

      で計算できます。この計算をする関数 avrg() は次のようになります。最初、psumを0にしておき、これに、k*prb[k] を加えていきます。prb[k] は値がkとなる二項分布の確率です。理論計算では、平均は n・p になります。
      double avrg(){
          double psum=0.0;
          int k;
          for(k=0;k<=nnum;k++){
            psum=psum+k*prb[k];
            //System.out.println("avrg k:"+k+" prb:"+prb[k]+" sum:"+psum);
          }
          return psum;
        }
    6. 分散
       分散は

      Σ(i=i..n) xi2P{ xi } - μ2    

      で求められます。μは平均です。μが別に求められている場合、確率変数の2乗の平均を求めれば、よいことになります。したがって、プログラムは 平均 の場合とほぼ同じです。理論式では、n ・p ・(1-p) になります。

    7. グラフ表示
       二項分布の確率をグラフ表示します。手法は 「同じ誕生日」 の場合と同じです。確率の最大値を長さ100で表示しています。

  2. プログラム

    1. 画面レイアウト
       n(回数)とp(確率) を入力する枠を nFileld、pbFieldとします。また、button1を「計算」ボタンとし、ボタンを押したとき、button1_actionPerformed を実行するよう設定します。平均と分散はラベル avrgLabel と stdvlabel に表示します。

    2. 処理の流れ
       計算ボタンを押すと button1_actionPerformed が呼び出されるます。この関数で、確率を計算します。Integer.parseInt() は k の値を整数に変換し、nnumに代入します。
      確率の値は少数ですから、Double.parseDouble で読み込み、pb に記録します。
       nnumとpbより、二項分布の値を計算し、配列 prb[k] に記録します。

      次に、av=avrg(); で平均、sd=p2avrg()-av*av; で、分散を計算します。p2avrg() は二乗の平均で、これから、μ(av)の二乗を引けば分散になります。
      最後に、repaint() を呼び出し、画面を再表示します。

    3. 画面表示:paint()
       配列 prb[k] の値をグラフ表示します。「誕生日」の場合とほぼ同じですが、回数nが大きくなると、各確率の値が小さくなるため、グラフ表示する前に prb[k] の最大値を pmax に求め、pmax の値が長さ100となるよう、棒グラフの長さを調整しています。

    4. ソース
      import java.awt.*;
      import java.awt.event.*;
      import java.applet.*;
      
      public class Binomi extends Applet {
      
        double prb[]=new double[100];
        int nnum=-1;
        double av=0.0,sd=0.0;
      
        private TextField nField = new TextField();
        private Button button1 = new Button();
        private TextField pbField = new TextField();
        private Label label1 = new Label();
        private Label label2 = new Label();
        private Label avrgLabel = new Label();
        private Label label4 = new Label();
        private Label stdvlabel = new Label();
        private Label label3 = new Label();
      
      
        //コンポーネントの初期化
        public void init() {
          label2.setText("回数");
          label2.setBounds(new Rectangle(20, 250, 50, 20));
          nField.setText("5");
          nField.setBounds(new Rectangle(80, 250, 50, 20));
      
          button1.setLabel("計算");
          button1.setBounds(new Rectangle(80, 170, 70, 25));
          button1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(ActionEvent e) {
              button1_actionPerformed(e);
            }
          });
          
          pbField.setText("0.5");
          pbField.setBounds(new Rectangle(200, 250, 60, 20));
          label1.setText("確率");
          label1.setBounds(new Rectangle(150, 250, 30, 20));
          avrgLabel.setBackground(Color.cyan);
          avrgLabel.setText("0.0");
          avrgLabel.setBounds(new Rectangle(80, 220, 60, 20));
          label4.setText("平均");
          label4.setBounds(new Rectangle(20, 220, 30, 20));
          
          stdvlabel.setBackground(Color.cyan);
          stdvlabel.setText("0.0");
          stdvlabel.setBounds(new Rectangle(200, 220, 45, 20));
          label3.setText("分散");
          label3.setBounds(new Rectangle(150, 220, 40, 20));
          
          this.setLayout(null);
          this.add(nField, null);
          this.add(label2, null);
          this.add(avrgLabel, null);
          this.add(label4, null);
          this.add(button1, null);
          this.add(pbField, null);
          this.add(label1, null);
          this.add(stdvlabel, null);
          this.add(label3, null);
        }
       
      //階乗の計算
        double fact(int num){
          double rs=1.0;
          for (int i=1;i<=num;i++){
            rs = rs*i;
          }
          return rs;
        }
        
      //組み合わせ数の計算
        double comb(int nnum,int knum) {
          double rs=1.0;
          for (int i=nnum;i>nnum-knum;i--){
            rs = rs*i;
          }
          return rs/fact(knum);
        }
        
      //2項分布の確率計算
        void button1_actionPerformed(ActionEvent e) {
          nnum=Integer.parseInt(nField.getText());
          double pb=Double.parseDouble(pbField.getText());
          int k=0;
          double cb;
      
          double prob=0.0;
          for(k=0;k<=nnum;k++){
            cb=comb(nnum,k);
            prob=cb*Math.pow(pb,k)*Math.pow(1.0-pb,nnum-k);
            prb[k]=prob;
            //System.out.println("k:"+k+" prb:"+prb[k]);
          }
          av=avrg();//平均の計算
          sd=p2avrg()-av*av;//分散の計算
          avrgLabel.setText(Double.toString(av));
          stdvlabel.setText(Double.toString(sd));
          repaint();//再表示
        }
        
      //平均の計算
        double avrg(){
          double psum=0.0;
          int k;
          for(k=0;k<=nnum;k++){
            psum=psum+k*prb[k];
            //System.out.println("avrg k:"+k+" prb:"+prb[k]+" sum:"+psum);
          }
          return psum;
        }
      
      //2乗和の計算
        double p2avrg(){
          int k;
          double sdsum=0.0;
          for(k=0;k<=nnum;k++){
            sdsum=sdsum+k*k*prb[k];
            //System.out.println("avrg k:"+k+" prb:"+prb[k]+" sum:"+sdsum);
          }
          return sdsum;
        }
        
      //グラフ表示
        public void paint(Graphics g){
          //グラフ表示
          int i;
          //グラフの原点
          int ox=20,oy=150;
          double pmax=0.0;
          //確率の最大値を求める
          for(i=0;i<=nnum;i++){
            if(pmax<prb[i]) pmax=prb[i];
          }
      
          for(i=0;i<=nnum;i++){
            // (int)は整数型に変換、最大値を長さ100で表示
            g.drawLine(ox+i*5,oy,ox+i*5,(int)(oy-prb[i]*100/pmax));
          }
        }
      
      
      }//class

    5. 実行

       回数(80以内)、確率(1以下)を設定し、「計算」ボタンを押します。2項分布の平均、分散、確率分布が表示されます。



  3. 演習

     
    1. 課題1
      プログラムのソースをファイルに保存し、コンパイル&実行をして下さい。

      以下の課題の少なくとも一つをプログラムしてください。

    2. 課題2
       平均値と分散を理論式で表示し、別のラベルに表示してください。
      理論式による平均は n・p 分散は n・p・(1-p) です。

    3. 演習2

       このままでは確率の値がわかりません。グラフにメモリを入れて、最大値やグラフの軸を表示して下さい。
       文字は g.drawText("文字列",x,y)
      で任意の位置に表示できます。