[R] 実録・手作業で行なっていた集計・グラフ化作業をRで自動化した話(Tips編)

R-eyecatch

はじめに

先日は概要編と称して主にエクセルを用いて手作業で実施していた集計・グラフ化作業をRを用いてスクリプトで管理する体制に移行した概要を記述しました。

今回はTips編と称して、集計・グラフ化作業をRのスクリプト化するにあたって役に立ったTipsを10個紹介します。

Table of contents

  1. ライブラリが入っていなかったらインストールする
  2. ディレクトリが無かったら作成
  3. 特定の離散値しかとらないカラムに対してカウント数0も含めて集計
  4. データフレームの因子データを一括で数値データに変換
  5. データフレームをライブラリを用いてエクセルファイルとして出力
  6. グラフの判例を枠外に出す
  7. グラフ内の日本語文字化けを直す
  8. 対散布図と相関の度合いをライブラリを用いてグラフで出力
  9. 調査を重ねるにつれて変化するCSVスキーマの差分吸収
  10. SELECT 集約関数 FROM テーブル GROUP BY カラム名 をRのデータフレームで実現する

ライブラリが入っていなかったらインストールする

下記ブログにまとめました。Rをコマンド実行する度にパッケージをインストールする時間がかかってしまうのを防ぐために有用です。

[R] Rでパッケージを必要に応じてインストールする

ディレクトリがなかったら作成

Rでディレクトリの存在を確認する手段はいくつかありますが、R 3.2.0 からは dir.exists() 関数が新しく呼べるようになり(逆にそれまで無かったのが不思議ですが)、引数に与えたファイルパスのディレクトリがあるかどうか判定できるようになりました。

この関数を用いれば「ディレクトリが無かったら作成する」という作業はRで以下のように表せます。

ifelse(
  !dir.exists(file.path(workDirectory)),
  dir.create(file.path(workDirectory), recursive = T), 
  FALSE
)

workDirectoryはディレクトリのパスを表します。dir.create() 関数では recursiveT をいれることで指定ディレクトリの親がいなくても再帰的につくるようにできます。

特定の離散値しかとらないカラムに対してカウント数0も含めて集計

カラムについて特定の離散値しかとらず、その度数についてのカウントを0を含めて行いたいときには factor 関数を噛ませることで集計対象の離散値を因子に変換したのちに table 関数を用いることで集計を行えます。

例えば次のような、文字列を要素とするデータフレームに対して

> answers
   m1           m2           m3           m4           m5
1  満足 初めて知った         満足         普通     やや満足
2  満足     やや不満         普通         普通         普通

次のように離散値の値をリストとして宣言し

> satisfactionFactors <- c("満足", "やや満足", "普通", "やや不満", "不満", "初めて知った")

その上で因子に変換し、Dataframeのカラム毎に結果を適用する lapply 関数を用いることで次のような結果が得られます。

> result <- t(
>   data.frame(lapply(answers, function(ans) { 
>     as.vector(
>       table(
>         factor(ans, levels = satisfactionFactors)
>       )
>     ) 
>   }))
> )
> colnames(result) <- satisfactionFactors
> result
   満足 やや満足 普通 やや不満 不満 初めて知った
m1    2        0    0        0    0            0
m2    0        0    0        1    0            1
m3    1        0    1        0    0            0
m4    0        0    2        0    0            0
m5    0        1    1        0    0            0

データフレームの因子データを一括で数値データに変換

下記ブログにまとめました。因子データと数値データのマッピング表を作り、それを関数として切り出して、lapply関数に適用することで因子データが数値データにマッピングされるようにしています。

[R] データフレームの因子データを一括で数値データに変換する

データフレームをライブラリを用いてエクセルファイルとして出力

データフレームの内容を成果物としてエクセルファイルに保存したいケースがあるかと思います。openxlsxライブラリにはRからエクセルファイルを扱うためのインターフェイスが提供されており、それを用いて以下のようにどのエクセルのシートに対してデータフレームを貼り付けるかや、特定セルの幅を設定できます。

install.packages('openxlsx')
library(openxlsx)

