情報基礎A 「Cプログラミング」(ステップ7・統計計算・ファイル入力と相関係数)

このページでは、Cを使った基本的な統計計算の方法について考える。

ファイルからデータを読み込んで配列にセットする

コンピュータに統計処理を行わせる場合、プログラムの中に数値データを書き込んでしまうよりは、 データを別ファイルに保存しておいて、プログラムはその内容を読んで、結果を出力する、といった流れのほうが何かと便利だ。 例えば、1万件のデータを処理しなければならない場合、エディタを使ってプログラムにそれを書き込むだけで、相当な手間になるはずだ。 ここでは、データファイルの形式として、データ1件分(1レコード)が1行ごとに、以下のように並んでいる場合を考えよう。

身長と体重データの例

ダウンロード

186 87
180 76
175 67
178 78
174 74
185 86

このデータが、height-vs-weight.txtというファイル名で、Cプログラムと同じディレクトリ(フォルダ)に保存されていると想定する。 以下は、そこからデータを読み出して、値を配列にセットするプログラムの例である:

#include <stdio.h>
#include <math.h>

#define NMAX 1000
main()
{
  FILE *fp ;
  int n=0 ;
  float x, y, x_vs_y[NMAX][2] ;

  fp=fopen("height-vs-weight.txt","r") ;
  if (fp==NULL) return ;
  for (;;) {
    if (fscanf(fp,"%f%f",&x,&y)==2 && n<NMAX) {
      printf("n= %d x= %f y= %f\n",n,x,y) ;
      x_vs_y[n][0]=x ;
      x_vs_y[n][1]=y ;
      n=n+1 ;
    } else break ;
  }
  fclose(fp) ;
}

このプログラム中に登場した新しい記法や関数について、以下で説明する:

#define NMAX 1000
...
float x, y, x_vs_y[NMAX][2] ;
あらかじめ読み込まれるデータの件数は分からないので、先頭部でNMAXという記号を1000と定義し、 配列x_vs_yの要素数をNMAXを使って設定している。 件数が多い場合は、1000のところを10000などに(その一箇所だけを)変更すればよい。 さらに、以降のプログラム中で、読み込まれたデータ件数がNMAXを越えたら、データ読み込みを中止するようプログラムされている。
FILE *fp ;
...
fp=fopen("height-vs-weight.txt","r") ;
ファイルにアクセスするための経路(ストリーム)をfpという名前で用意し、fopen()関数によって、経路を「接続」する。 fopen()関数の最初の引数はファイル名である。二番目の"r"は、データ読み込み用にその経路を設定することを意味する。 概念的には、ファイルにデータの吸い出し口と注入口があって、それらを区別して使うようなイメージである。 ただし、データを読み出して(吸い出して)も、ファイルのデータが消えることは無い。
if (fp==NULL) return ;
ファイルが正常に開かれないとfopen()関数はNULLを返すので、その場合はmain()関数から 出て(returnして)、プログラムを終了する。
if (fscanf(fp,"%f%f",&x,&y)==2 && n<NMAX)
fscanf(fp,...)は、fpで関係づけられたファイルからデータをから読み込む以外は、既出のscanf()と同じ動作をする。 繰り返しfscanf()が呼ばれる度に、ファイルの先頭から順に、末尾に向かってデータが読み込まれる(今どこまで読み出したか、は、Cの処理系が自動的に管理してくれる)。 fscanf()関数から読み込んだデータの数が返されるので、上のプログラムでは、それが2件あったかどうかチェックしている。データが正しく2件ずつ読み込まれなかった(データファイルに間違いがあるか、あるいは、ファイルを最後まで読んで、これ以上読み出しができなくなった)場合は、if文のelse節が実行され、反復が終了する。
fclose(fp) ;
データの読み出しが終わったら、ファイルを閉じておく。

データ間の関係性を調べる:相関係数

二つの量$X$と$Y$の間の関係性を特徴づける量として、相関係数(さらに詳しくは、ここで述べるのはピアソンの相関係数)が広く使われている。 $n$ 点のデータのペア $(x_i,y_i)$ (ただし、$n=0,1,\cdots,n-1$)が与えられたとして、それぞれの平均 $$ E(X) = \frac{1}{n} \sum_{i=0}^{n-1} x_i,\ \ \ E(Y) = \frac{1}{n} \sum_{i=0}^{n-1} y_i $$ および、二乗平均 $$ E(XX) = \frac{1}{n} \sum_{i=0}^{n-1} {x_i}^2,\ \ \ E(YY) = \frac{1}{n} \sum_{i=0}^{n-1} {y_i}^2 $$ を求める。これらを用いると、$X$と$Y$の分散は $$ V_X = E(XX) - E(X)^2 ,\ \ \ V_Y = E(YY) - E(Y)^2 , $$ となる。

同様に、$X$と$Y$の積の平均 $$ E(XY) = \frac{1}{n} \sum_{i=0}^{n-1} x_i y_i $$ から、共分散(covariance) $$ V_{XY} = E(XY) - E(X) E(Y) $$ が得られる。共分散を分散で規格化した量が相関係数で、 $$ r = \frac{V_{XY}}{\sqrt{V_X V_Y}} $$ で与えられる。相関係数は $-1 \le r \le +1$ の値を取る。

横軸と縦軸のスケールをデータのばらつきに合わせ調整した上で、$X$, $Y$を散布図で表現した際に、点が斜め上45度方向の直線に沿って分布すると$r$は1に近く、 右下がり45度方向の直線に沿って分布すると$r$は-1に近い値を取る。 反対に、$r$が0に近い場合は、$X$が増えても$Y$は同じようには(直線的には)増えない、という状況に対応しているが、 このことは、必ずしも$X$と$Y$の間に関係性がないことを意味するわけではない

icon-pc 練習:相関係数

プロ野球球団の選手の身長(cm)、体重(kg)、年齢、推定年俸(万円)などのデータがプロ野球データFreakで公開されている。それを元に、2015年のある球団について、

身長(cm)  体重(kg)  年齢  推定年俸(万円)

の数値を並べたファイルをこちらに用意した。 このファイル(rakuten.txt)を読み込み、 W:身長 X:体重 Y:年齢 Z:年俸 としたとき、年俸との相関係数($R_{WZ}, R_{XZ}, R_{YZ}$)を計算するプログラムを作成せよ。

なお、年俸は、他の変量と較べて、選手(働き)によって桁が違ってくるような量である。 すなわち、金額そのものよりも、その「桁」のほうに注目するのが自然であろう。 そのような観点から、年俸については、log()関数を使い、金額の対数を変量とした解析を行うこと。

年俸と最も相関が強い考えられるのは、身長か、体重か、年齢か?

icon-hint ヒント

データを4件ずつ読み込むためには、このページで示したサンプルプログラムの中で

float x_vs_y[NMAX][2]   →  float wxyz[NMAX][4]

fscanf(fp,"%f%f",&x,&y)==2  → fscanf(fp,"%f%f%f%f",&w,&x,&y,&z)==4

といった書き換えが必要となる。


icon-teacher 解説: 疑似相関

$X$と$Y$の相関係数が大きいからと言って、$X$と$Y$の間に直接的な因果関係があるとは限らない。 たとえば、Spurious Correlationsのサイトには、 疑似相関の例がいくつも紹介されている。

例えば、第三の(隠れた)要因$Z$があって、$X, Y$が共に$Z$に影響されているような場合、$X$と$Y$の間に強い相関が見られる可能性がある。