回帰分析の簡単なレポート作成

〜 markdownを利用してhtml, LaTeX→pdf作成 〜

最終更新日: 2013/11/20

 Rとrubyの連携により、markdownの原稿を html と LaTeX の2種類に変換します。

 回帰分析の簡単なレポート作成を材料として取り上げます。


《目次》


《はじめに》

 回帰分析の結果について、簡単なレポートを作成します。

 統計解析ソフトR、ruby、そしてrubyのライブラリkramdownを用います。拙作rrx(rubyからRを利用するためのライブラリ)も使います。

 kramdownは、markdownの原稿をhtml, LaTeXに変換する機能を提供するものです。これを利用して、同じ内容のレポートをhtml, LaTeXの両形式で作成することを考えます。

 LaTeX文書は、dvipdfmxコマンドを用いて、最終的にはpdfにすることを念頭に置いて作成します。

1. サンプルスクリプトの実行環境

 圧縮ファイル lm_s.zip に含まれているサンプルスクリプト lm_htm01.rb などを私が動かした環境は次のとおり。

 素材データ data01.csv がカレントディレクトリにある状態で、次のように実行します。

  ruby lm_htm01.rb [enter]

 そうすると、result201020.htm, cor201020.png, lm201020.png の3つのファイルが書き出されます。後者の2つはグラフの画像ファイルです。

 result201020.htm をブラウザで開くと、レポートを閲覧できます。

 「201020」の数字列は、スクリプトを実行した時の時・分・秒を示します。その時々でかわります。

△ 目次に戻る


2. 回帰分析の材料

 ある喫茶店でのアイスティーの売上げと最高気温が列記されたcsvデータがあるとします。1日ごとにデータが記録されています。

 最高気温が高いほどアイスティーの売上げも上がる、そんな関係を予想したとして、この2つをどのような関係式で示せるかを検証します。

 実は、この事例は、下のサイトで詳しく解説されています。

Rで線形単回帰分析 - matsuou1の日記

 取り上げられているデータは、「マンガでわかる統計学 回帰分析編」のデータのようです。

 圧縮ファイルに同梱してある data01.csv は、基本的にそれと同じです。

 上記サイトを私なりに咀嚼したつもりで(?)、簡単なレポートにしてみました。そのレポートは次のwebで閲覧できます。同文のpdfもあります。

最高気温とアイスティーの売上げの関係に関する回帰分析

pdf版:最高気温とアイスティーの売上げの関係に関する回帰分析

 上のwebを作成するためのrubyスクリプトについて、以下で記します。

 説明の主眼は、Rの分析結果をどのようにして取り込むか、また、それをhtml, LaTeXの文書にどのようにして変換するかというところにあります。回帰分析の意味や方法については、前述のサイトを参照して下さい。

△ 目次に戻る


3. Rプログラム

 Rプログラムでは、データの読み込み、相関のチェック、回帰分析の結果取得、それと散布図等のグラフ作成を行います。グラフはpngファイルを書き出します。

 相関のチェックは cor.test()、回帰分析は lm() で行います。

 それら材料を受け取って、実際のレポートにするのは ruby の役割です。

 下にRプログラムを掲げます。

−−−− Rプログラムここから
dtf <- read.csv("ddd", header=T)
res <- cor.test(dtf$xxx, dtf$yyy)  # 相関の検定
robj(res, "相関")
      # 散布図の作成
png(file="cor<%= id_n %>.png")
par(family = "Japan1GothicBBB")  # 日本語フォントにゴチックを指定
par(mar = c(5, 5, 3, 3))  # 余白
plot(dtf$xxx, dtf$yyy, type="p",
    main="図1:散布図", cex.main=2,  # メインタイトルとその大きさ
    xlab="xxx", ylab="yyy", cex.lab=2)
dev.off()
      # 回帰分析
res <- lm(yyy ~ xxx, data = dtf)
robj(res, "回帰")
res2 <- summary(res)
      # F検定のp値を算出
fx <- res2$fstatistic
res2[["f_p.value"]] <- 1 - pf(fx["value"], fx["numdf"], fx["dendf"])
robj(res2, "回帰要約")
      # 散布図と回帰直線を描く
png(file="lm<%= id_n %>.png")
par(family = "Japan1GothicBBB")  # 日本語フォントにゴチックを指定
par(mar = c(5, 5, 3, 3))  # 余白
plot(dtf$xxx, dtf$yyy, type="p",
    main="図2:散布図と回帰直線", cex.main=2,  # メインタイトルとその大きさ
    xlab="xxx", ylab="yyy", cex.lab=2)
