見出し画像

高校数学をプログラミングで解く(数学I編)「3-2 データの散らばりと四分位範囲」

マガジンリスト > 数学Ⅰ編 3.データの分析 > 3-2 データの散らばりと四分位範囲


はじめに

今回は、数学Iで学ぶ「データのちらばりと四分位範囲」について、データの四分位数を求め、その結果を利用して箱ひげ図を描くプログラムを作成します。

四分位数と箱ひげ図

まず、四分位数と箱ひげ図について解説しておきます。

四分位数

① 範囲 データの最大値と最小値の差
② 四分位数 データを値の大きさの順に並べたとき、4等分する位置にくる値。小さい方から第1四分位数、第2四分位数(中央値)、第3四分位数といい、これらを順に$${Q_1, Q_2, Q_3}$$で表す。
③ 四分位範囲 $${Q_3-Q_1}$$ 四分位偏差 $${\frac{Q_3-Q_1}{2}}$$

箱ひげ図

データの最小値、第1四分位数、中央値、第3四分位数、最大値を、箱と線(ひげ)で表現する図。

図1 箱ひげ図の例

今回は、与えられたデータに対して四分位数を求めて箱ひげ図を描くプログラムを作成していきます。

利用するデータの準備

以下で作成する各種プログラムで利用するデータは以下の20個の数を利用します。これは、記事『高校数学をプログラミングで解く(数学I編)「3-1 データの整理、データの代表値」』と同じものです。

$$
3 \ 4 \ 9 \ 7 \ 6 \ 10 \ 5 \ 5 \ 7 \ 9 \ 6 \ 8 \ 1 \ 5 \ 7 \ 10 \ 8 \ 6 \ 3 \ 7
$$

なお、プログラムではfloat型の配列として扱います。

  // データ
  int data_num = 20; // データ数
  float[] data = {3,4,9,7,6,10,5,5,7,9,6,8,1,5,7,10,8,6,3,7}; // データ

四分位数を求める

まず、四分位数を求めるプログラムを作成します。

四分位数を求めるためのアルゴリズム

アルゴリズムは以下の手順で行います。
① 与えられたデータを昇順にソートします。ソートについては、記事『高校数学をプログラミングで解く(数学I編)「3-1 データの整理、データの代表値」』で説明しています。その際作成した、データをソートするための関数 bubblesort を利用します(ソースコード1参照)。
② ソートしたデータから、最小値、最大値を求めます。これらは昇順にソートしたデータ(配列)の先頭と末尾の配列の要素が対応します。
③ 次に、第2四分位数を求めます。第2四分位数の導出はデータ数$${N}$$が偶数か奇数かで場合分けが必要になります。
$${N}$$を$${2}$$で割ったときの商を$${h}$$とします。
図2を見てください。データ数$${N}$$が偶数の場合(図2の「データ数が10個の場合」と「データ数が12個の場合」)、第2四分位数$${Q_2}$$は配列の$${h-1}$$番目の要素と$${h}$$番目の要素との平均値となります。一方、データ数$${N}$$が奇数の場合(図2の「データ数が11個の場合」と「データ数が13個の場合」)、第2四分位数$${Q_2}$$は$${h}$$番目の要素となります。
④ 最後に、第1四分位数、第3四分位数を求めます。第1四分位数と第3四分位数の導出は③で計算した$${h}$$が偶数か奇数かで場合分けが必要になります。
$${h}$$を$${2}$$で割ったときの商を$${q}$$とします。
もう一度、図2を見てください。データ数$${h}$$が偶数の場合(図2の「データ数が12個の場合」と「データ数が13個の場合」)、第1四分位数$${Q_1}$$は配列の$${q-1}$$番目の要素と$${q}$$番目の要素との平均値となり、第3四分位数$${Q_3}$$は配列の$${N-q-1}$$番目の要素と$${N-q}$$番目の要素との平均値となります。一方、データ数$${h}$$が奇数の場合(図2の「データ数が10個の場合」と「データ数が11個の場合」)、第1四分位数$${Q_1}$$は$${q}$$番目の要素となり、第3四分位数$${Q_3}$$は$${N-q-1}$$番目の要素となります。

図2 四分位数の算出例

四分位数を求めるプログラム

では、四分位数を求めるプログラムを作成します。

// データの四分位数を算出する
void setup(){

  // データ
  int data_num = 20; // データ数
  float[] data = {3,4,9,7,6,10,5,5,7,9,6,8,1,5,7,10,8,6,3,7}; // データ

  // 四分位数
  float[] quartile = calcquartile(data_num, data);
  println(quartile);
  
}