workbook <- createWorkbook() # ワークブック(エクセルファイルに相当)の作成
addWorksheet(workbook, "Sheet1") # シートの追加
writeData(workbook, sheet="集計結果", x = yourDataframe, rowNames = T, withFilter = F) # シートにデータフレームを書き出し
setColWidths(workbook, sheet="集計結果", cols = c("A", "G"), widths = c(40.0, 13.0)) # 特定のセル列の幅を指定
saveWorkbook(workbook, reportXlsxPath, overwrite = T) # ワークブックをエクセルファイルに書き出し

グラフの判例を枠外に出す

デフォルトのRに用意されているグラフ描画関数について、グラフの判例をグラフ外部に表示したいことがあります。

グラフの描画範囲をずらすためにグラフィックスパラメータを設定するための par 関数を用い、判例描画設定を行うためにlegend 関数を用いれば、下記のようなコードで判例をグラフの枠外に描画することができます。

par(mar=c(5.1, 4.1, 4.1, 8.1), xpd=TRUE)
barplot(memGraphResult, horiz = T, 
        col=rainbow(nrow(yourLegends)))
legend("topright", inset=c(-0.3, 0), legend=yourLegends, 
       fill = rainbow(nrow(yourLegends)))

下記外部エントリが非常に参考になりました。

グラフの日本語文字化けを直す

こちらも OSX 環境では par 関数で描画前にフォントを指定することで実現可能です。

par(family="HiraMaruProN-W4")

下記外部エントリが参考になりました。

対散布図と相関の度合いをライブラリを用いてグラフで出力

目的変数に対して説明変数がどのくらい寄与しているかをサクッと可視化したい場合があります。目的変数と説明変数のデータが揃ったデータフレームがあればライブラリを用いてすぐに以下のように実現可能です。

install.packages('PerformanceAnalytics')
library(PerformanceAnalytics)

chart.Correlation(yourDataframe)

下記外部エントリが参考になりました。

調査を重ねるにつれて変化するCSVスキーマの差分吸収

外部から出力されたCSVのスキーマですが、これをデータフレームに取り込んで調査結果を時間的に比較する際には、なるべくコードの初期の段階で同一のデータフレームとして扱った方が時間がたって新しい調査のCSVスキーマが変化した時の運用コストがさがります。

# CSVのスキーマが調査の度に変わる可能性があるため
# 定点観測集計のための集計、グラフ化は専用スクリプトで行う
# スキーマの差異の吸収のためのコードを運用として極力最小限にし、
# 単一の結果テーブルに対する操作で集計、グラフ化を行う
answers201609 <- read.csv(answers201609CSVPath, stringsAsFactors = FALSE)[, c(3:11, 15:18)]
colnames(answers201609) <-
  c("m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9", 
    "o1", "o2", "o3", "o4")
answers201609$m10 <- NA
answers201609$o5 <- NA
answers201609$month <- "201609"

answers201612 <- read.csv(answers201612CSVPath, stringsAsFactors = FALSE)[, c(10:19, 22:26)]
colnames(answers201612) <-
  c("m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9", "m10", "o1", "o2", "o3", "o4", "o5")
answers201612$month <- "201612"

answers <- rbind(answers201609, answers201612) # スキーマ差異吸収コードはここまで

なるべくコードの早めの段階で統一されたデータフレームとして保存することで、外部の変更に対するコードの変更を少なめにできます。

SELECT 集約関数 FROM テーブル GROUP BY カラム名 をRのデータフレームで実現する

データフレームの特定のカラムの値でレコードをグループ化して平均値等の結果を取得したいことがあります。SQLでの SELECT AVG(col_name) FROM TblName GROP BY group_col_name に相当する操作です。これを実現するためには aggregate 関数を用いて以下のように結果を取得できます。

aggregate(points, list(points$month), function(ps) signif(mean(ps), 3))

この関数の最後の引数で集約関数をカスタマイズして指定していますが、ここでは平均値を取って、有効数字三桁目までを計算しています。

SQLの代表的な操作についてはRのデータフレームをテーブルと見なした時の対応表が以下の外部エントリにまとまっています。大変参考になりました。

まとめ

今回はRを用いて集計・グラフ化の作業を自動化してみました。データの集計作業を自動化することで、定形作業にかかる時間を減らせることに本記事がお役にたてば幸いです。

AWS Cloud Roadshow 2017 福岡