abline(coef = res$coefficients, col = "blue")
dev.off()
−−−− Rプログラムここまで

 上のRプログラムの中に ddd, xxx, yyy のように、小文字のアルファベットが3つ重なるものが出てきます。

 これは、後でrubyの正規表現置換によって「<%= ddd %>」とか「<%= xxx %>」などに置換されます。つまりerbで処理できる形に改められるわけです。

 最初からerb対応の形で記述しておくのが正当なやり方だと思いますが、その簡便さに捨てがたいものがあり、こんなふうにしました。場合によってはトラブルの原因になるかもしれないので、同じやり方を採る時は注意して下さい。

 予想がつくと思いますが、最終的には、dddが読み込むデータファイル名(data01.csv)、xxxが説明変数(最高気温)、yyyは目的変数(アイスティーの売上げ)に置き換えられます。

 「<%= id_n %>」というのが出てきますが、id_nは識別番号です。

 グラフの画像ファイル名を単に cor.png とか lm.png としてもいいのですが、cor01.png, lm01.png のようにした方が、別のレポート作成と混同せずに済むと思って付けたものです。

 実際のサンプルスクリプトでは、この id_n に時刻の「時・分・秒」を割り当てます。スクリプトを実行した時刻です。8時19分32秒であれば 081932 となります。

 グラフは2つ書き出します。図1が散布図、図2は散布図に回帰直線を書き入れたものです。

 相関のチェックには cor.test() を使いましたが、回帰分析のところで決定係数とその有意性を見るので、cor() で相関係数の値をチェックするので十分かもしれません。

 というより、説明変数が2つ以上になる重回帰分析の場合は、それらすべての組合せについて相関を一覧することのできる cor() を用いるのがいいと思います(重回帰分析の例参照)。

 回帰分析の結果については、lm() の戻り値と、それをsummary() にかけたものの2つをrubyに引きわたしています。

 summary() にかけたものだけでもいいのですが、lm() の戻り値の方が、回帰係数を容易に取り出せるので、それも引きわたすようにしました。

 Rのオブジェクトをrubyに引きわたすのは、robj() で行っています。

 なお、lm() の戻り値や、それを summary() にかけたものには、決定係数の有意性を見るためのp値が盛り込まれていません。

 そこで、そのp値を算出して変数resに付け加えます。その上でresをrubyに引きわたしています。

△ 目次に戻る


4. rubyスクリプト

 rubyスクリプトの大まかな流れは次のようになります。

 上の第1ステップ:data_name, xxx, yyy の設定を変更すれば、他の部分を変更しなくても、別のデータに関する回帰分析のレポートを作成できます。xxxとyyyが高い相関関係にあるとの前提で書かれたレポートなので、内容的に的外れになってしまう可能性はありますが、ともあれ同じ雛形のレポートが作成されます。

 圧縮ファイルに同梱してある lm_htm02.rb は、web上のExcelファイルを材料にして、BMIと体脂肪率の関係を分析するものです。

 また、最後の「kramdownによるhtmlの生成」のところを少し書き換えれば、htmlでなくLaTeX文書の作成になります。

 同梱の lm_tex01.rb を実行すると、同じ内容のLaTeX文書が書き出されます。

(1) 基本情報の設定

 必要なライブラリをrequireした後に次の3行があります。

  data_name = "data01.csv"  # 素材データ
  xxx = "最高気温"  # 説明変数(推定の材料)
  yyy = "アイスティーの売上げ"  # 目的変数(推定したい項目)

 これらが回帰分析を行う上での基本情報です。

 この3行を下のように書き換えると lm_htm02.rb になります。

  data_name = "http://www.qmss.jp/databank/preliminaries/data/1-2b-2.xls"
  xxx = "BMI"
  yyy = "体脂肪率"

 上記のExcelファイルは、文科省基準「新体力テスト」(12−19歳)のデータのようです。

 「わかりやすい統計学」で取り上げているもので、多変量データの解説で参考にしているデータです。重回帰分析にも使えると思います。

 この図書の紹介サイトは次のところです。様々なExcelデータをここからダウンロードできます。

『わかりやすい統計学』