// バブルソート(昇順)
float[] bubblesort(
  int data_num, // データ数
  float[] data // データの配列
){
  float[] bs = new float[data_num];
  for(int i=0; i<data_num; i++){
    bs[i] = data[i];
  }
  for(int i=0; i<data_num; i++){
    for(int j=1; j<data_num-i; j++){
      if(bs[j-1] > bs[j]){
        float temp = bs[j];
        bs[j] = bs[j-1];
        bs[j-1] = temp;
      }
    }
  }
  return bs;
}

// 四分位数を求める関数
float[] calcquartile(
  int data_num, // データ数
  float[] data // データの配列
){
  // データを昇順にソートする
  float[] bs = new float[data_num];
  bs = bubblesort(data_num, data);

  // 最小値、第1四分位数、第2四分位数、第3四分位数、最大値の順で格納される配列
  float[] Q = {0,0,0,0,0};
  
  // 最小値
  Q[0] = bs[0];
  // 最大値
  Q[4] = bs[data_num-1];
  // 第2四分位数
  int half_size = data_num / 2; // データの半分
  if( data_num % 2 == 0 ){ // データ数が偶数のとき
    Q[2] = ( bs[half_size - 1] + bs[half_size] ) / 2.0;
  } else { // データ数が奇数のとき
    Q[2] = bs[half_size];
  }
  // 第1四分位数と第3四分位数
  if( half_size % 2 == 0){ // データの半分の数が偶数のとき
    Q[1] = ( bs[half_size / 2 - 1] + bs[half_size / 2] ) / 2.0;
    Q[3] = ( bs[data_num - half_size / 2 - 1] + bs[data_num - half_size / 2] ) / 2.0;
  } else { // データの半分の数が奇数のとき
    Q[1] = bs[half_size / 2];
    Q[3] = bs[data_num - half_size / 2 - 1];
  }  
  return Q;
}

ソースコード1 データの四分位数を求めるプログラム

なお、四分位数を求める部分は関数化しており、引数にデータ数「data_num(int型)」とデータ「data(float型の配列)」を渡すと、データを昇順にソートし、ソート結果から、最小値、第1四分位数、第2四分位数、第3四分位数、最大値、の順に値を格納した配列を返す関数となっています。

このソースコード1を、Processingの開発環境ウィンドウを開いて(スケッチ名を「calcQuartile」としています)、テキストエディタ部分に書いて実行すると、図3のようにデータの最小値、第1四分位数、第2四分位数、第3四分位数、最大値、の順に「1.0、5.0、6.5、8.0、10.0」がコンソールに出力されます。

図3 スケッチ「calcQuartile」の実行結果

箱ひげ図を描く

四分位数を求めることができたので、今度は箱ひげ図を描くプログラムを作成します。

箱ひげ図を描くプログラム

箱ひげ図自体は線分と箱で作られているので、line 関数や rect 関数を用いて四分位数の値に合わせて比較的簡単に描くことができます。

// 箱ひげ図を描く
void setup(){
  size(500, 300); // キャンバスの大きさを指定する
  background(255,255,255); // 背景を白色にする
  noFill(); // 図形の塗りつぶしなし
  noLoop(); // 繰り返し処理をしない
  
  // データ
  int data_max_value = 10;
  int data_num = 20; // データ数
  float[] data = {3,4,9,7,6,10,5,5,7,9,6,8,1,5,7,10,8,6,3,7}; // データ

  // 四分位数
  float[] quartile = calcquartile(data_num, data);
//  println(quartile);
  
  // 余白
  float margin = 50.0;
  
  // 表示範囲
  float range = width - 2.0 * margin;
  // 目盛り間の幅
  float segment = range / data_max_value;
  // 目盛りのサイズ
  float segment_size = 10.0;
  
  // 箱ひげ図の両端の線分のサイズ
  float edge_size = 50.0;
  // 箱ひげ図の箱のサイズ
  float box_size = 100.0;
  // 箱ひげ図の中心軸の位置
  float center_line = (height - 2.0 * margin) /2.0;

  translate(margin, margin); // 座標系の原点を移動

  // 軸を描く
  line(0.0, 0.0, range, 0.0);
  // 横軸に目盛りを描く
  fill(0,0,0); // 文字の色を黒色にする
  textSize(20); // 文字のサイズを調整
  textAlign(CENTER, BOTTOM);
  for(int k=0; k<=data_max_value; k++){
    line(segment * k, -segment_size/2.0, segment * k, segment_size/2.0);
    text(k, segment * k, -segment_size/2.0);
  }  
  
  // 箱ひげ図を描く
  noFill();
  // 最小値の線分
  line(segment * quartile[0], center_line - edge_size/2.0, segment * quartile[0], center_line + edge_size/2.0); 
  // 最小値から第1四分位数までの線分
  line(segment * quartile[0], center_line, segment * quartile[1], center_line);
  // 第1四分位数から第2四分位数までの箱
  rect(segment * quartile[1], center_line - box_size/2.0, segment * (quartile[2]-quartile[1]),box_size); 
  // 第2四分位数から第3四分位数までの箱
  rect(segment * quartile[2], center_line - box_size/2.0, segment * (quartile[3]-quartile[2]),box_size);   
  // 第3四分位数から最大値までの線分
  line(segment * quartile[3], center_line, segment * quartile[4], center_line);  
  // 最大値の線分
  line(segment * quartile[4], center_line - edge_size/2.0, segment * quartile[4], center_line + edge_size/2.0); 

}

