今回のお題

以下のようなデータから、ヒストグラムを描くのが、今回のお題です。

しかも、今回は凡例まで入ってますが…

データの準備

テキスト添付DVDのExcelファイル「03データのばらつきの記述(量的データ).xlsx」のシート「ヒストグラムその2(データ)」を、ファイル名を「chapter03_2.csv」として、CSV形式で保存しました。他のシートは、元データ以外に余分なものが書き込んであるので、それらを消さないと読み込んだときに、エラーになる場合があります。
データは、項目名にスペースが入っていたりするのが、気に入りませんが、そのまま、保存しました。

『身近な統計(’12)』を受講していない場合は、似たようなデータは日本野球機構のこのあたりから入手できますので、利用して下さい。また、必要に応じて、Rのスクリプトを変更して下さい。

グラフについて

Excelの分析ツールを使って、自動で作成しているので、階級幅が細かい数字になっていて、人にとって無意味な(わかりにくい)分け方になっています。
また、プロット領域内に十分なスペースがあるのに、凡例が外側(下側)にあります。
プロット領域が狭いのは、本来、表現したい情報の領域が狭い(グラフが潰れたりする)ということなので、何とかしたいところです。

作成例

データのことや、グラフのことを考えて作ってみました。
また、(私自身も)できることを増やしたいので、少しずつ、いろいろやっています。

グラフ

こんな感じにできました。

スクリプトの説明

大雑把に説明します。

3行目

read.csv()で、CSV形式のファイルから、データテーブルとして読み込みます。
skip = 2 は、最初の2行を読み飛ばすためです。元のExcelデータは、1行目がデータの表のタイトル、2行目が引用元の説明でしたので、処理するデータとしては必要ないため、読み飛ばします。

8行目

hist()で、ヒストグラムを描く準備をします。
ありがたいことに、hist()は、グラフを描くだけでなく、ヒストグラムに必要な数値処理をして、その結果をリストで返してくれます。

hist()で、直接、ヒストグラムを描くには、例えば、データを読み込んだ状態で、
> hist(ht)
としてみて下さい。

生のデータから、いきなり、ヒストグラムが描けてしまいます。
しかし、何の指定もなく、グラフを描くと、下の目盛やラベルが階級ごとについていなかったり、プロット領域を囲んだときに、一番長い棒が、上の線についてしまうので、必要な調整をした方が、グラフが見やすくなります。

ここでは、plot = FALSE を指定して、グラフを描かないようにして、その返値を利用します。
返ってきた値を変数hに入れているので、確認してみましょう。

> h
$breaks
[1] 0.20 0.22 0.24 0.26 0.28 0.30 0.32 0.34 0.36

$counts
[1]  1  5  9 13 15  9  6  1

$density
[1]  0.8474576  4.2372881  7.6271186 11.0169492 12.7118644  7.6271186  5.0847458  0.8474576

$mids
[1] 0.21 0.23 0.25 0.27 0.29 0.31 0.33 0.35

$xname
[1] "ht"

$equidist
[1] TRUE

attr(,"class")
[1] "histogram"
  • $breaks
    階級の境界です。データをどのような階級に分けたかのかがわかります。
    境界なので、階級の上下があるため、階級の数+1個の要素を持ちます。
    (関数を呼び出すときに、指定することもできます。)
  • $counts
    各階級の度数・頻度です。
  • $densisty
    区階級の密度です。密度でグラフを描いた場合、その面積の合計が1(=100%)になります。
    各階級の度数・頻度を度数合計で割り、さらに階級幅で割った値になっているはずですので、余裕があれば、確認してみて下さい。
  • $mids
    階級値(階級の境界の上下の中間)です。
  • $xname
    ヒストグラムのために、hist()に渡した引数の名前です。
  • $equidist
    $breaks」が等分かどうかを、理論値で表します。ここではTRUEですので、等分です。
  • attr(,”class”)
    このオブジェクト(リスト)のクラス(属性)です。“histogram”になっています。
    この属性があることが、後に重要な役割を果たします。

階級の境界の値は2桁の切りの良いところで収まり、階級幅も0.02刻みになっていて、わかりやすい値になっていることも確認できました。
各階級の度数・頻度もわかりますので、次の行で、その中の最大値を縦軸の調整のために使うよう用意します。

13行目、14行目

