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

第61回統計の数学 回帰直線[後編]

前回は、回帰直線を数学的に詳しく学びました。今回は前回の知識を使って、Java言語で回帰直線のグラフを描きましょう。Officeソフトで回帰直線を得ることと、Java言語のプログラムで回帰直線を得ることの比較は、電卓での計算と筆算での計算の比較に例えられます。2つの結果が等しい、あるいは非常に近ければ、それぞれの方法が正しく実行されたかどうかの確認になります。

サンプルのソースコードが少々長くなりますが、解答の必要な部分はわずかです。是非ともくじけずに取り組んでみてください。

問題 最小二乗法を用いて、回帰直線の定数を求め、データにフィットするグラフを描きましょう。

今回の問題で示すソースコードには、最小二乗法を計算する部分のコードが欠落しています。コードを補充して完成させてください。

できる限り短いコードにするために、GUIでプログラムを終了させるためのコードを含みません。Windows上で実行中ならコマンドプロンプトのウインドウをアクティブにし、CTRL+C[1]のキー操作でプログラムを終了する必要があります。その他のOS上で実行中なら、プロセスをkillして終了させてください。

今回読み込むデータは次のCSV[2]ファイルです。各行にひとつの座標値(x,y)が記述されています。このデータファイルをテキストエディタで作成して、Sample_RegLine.javaと同じディレクトリにファイル名"data001.csv"として保存してください。

データファイル data001.csv

  • 100,120
  • 200,195
  • 278,280

コンパイル時には、ソースコードの5行目に示した-Xlint:uncheckedというオプション[3]を忘れずに付けてください。Java言語の古い規格を使用していますので、このオプション無しにはコンパイルが許可されません。コンパイルが許可されませんから、実行に必要なクラスファイルが作成されません。

正しくプログラムを作成したとしても、次のような警告を表示します。

C:\>javac Sample_RegLine.java -Xlint:unchecked
Sample_RegLine.java:126: 警告:[unchecked] raw 型java.util.Vector のメンバとしてのadd(E) への無検査呼び出しです。
      Data.add(pos);
              ^
Sample_RegLine.java:145: 警告:[unchecked] raw 型java.util.Vector のメンバとして
のadd(E) への無検査呼び出しです。
      v.add(rdata);
           ^
注:Sample_RegLine.java は推奨されないAPI を使用またはオーバーライドしています。

注:詳細については、-Xlint:deprecation オプションを指定して再コンパイルしてください。
警告2個

今回のプログラムが正しく実行されると、次の出力と別ウインドウに表示されるグラフ図61.1を得ることができます。

図61.1 サンプルプログラムで回帰直線のグラフを表示したところ
図61.1 サンプルプログラムで回帰直線のグラフを表示したところ
C:\>java Sample_RegLine
y=ax+b
value of a = 0.4660083326656518
value of b = 125.40942232192933
バッチジョブを終了しますか(Y/N)? y

「バッチジョブを~」はCTRL+Cで表示されますので、yキーを押し、エンターキーを打ってください。グラフが画面から消えて、プログラムが終了します。グラフ右上の×ボタンを押してもプログラムは終了しません。この出力は、第59回の図59.9でOfficeソフトを使った結果とまったく同じです。

それでは、以下に未完成のソースコードを示します。83行目からにコードを追加しましょう[4]⁠。コードの足りないのは、最小二乗法を実行して回帰直線の定数を求めるメソッドです。