(2) 素材データのファイル名に関する調整

 Rプログラムで読み込むファイルの名前を変数 ddd に代入します。

 csvファイルであれば素直に「ddd = "data01.csv"」のようにできますが、Excelファイルの場合は、素材データが書かれているワークシートをcsvファイルとして書き出して(テンポラリファイルに書き出す)、そのファイル名をdddに代入します。

 該当部分は次のとおり。

  if data_name =~ /\.xls$/i
    sheets = Rrx.xls2ary(data_name)  # Excelデータを配列に変換
    tcsv = Rrx.temp_make(sheets[0], ".csv")
    ddd = tcsv.path
  else
    ddd = data_name
  end

 「tcsv = Rrx.temp_make(sheets[0], ".csv")」は、第1ワークシートをcsvファイルとして書き出すための記述です。

 そのcsvファイルのフルパス名は tcsv.path で参照できます。

 tcsv.unlink とすれば、csvファイルを削除できます。Rプログラムを実行した後であれば、削除してかまいません。

 上の記述では拡張子 *.xls のExcelファイルだけを対象にしていますが、rubyライブラリのrooをrequireしていれば、*.xlsx, *.ods なども扱えます。

 MS-Windows環境下でExcelがインストールされているなら、拙作exlap(ruby用のライブラリ)をrequireすると、同じように *.xlsx, *.xlsm, *.ods などを扱うことができます。

 *.xls を扱うだけなら spreadsheet をrequireします。MS-Windows以外の環境でも使えます。