plot()で、ヒストグラムを描きます。
plot()は、x,yを与えれば、折れ線グラフや点プロットを描いてくれますが、他の方法にも対応してくれます。
ここでは、変数hを与えていますが、ここで、上に書いた属性が効いてきます。
変数hが”histogram”のクラスのオブジェクトなら、plot(h)はヒストグラムを描いてくれますが、たとえ変数hが、同様な値、同様な構造を持っていたとしても、クラスが”histogram”でなければ、エラーになります。

> h
$breaks
[1] 0.20 0.22 0.24 0.26 0.28 0.30 0.32 0.34 0.36

$counts
[1]  1  5  9 13 15  9  6  1

$density
[1]  0.8474576  4.2372881  7.6271186 11.0169492 12.7118644  7.6271186  5.0847458  0.8474576

$mids
[1] 0.21 0.23 0.25 0.27 0.29 0.31 0.33 0.35

$xname
[1] "ht"

$equidist
[1] TRUE

> plot(h)
 xy.coords(x, y, xlabel, ylabel, log) でエラー: 
  'x' is a list, but does not have components 'x' and 'y'

今回使ったplot()の引数は以下の通りです。

  • h
    ヒストグラムのデータです。
  • main = “”
    ヒストグラムを描かせると「Histogram of 〇〇」(〇〇は$xnameの値)が描画されるので、消します。
  • xlab = “データ区間”, ylab = “頻度”
    横軸、縦軸のタイトルです。
  • ylim = c(0, f_max + 1)
    縦軸を調整しています。調整しないと、最大値が使われます。
  • col = “lightgray”
    色を明るい灰色にしています。…お好みで、どうぞ。
  • yaxs = “i”, xaxs = “i”
    これを指定すると、データの最大値、最小値にあったプロット領域になり、指定しないと、少し余裕を持ったプロット領域になります。
    ヒストグラムの場合は、指定しないと、棒の下が横軸にぴったりつきません。
    ボックスで囲わなければ気になりませんが、囲うと気になるので、指定しました。
  • xaxt = “n”
    横軸は自動で描くと、階級の境界や階級値で上手く描いてくれないことがあるので、横軸を後に別で描くため、ここでは描かないように指定しています。

15行目

axis()で、横軸を描いています。
横軸の目盛の位置(at = …)とラベル(labels = …)にhist()の返値を利用しています。
実は前回、こっそり使っていましたが、tick = FALSE とすると、目盛を描きません。

16行目〜19行目

ここで、累積の折れ線グラフを描いています。
hist()で、大方必要なデータは得られているので、そこから、階級幅と累積を計算し、lines(), points()でグラフを描きます。

20行目、21行目

右側に第2軸を描いています。

24行目〜31行目

ここで、凡例を描いています。
legend()は、legend(x, y, legend, fill) でボックス、legend(x, y, legend, lty, pch)で線と点の凡例が描画できます。
前回、さらっと流したので、ここで使っているlegend()の引数(オプション)を書いておきます。

  • x
    横の位置です。グラフの横軸の値で指定します。
  • y
    縦の位置です。グラフの縦軸の値で指定します。
  • fill
    ボックスの色を指定します。
  • lty
    線のタイプを指定します。
  • pch
    点のタイプまたは文字を指定します。
  • bty
    凡例のエリアを囲む”o”か、囲まない”n”かを指定します。

さらに、棒グラフだけ、折れ線グラフだけなら、ベクトルで複数の情報を与えるだけで、複数の凡例を描けるのです。
しかし、困ったことに、棒グラフと折れ線グラフの凡例を同時に描画する方法が見つけられなかったので、少し工夫してみました。
これまた、ありがたいことに、凡例を描いた位置をリストで返してくれるので、これを利用します。
l1の内容は、以下のようになっていました。

> l1
$rect
$rect$w
[1] 0.01915106

$rect$h
[1] 2.171946

$rect$left
[1] 0.21

$rect$top
[1] 16


$text
$text$x
[1] 0.2197104

$text$y
[1] 14.91403

構造が入れ子になっていているので、$を2回使って、末端の情報を参照します。

  • $rect:凡例を描画したエリアに関する情報
    • $w:幅
    • $h:高さ
    • $left:左の位置
    • $top:上の位置
  • $text:凡例に関する情報
    • $x:横(左側)の位置
    • $y:縦(上側)の位置

まず、1つ目の凡例を描いて、その情報を使って、2つ目の凡例の位置を決めて描き、枠の位置を計算して、最期にrect()で、囲んでいます。
テキストの位置が少しずれますが、ご容赦下さい。m(_ _)m

その他

png(), dev.off()など、直接、ファイルに書き出す関係のところは、コメントアウトして、動作しないようにしています。