// バブルソート(昇順)
float[] bubblesort(
  int data_num, // データ数
  float[] data // データの配列
){
  float[] bs = new float[data_num];
  for(int i=0; i<data_num; i++){
    bs[i] = data[i];
  }
  for(int i=0; i<data_num; i++){
    for(int j=1; j<data_num-i; j++){
      if(bs[j-1] > bs[j]){
        float temp = bs[j];
        bs[j] = bs[j-1];
        bs[j-1] = temp;
      }
    }
  }
  return bs;
}

// 四分位数を求める関数
float[] calcquartile(
  int data_num, // データ数
  float[] data // データの配列
){
  // データを昇順にソートする
  float[] bs = new float[data_num];
  bs = bubblesort(data_num, data);

  // 最小値、第1四分位数、第2四分位数、第3四分位数、最大値の順で格納される配列
  float[] Q = {0,0,0,0,0};
  
  // 最小値
  Q[0] = bs[0];
  // 最大値
  Q[4] = bs[data_num-1];
  // 第2四分位数
  int half_size = data_num / 2; // データの半分
  if( data_num % 2 == 0 ){ // データ数が偶数のとき
    Q[2] = ( bs[half_size - 1] + bs[half_size] ) / 2.0;
  } else { // データ数が奇数のとき
    Q[2] = bs[half_size];
  }
  // 第1四分位数と第3四分位数
  if( half_size % 2 == 0){ // データの半分の数が偶数のとき
    Q[1] = ( bs[half_size / 2 - 1] + bs[half_size / 2] ) / 2.0;
    Q[3] = ( bs[data_num - half_size / 2 - 1] + bs[data_num - half_size / 2] ) / 2.0;
  } else { // データの半分の数が奇数のとき
    Q[1] = bs[half_size / 2];
    Q[3] = bs[data_num - half_size / 2 - 1];
  }  
  return Q;
}

ソースコード2 箱ひげ図を描くプログラム

このソースコード2を、Processingの開発環境ウィンドウを開いて(スケッチ名を「drawBoxandWhiskerPlot」としています)、テキストエディタ部分に書いて実行すると、図4のように、実行ウィンドウのキャンバスに箱ひげ図を描きます。

図4 箱ひげ図

プログラムで利用した変数

箱ひげ図を描くプログラムで利用した変数について、まとめておきます(図5)。なお、ここでまとめた変数はいずれもfloat型の変数になります。

図5 箱ひげ図を描くプログラムで利用した変数

まとめ

今回は、数学Iで学ぶ「データのちらばりと四分位範囲」について、データの四分位数を求め、その結果を利用して箱ひげ図を描くプログラムを作成しました。
実際に、自分で箱ひげ図を描いてみると、箱ひげ図の理解、さらにはデータの見方に関する知見が深まったのではないでしょうか。

今回作成したプログラムでは箱ひげ図を横向きに描きましたが、箱ひげ図は縦向きに描くことができますので、縦向きの箱ひげ図を描くプログラムにもチャレンジしてみてください。また、箱ひげ図を描くプログラムは汎用性を持たせた方がよいと思いますので、関数化しておくと便利でしょう。関数化にもぜひチャレンジしてみてください。

参考文献

改訂版 教科書傍用 スタンダード 数学I(数研出版、ISBN9784410209178)

この記事が気に入ったらサポートをしてみませんか?