ソースコード:Sample RegLine.java未完成版
001: /*
002:  * filename : Sample_RegLine.java
003:  * CVSファイルを読み込んで、回帰直線の
004:  * 定数値を求め、グラフ化します。
005:  * compile : c:\>javac Sample_RegLine.java -Xlint:unchecked
006:  * usage : c:\>java Sample_RegLine
007:  */
008: 
009: import java.io.*;
010: import java.util.*;
011: import java.lang.*;
012: import java.awt.*;
013: 
014: 
015: public class Sample_RegLine extends Frame {
016: 
017:   //データファイル名
018:   static String DATAFILE = "./data001.csv";
019: 
020:   //表示するウインドウの最大・最小座標値
021:   static int SCREEN_MAX_X = 300;
022:   static int SCREEN_MIN_X = 0;
023:   static int SCREEN_MAX_Y = 300;
024:   static int SCREEN_MIN_Y = 0;
025:   //
026:   //グラフ中にデータをプロットする丸のサイズ
027:   static int DATA_PLOT_OVAL_WIDTH = 4;
028:   static int DATA_PLOT_OVAL_HEIGHT = 4;
029:   //図形を描画するオブジェクト
030:   MyCanvas mc;
031: 
032:   /**
033:    * メインメソッド
034:    * 簡略のためすべてここから呼び出し
035:    */
036:   public static void main (String args []) {
037: 
038:     //線形最小二乗法によって得られた定数
039:     //y=ax+b のa,b を格納する。val[0]=a,val[1]=b
040:     //基本型は参照渡しできないのでこうしてみる
041:     double val[] = new double[2];
042:     //ファイルから読み込んだデータの一時保管用
043:     Vector v1 = new Vector();
044:     //ファイルから読み込んだ元データは文字列であるため、
045:     //数値に変換したものをこちらに格納する。
046:     Vector DataA = new Vector();
047: 
048:     //データファイル読み込み
049:     try{
050:       readTextFromFile_AndSetVector(DATAFILE,v1);
051:     }
052:     catch(Exception e){
053:       System.out.println(e.toString());
054:       System.exit(-1);
055:     }// of try catch
056: 
057:     //一時読み込みしたデータは文字列なので数値に変換し、
058:     //配列にセットする
059:     KataHenkan(v1,DataA);
060:     //得られた数値データに最小二乗法を適用し、近似
061:     //直線の定数を得る
062:       SenkeiSaishouJijyouHou(DataA,val);
063: 
064:     System.out.println("y=ax+b");
065:     System.out.println("value of a = "+val[0]);
066:     System.out.println("value of b = "+val[1]);
067: 
068:     //ウインドウを作成し、結果を表示
069:     new Sample_RegLine(DataA,val).show();
070: 
071:   }// end of main()
072: 
073: 
074: 
075:   /*
076:    * 目的: 線形最小二乗法を実行
077:    * 引数: data 数値データの配列への参照
078:    *      val[] 戻り値用 回帰直線の定数値
079:    */
080:   static void SenkeiSaishouJijyouHou(Vector Data,
081:                                      double val[]){
082: 
083:   //----ここから
084: 
085:   //----ここまで
086: 
087:   }// end of SenkeiSaishouJijyouHou
088: 
089:   /*
090:    * 目的: CSVの数値データを数値型に型変換
091:    * 引数: v文字列のデータを格納したVector
092:    *      Data変換後のデータを格納したVector
093:    */
094:   static void KataHenkan(Vector v, Vector Data){
095:     for(int i=0; i <= (v.size()-1); i++){
096:       String str = (String)v.get(i);
097:       StringTokenizer st
098:               = new StringTokenizer(str, ",");
099:       Point pos =
100:         new Point(
101:           Integer.parseInt((String)st.nextToken()),
102:           Integer.parseInt((String)st.nextToken())
103:         );
104:       Data.add(pos);
105:     }// of for i
106:   }// end of static void KataHenkan
107: 
108: 
109: 
110:   /*
111:    * 目的: CSVファイルから1行ずつデータを読み込む
112:    * 引数: filenameデータファイルのファイル名
113:    *     vデータファイルから読み込んだデータ
114:    */
115:   static void readTextFromFile_AndSetVector
116:                       (String filename,Vector v) {
117:     try {
118:       FileReader fr = new FileReader(filename);
119:       BufferedReader br = new BufferedReader(fr);
120:       String rdata;
121:       String alldata = "";
122:       while((rdata = br.readLine()) != null) {
123:         v.add(rdata);
124:       }// of while
125:       fr.close();
126:     }catch(Exception e){
127:       System.out.println(e);
128:     }// of try catch
129:   }// of readTextFromFile_AndSetVector
130: 
131:   /*
132:    * 目的: コンストラクタ
133:    * 引数: Dataプロットするデータ
134:    *       val回帰直線の定数
135:    */
136:   public Sample_RegLine
137:                      (Vector Data,double val[]) {
138:     super();
139:     setTitle("最小二乗法のグラフをプロットする");
140:     setSize(SCREEN_MAX_X-SCREEN_MIN_X,
141:             SCREEN_MAX_Y-SCREEN_MIN_Y);
142:     setLayout(null);
143: 
144:     mc = new MyCanvas();
145:     mc.setBounds(SCREEN_MIN_X,SCREEN_MIN_Y, //左上隅の座標値
146:                  SCREEN_MAX_X,SCREEN_MAX_Y);//Width とHeight
147: 
148:     mc.setData(Data);
149:     mc.setVals(val);
150:     this.add(mc);
151:   }// end of Sample_RegLine(コンストラクタ)
152: 
153:   /**
154:    *目的: 描画関係をまとめた。
155:    */
156:   class MyCanvas extends Canvas {
157: 
158:     Vector plotData;
159:     double val[];
160: 
161:     //ウインドウが再描画されるときにデータと
162:     //直線を再描画
163:     public void paint(Graphics g) {
164:       plotPoints();
165:       plotLine();
166:     }// end of paint
167: 
168:     //描画するデータへの参照を受け取る
169:     public void setData(Vector Data){
170:       plotData = Data;
171:     }//end of setData
172: 
173:    //描画する近似直線式の定数を受け取る
174:     public void setVals(double vals[]){
175:       val = vals;
176:     }// end of setVals
177: 
178:     //近似直線を描画する
179:     void plotLine(){
180:       Graphics g=getGraphics();
181:       g.setColor(Color.blue);
182:       g.drawLine(0,-(int)(val[1]) + SCREEN_MAX_Y,
183:         SCREEN_MAX_X,
184:         - (int)(SCREEN_MAX_X*val[0]+val[1]) + SCREEN_MAX_Y);
185:     }// end of plotLine
186: 
187:     //データを画面に点をプロットする。
188:     void plotPoints(){
189:       Graphics g=getGraphics();
190:       g.setColor(Color.red);
191: 
192:       Point temp = new Point();
193:       for (int i=0; i< plotData.size() ; i++){
194:         temp = (Point)plotData.get(i);
195:         g.drawOval((int)temp.getX(),
196:                   -(int)temp.getY() + SCREEN_MAX_Y,
197:                   DATA_PLOT_OVAL_WIDTH,
198:                   DATA_PLOT_OVAL_HEIGHT);
199:       }// of for i
200:     }//end fo plotPoints()
201:   }// end of MyCanvas
202: }// end of this file...