(3) Rプログラムとmarkdown原稿の整理

 Rプログラムとmarkdown原稿は、rubyスクリプトの最後の方に書いてあります。具体的には「__END__」という行の後ろに書いてあります。なので、DATA.read で読み込むことができます。

 Rプログラムとmarkdown原稿の区切りは、「###### 以下はmarkdownの原稿」という行です。行頭に半角のシャープ記号が3個以上あると、その行を区切りとみなして分割します。

 分割する前に、ddd, xxx, yyy などの小文字アルファベット3段重ねを「<%= ddd %>」などに置換します。この置換は、Rプログラムとmarkdown原稿に共通するので、分割前に行います。

 アルファベット3文字に続けて半角数字がくる場合(xxx2など)も置換の対象になります。また、3文字の後に半角下線と英数字がくるケース(xxx_mainなど)、3文字の後に「[……]」がくるケース(xxx["分析結果"]など)も置換の対象になります。

 該当の処理部分は下のとおり。

  src_data = DATA.read  # Rプログラムとmarkdownの雛形文書
  reg = /([a-z])\1\1(\d+|_[A-Za-z0-9]+|[\[].+?[\]])*/
  src_data = src_data.gsub(reg, "<%= \\& %>")  # aaa, xxx などの変換
  r_prog, mkd_str = src_data.split(/^###+.*?\n+/)  # Rとmarkdownの分離
  id_n = Time.now.strftime("%H%M%S") # 時・分・秒:出力ァイル名に盛り込む

 Rプログラムが変数r_prog、markdown原稿が変数mkd_strにセットされます。

 markdown原稿の先頭にある空白行は消去されます。


(4) Rの実行

 r_progをerbにかけて最終的に整え、その上で Rrx.rexec(r_prog) で実行します。

 実行結果は変数 hs にセットします。hsはハッシュ(連想配列)です。

 一応、該当箇所を掲げておきます。

  r_prog = ERB.new(r_prog).result(binding)
  hs = Rrx.rexec(r_prog)  # Rの実行
  tcsv.unlink if tcsv  # csvテンポラリファイルの削除

 Rプログラムが実行中に警告メッセージなどを出すかもしれない、と思った場合は、次の1行を挿入します。

  print hs[:log]  こうすると、Rプログラムの実行結果がすべて標準出力に出力されます。警告メッセージなども出力されます。

(5) 分析結果の整理

 変数hsを参照して、Rの分析結果をrubyの適当な変数にセットします。

 相関の検定結果は cor に代入します、その中でも、相関係数・自由度・p値を cor_result にセットします。

 回帰分析の結果は lm、回帰分析の要約(summary)は lm2 にセットします。

 この lm, lm2 を材料にして、y切片をaaa、xの傾きをbbb、決定係数をrrr、回帰係数に関する一連の情報(2次元配列)をcoefにセットします。

 coefは、2次元配列なので表形式で出力するのが容易ですが、項目名が英語です。例えば、標準誤差が「Std. Error」となっています。これを日本語に変更します。

 該当箇所は次のとおり。

  cor = hs["相関"]  # 相関の検定結果
  cor_result = sprintf("r=%g, df=%d, p-value=%g", cor["estimate"],
      cor["parameter"], cor["p.value"])
  lm = hs["回帰"]  # 回帰分析の結果
  aaa = lm["coefficients"][0]  # y切片
  bbb = lm["coefficients"][1]  # xの傾き
  lm2 = hs["回帰要約"]  # 回帰分析結果のsummary
  rrr = lm2["r.squared"]  # 決定係数
  rrr_main = sprintf("%.1f", rrr*100.0)
  rrr_other = sprintf("%.1f", 100.0-rrr_main.to_f)
  coef = lm2["coefficients"].dup  # 回帰係数
  coef[0] = [nil, "見積り", "標準誤差", "t検定値", "t検定時のp値"]
  coef[1][0] = "定数項"

 「定数項」は intercept を訳したものですが、y切片のことです。


(6) kramdownによるhtmlの生成

 まず、変数 mkd_str (markdownの原稿)をerbにかけて、最終調整します。

 次に、htmlのスタンドアロンを生成するためのテンプレートをテンポラリファイルとして書き出します。「Rrx.html_template()」で行えます。

 あとは kramdown で変換処理を行い、結果をファイルに出力します。

 該当部分は次のとおり。

  mkd_str = ERB.new(mkd_str).result(binding)
  tfile = Rrx.html_template()  # テンプレート生成(tempfile)
  kdoc = Kramdown::Document.new(mkd_str, :template=>tfile.path)
  html_str = kdoc.to_html
  File.open("result#{id_n}.htm", "w") {|ff|  ff.write html_str}
  tfile.unlink  # テンプレートファイルを削除

 この部分を適当に変更すると、LaTeX文書を出力するものになります。

 lm_tex01.rb の該当箇所を掲げてみます。

  mkd_str = ERB.new(mkd_str).result(binding)
  tfile = Rrx.tex_template()  # テンプレート生成(tempfile)
  kdoc = Kramdown::Document.new(mkd_str, :template=>tfile.path)
  tex_str = kdoc.to_latex
  tex_str = tex_str.sub(/^(\\usepackage)(\{graphicx\})/, "\\1[dvipdfmx]\\2")
  File.open("result#{id_n}.tex", "w") {|ff|  ff.write tex_str}
  tfile.unlink  # テンプレートファイルを削除

 テンプレート生成のところが html_template() から tex_template() になっているのと、変換指定の to_html が to_latex になっている点が相違点です。

 その他、変換結果の tex_str に対して sub() で置換処理を施していますが、これは dvipdfmx コマンドでpdfを生成する時に、画像をうまく扱えるようにするためのものです。

  \usepackage{graphicx}

 上の1行を少しだけ書き換えて、次のようにします。

  \usepackage[dvipdfmx]{graphicx}

 こうすると、最終的に生成されるpdfに画像がうまく取り込まれます。

 ちなみに、lm_tex01.rb では、グラフの画像ファイルをpngではなくpdfで生成するようにしています。そのため、Rプログラムとmarkdownの原稿部分のpngの3文字がpdfに変更されています。

 pngのままでもいいかもしれませんが、pdfの方がスムーズに処理されると思います。

 LaTeX文書を基にしてpdfファイルを生成する方法については、次のサイトで紹介しているので、必要に応じて参照して下さい。

kramdownに関する覚え書き 〜 markdown, html, LaTeX の変換処理

 以上がrubyによる処理です。

△ 目次に戻る


5. markdownの原稿

 html用の原稿とLaTeX用の原稿には僅かな違いがあります。先にもちょっと触れましたが、html用ではグラフをpng形式で書き出し、LaTeXではpdf形式で書き出します。

 しかし、違いはそれだけです。html用の中のpngの3文字をpdfに置換すると、LaTeX用と同じものになります。kramdownによって変換処理するため、ほぼ同じ原稿から html, LaTeX の2種類を生成できます。

 もちろん、実際には個別の調整を加えたくなる点が出てきます。例えば、今回生成されるpdfでは、グラフの画像が文章中に挿入されるのではなく、まるまる1ページを占領します。これを避けようと思うと、画像の大きさを調整したり、「なるべく文中に挿入するよう努めなさい」という命令をLaTeX文書に埋め込む必要があります。

 とはいえ、基本的に同じ原稿から html, LaTeX の2種類を作ることができるのは、いろいろと便利です。

 markdownの原稿をこの後に掲げますが、その中に、普通のmarkdown記述にはないものとして次の1行があります。

  <%= Rrx.matrix2table(coef, 'border="1"') %>

 matrix2table() は、Rから引きわたされた2次元配列(ここでは coef)をmarkdownの表に変換するメソッドです。markdownの表は、例えば次のようなものです。

  |名称|電話番号|
  |時刻|117|
  |天気予報|177|

 サンプルに出てくるcoefには、回帰係数の値、標準誤差、t検定量、t検定のp値(両側)が2次元配列としてセットされています。これを表形式で出力します。

 今回は単回帰分析なので、見出しを除くと2行だけの表ですが、説明変数が2つ以上になると(つまり重回帰分析の場合)、この表の行数が増えます。

 markdown記法については説明を省略します。多くの解説サイトがあるので、必要に応じて参照して下さい。

 以下、長くなりますがmarkdownの原稿を掲げておきます。このサイトは、それでおわりです。

−−−−−−−− 原稿ここから
# xxxとyyyの関係に関する回帰分析

ここにxxxとyyyを列記したデータがある。これについて回帰分析を行う。

xxxを材料にしてyyyを推定できるか調べる。
このような推定を専門的表現で「yyyをxxxに回帰させる」というらしい。

## 相関の確認

まず、回帰分析を行う意味のあるデータなのかどうかを確認する。

2つの項目の相関が認められなければ、
回帰分析の結果が実質的な意味を持たない。

「xxx」と「yyy」の相関係数を確認すると次のとおり。

<%= cor_result %>

有意な強い相関が認められる。

ちなみに、散布図は図1のようになる。

![図1:散布図](cor<%= id_n %>.png)

* * * * * * * *

## 回帰分析

強い相関が認められるので、回帰分析を行ってみた。その結果は次のとおり。

- y切片: aaa
- xの傾き: bbb

つまり、次のような推定のための計算式(回帰式)が求められる。

「yyy」=aaa+bbb×「xxx」

上の回帰式によって描かれる直線が回帰直線である。

散布図に回帰直線を書き入れてみると図2のようになる。

![図2:散布図と回帰直線](lm<%= id_n %>.png)

個々の実測値と回帰式から得られる値との間には誤差(残差)がある。
すべての実測値についてこの誤差の平方和が最小になるように
考え出されたのが上の回帰式である。

* * * * * * * *

## 回帰式の信頼性:決定係数

先の回帰式がどの程度信頼できるかをみる一つの手がかりは、決定係数である。

- 決定係数: rrr
- F検定の値: <%= lm2["fstatistic"][0] %> (決定係数に関するF検定量)
- F検定のp値: <%= lm2["f_p.value"] %>

決定係数は、1に近いほど信頼性が高い。

今回の rrr という値は、「yyy」の変動が「xxx」の変動によって
rrr_main%だけ説明できることを意味している。

残りのrrr_other%は、回帰式に含まれていない別の要素が関係しているとみられる。

決定係数の信頼性はF検定の結果により判断する。もっともっと幅広く
調査したとき、もしかすると決定係数が0になるかもしれない。その危険性を
示すのがF検定のp値である。これが0.05(5%)とか0.01、あるいは0.001より
小さければ、そのレベルに応じた有意性が認められることになる。

このp値が0.05以上だと、「母集団において決定係数が0である」
という仮説を、母集団に関して棄却できないことになる。

## 回帰式の信頼性:回帰係数

回帰係数(coefficient:y切片やxの傾きの関連)
に関する詳細な情報を掲げると次のとおり。

<%= Rrx.matrix2table(coef, 'border="1"') %>

この表で、1行目の「定数項」の「見積り」はy切片の値であり、
2行目の「<%= coef[2][0] %>」の「見積り」の欄はxの傾きの値である。

重回帰分析の場合は、説明変数の個数に応じて、
3行目・4行目・……が表示されることになる。

表の右端の「t検定時のp値」は、該当の変数が実は影響力を
持たないかもしれない危険性(確率)を示す。

xの傾きが0であれば、xの値が何であってもyの値に影響しない。

xxxの傾きがbbbと見積もられてはいるが、もっともっと調査対象を拡げると、
実は0ということもあるかもしれない。その「かもしれない」の確率がp値である。
p値が十分小さければ、有意性が認められることになる。

定数項のp値については、実際に言及することは少ないと思うが、
もっともっと幅広く調査した時にy切片が0であるかもしれない確率を示す。
−−−−−−−− 原稿ここまで

〜 以上 〜


Copyright (C) T. Yoshiizumi, 2013 All rights reserved.