解説

問題 最小二乗法を用いて、回帰直線の定数を求め、データにフィットするグラフを描きましょう。

コードの不足していた部分を示します。いかがでしょう。前回までに学んだややこしい数式の羅列も、コードにしてしまうと、こんなにシンプルです。結局、肝心な部分はこれだけなのです。

ソースコード:Sample RegLine.java完成版
01:   /*
02:    * 目的: 線形最小二乗法を実行
03:    * 引数: data数値データの配列への参照
04:    *      val[] 戻り値用 回帰直線の定数値
05:    */
06:   static void SenkeiSaishouJijyouHou(Vector Data,
07:                                      double val[]){
08:     double x,y,x_sum=0,y_sum=0, xx_sum=0,xy_sum=0;
09:     int n;
10:     Point temp;
11:     n = Data.size();
12:     for(int i=0;i<Data.size();i++){
13:       temp = (Point)Data.get(i);
14:       x=(double)temp.getX();
15:       y=(double)temp.getY();
16:       x_sum+=x;
17:       y_sum+=y;
18:       xx_sum+=x*x;
19:       xy_sum+=x*y;
20:     }// of for i
21:     val[0]= (double) (n*xy_sum-x_sum*y_sum)
22:             / (double) (n*xx_sum-x_sum*x_sum);
23:     val[1]= (double) (xx_sum*y_sum - xy_sum * x_sum)
24:             / (double) (n*xx_sum-x_sum*x_sum);
25:   }// end of SenkeiSaishouJijyouHou

今回はここまで

Java言語を使って回帰直線を得る実習は、首尾よくこなせたでしょうか。表示されたグラフは、Officeソフトで表示したグラフと一致したでしょうか。回帰直線の定数は正しかったでしょうか。Officeソフトで得た定数よりも、精度の高い値が得られたはずです。プログラミング言語を利用する価値は、ここにあります。市販のソフトウエアにできないところに「手を届かせる」ことができるのです。

お疲れ様でした。長いコードを打ち込むことは大変だったことでしょう。

おすすめ記事

記事・ニュース一覧