StatsFragments

Python, R, Rust, 統計, 機械学習とか

R {ggplot2} で 少し複雑なサブプロットが描きたい

この記事は R Advent Calendar 2015 4 日目の記事です。


{ggplot2} でのサブプロット

{ggplot2} でサブプロットを描画したいことがある。同じ種類のプロットを水準別に描画するなど、単純なものであれば facet で描ける。例えば 適当な散布図を Species 別に描きたい場合、

library(dplyr)
library(ggplot2)

ggplot(iris, aes(Sepal.Width, Sepal.Length)) + 
  geom_point() + 
  facet_wrap(~ Species)

f:id:sinhrks:20151204000924p:plain

facet では異なる種類のプロットをサブプロットとすることはできない。例えば散布図の縦軸/横軸をそれぞれ変えたい、といった場合には gridExtra::grid.arrange を使う。

library(gridExtra)
p1 <- ggplot(iris, aes(Sepal.Width, Sepal.Length)) + geom_point()
p2 <- ggplot(iris, aes(Petal.Width, Petal.Length)) + geom_point()
p3 <- ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point()
p4 <- ggplot(iris, aes(Sepal.Width, Petal.Width)) + geom_point()
grid.arrange(p1, p2, p3, p4)

f:id:sinhrks:20151204000934p:plain

が、grid.arrange は引数を ドット ... で受け取ること、また呼び出すと直ちに描画を行うことから、以下のような場合は少し面倒だ。

  • 描画が直ちに行われるため、サブプロットの描画設定をまとめて変更できない。単一のテーマを使う場合でも、個々のプロットそれぞれについてあらかじめ処理を行っておく必要がある。
  • リストが渡せない。サブプロットの数を動的に変更したい場合は、リストに入れて do.call が必要である。

というわけで、自作パッケージ {ggfortify} でこの辺の操作を少し便利にできるようにした。サブプロット周りの処理は ggmultiplot クラスで行う。

コンストラクタの引数にはリストが渡せるため、サブプロット数が変わる場合でも楽だと思う。また、インスタンスに対して + 演算子を用いることで ggplot と同じ処理を各プロットに対して適用できる。

# install.packages('ggfortify')
library(ggfortify)
p <- new('ggmultiplot', plots = list(p1, p2, p3, p4))
p
# 略。上記 grid.arrange と同じ出力

# theme_bw を全サブプロットに適用
p + theme_bw()

f:id:sinhrks:20151204000945p:plain

サブプロットの要素には リストと同じようにアクセスできる。[ でのアクセスは ggmultiplot インスタンスに、[[ での アクセスは ggplot インスタンスになる。代入もできるため、一部のプロットにだけ処理を適用したい場合は以下のように書ける。

p[2:3] <- p[2:3] + stat_smooth(method = 'lm')
p

f:id:sinhrks:20151204000953p:plain

+ 演算子の右項に ggplot インスタンスを指定するとサブプロットが追加できる。

p + (ggplot(iris, aes(Sepal.Width, fill = Species)) + geom_density())

f:id:sinhrks:20151204002618p:plain

ggmultiplot 作成時に ncol もしくは nrow キーワードを指定することで、サブプロットの列数 / 行数が指定できる。

p <- new('ggmultiplot', plots = list(p1, p2, p3, p4), ncol = 3)
p

f:id:sinhrks:20151204002529p:plain

autoplot との組み合わせ

{ggfortify} を使うと主要なクラスを ggplot2::autoplot 関数で描画できるようになる。autoplot にリストを渡した場合は、リストの各要素が autoplot 可能であればそれらをサブプロットとして描画する。

サンプルとして、{broom} パッケージの vignettes にある例をやってみたい。ここでは、適当なデータに対して クラスタ数を変えながら kmeans クラスタリングを行った結果をプロットしている。

これは autoplot を使って以下のように書ける。

kclusts <- data.frame(k=1:9) %>% 
  dplyr::group_by(k) %>%
  dplyr::do(kclust = kmeans(iris[-5], .$k))
autoplot(kclusts$kclust, data = iris[-5], ncol = 3) + theme(legend.position = "none")

f:id:sinhrks:20151204001005p:plain

一行目は今なら {purrr} を使ったほうが簡潔でわかりやすくなると思う。

library(purrr)
kclusts <- purrr::map(1:9, ~ kmeans(iris[-5], .))
autoplot(kclusts, data = iris[-5], ncol = 3) + theme(legend.position = "none")
# 略

まとめ

{ggfortify} では ggmultiplot クラス、ならびに autoplot を利用してサブプロットを簡単に扱うことができる。

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

R パッケージを CRAN で公開する

少し前に 自作パッケージを CRAN で公開したのだが ブログに書くのを忘れていた。CRAN 公開時の注意点に関して、日本語の説明があまりない / 情報が古いので簡単にまとめたい。

パッケージの作成

この資料を読みましょう。

www.slideshare.net

継続的インテグレーション (CI)

Travis CI は R をサポート (community supportだが) しているため、.travis.yml に2行記載するだけで利用できる。CI 上でパッケージのチェック (R CMD check) も走るので利用したほうが楽。

複数の環境でテストを実行したい場合、Travis CI では Build Matrix という仕組みがある。R では実行したいテストの数だけ env: タグ中に環境変数を記載するのがよいと思う。自分のパッケージでは {ggplot2} の公式版 / GitHub 上の開発版で 2 つのテストを実行している。

そのほかの CI ツール

ほか、R から CI を利用するツールとして、以下のようなものもある。

vignettes の作成

11/30追記 {knitr} で作るのがカンタン。vignettes 化したい {knitr}ドキュメント中に以下のようなタグを含めればよい。VignetteIndexEntry は vignettes へのリンクテキストとして利用される。ASCII文字以外を含める場合、DESCRIPTION ファイル中で Encoding: UTF-8 を指定する。

<!--
%\VignetteEngine{knitr::knitr}
%\VignetteIndexEntry{Introduction to ggfortify package}
-->

参考 Package vignettes: How to build package vignettes with knitr | knitr

パッケージのチェック

開発版 (R-devel) の日次ビルド を利用して、R CMD Check --as-cran package_name.tar.gz で行う。開発版のほうが警告が厳しく出るようだ。

自分は 開発版環境を AWS 上に構築している。 Amazon Linux であれば以下のスクリプトでインストールできると思う。

R CMD check では ERROR, WARNING はもちろん、NOTE も "checking CRAN incoming feasibility" 1 つを除いて全て表示されないようにするのが望ましい。

特に、{dplyr} 等で NSE を使っている場合は xxx: no visible binding for global variable 'xxx' といった NOTE が表示されるが、これらについても CRAN 管理者から指摘される場合がある。 SE を使うか、関数中に (ダミーとして) 同名の変数を作るなどで回避しておくのがよい。

パッケージのサブミット

FTP で直接あげてはダメ。Submit package to CRAN に必要事項を記載して Upload package をする。その後、確認のメールが来たら Confirmation する。

公開待ちのパッケージは 以下の FTP サーバにて確認できる。

パッケージによっては 拡張子の後に以下のようなステータス情報が付けられる場合がある。これは CRAN 公式の情報ではなく、いくつかのフォーラム文書をみて自分が推測したものなので間違っているかもしれない。

  • .save: Upload されたが Confirmation されていないもの。
  • .pending: CRAN 管理者にて何らかの理由でペンディングされているもの (放っておくしかない)。
  • .noemail: Confirmation はされているが確認時のメールが CRAN に飛んでいないもの。

参考 What are the steps in submitting an R-package to CRAN and how long does each step take? - Stack Overflow (ほぼ情報ない)

その後の対応

CRAN 管理者から指摘がきたら適切に対応してください。

パッケージの公開

11/30追記 CRAN 上でのテストとレビューに問題がなければ CRAN 公開後にメールがくる。その後 1日程度すると Windows 用のバイナリのビルドが自動的に行われ、改めて通知がくる。

R {ggplot2} の散布図に凸包 / 確率楕円を描きたい

小ネタ。{ggplot2} でグループ別の散布図を描くときに、ちょっと飾り付けをしてグループをわかりやすくしたい。

凸包 (Convex)

最初にベースとなる散布図を描く。

library(dplyr)
library(ggplot2)

df <- iris

p <- ggplot(df, aes(x = Petal.Width, y = Petal.Length)) + 
  geom_point()
p

f:id:sinhrks:20150110205325p:plain

まずは 散布図全体について凸包をとる。ある点の集合の凸包は、 grDevices::chull で計算できる。chull は凸な点の index を返すので、この返り値に含まれるデータのみをフィルタして geom_polygon に渡せばよい。

chull(df[c('Petal.Width', 'Petal.Length')])
#  [1]  44  17  23  14  33  25 135 123 119 110 145 115

hulls <- df[chull(df[c('Petal.Width', 'Petal.Length')]), ]
hulls
#     Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
# 44           5.0         3.5          1.6         0.6    setosa
# 17           5.4         3.9          1.3         0.4    setosa
# 23           4.6         3.6          1.0         0.2    setosa
# 14           4.3         3.0          1.1         0.1    setosa
# 33           5.2         4.1          1.5         0.1    setosa
# 25           4.8         3.4          1.9         0.2    setosa
# 135          6.1         2.6          5.6         1.4 virginica
# 123          7.7         2.8          6.7         2.0 virginica
# 119          7.7         2.6          6.9         2.3 virginica
# 110          7.2         3.6          6.1         2.5 virginica
# 145          6.7         3.3          5.7         2.5 virginica
# 115          5.8         2.8          5.1         2.4 virginica

p + geom_polygon(data = hulls, alpha = 0.2)

f:id:sinhrks:20150110205337p:plain

凸包をグループ別に描画したい場合は、{dplyr} で各グループへ chull を適用する。

p <- ggplot(df, aes(x = Petal.Width, y = Petal.Length, colour = Species)) + 
  geom_point()

hulls <- df %>%
  dplyr::group_by(Species) %>%
  dplyr::do(.[chull(.[c('Petal.Width', 'Petal.Length')]), ])
hulls
# Source: local data frame [22 x 5]
# Groups: Species
# 
#    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
# 1           5.4         3.9          1.3         0.4     setosa
# 2           4.6         3.6          1.0         0.2     setosa
# 3           4.3         3.0          1.1         0.1     setosa
# 4           5.2         4.1          1.5         0.1     setosa
# 5           4.8         3.4          1.9         0.2     setosa
# 6           5.1         3.8          1.9         0.4     setosa
# 7           5.0         3.5          1.6         0.6     setosa
# 8           5.1         2.5          3.0         1.1 versicolor
# 9           4.9         2.4          3.3         1.0 versicolor
# 10          5.8         2.7          4.1         1.0 versicolor
# 11          6.1         2.8          4.7         1.2 versicolor
# 12          6.0         2.7          5.1         1.6 versicolor
# 13          6.7         3.0          5.0         1.7 versicolor
# 14          5.9         3.2          4.8         1.8 versicolor
# 15          5.8         2.8          5.1         2.4  virginica
# 16          4.9         2.5          4.5         1.7  virginica
# 17          6.0         2.2          5.0         1.5  virginica
# 18          6.1         2.6          5.6         1.4  virginica
# 19          7.7         2.8          6.7         2.0  virginica
# 20          7.7         2.6          6.9         2.3  virginica
# 21          7.2         3.6          6.1         2.5  virginica
# 22          6.7         3.3          5.7         2.5  virginica

p + geom_polygon(data = hulls, alpha = 0.2)

f:id:sinhrks:20150110205352p:plain

参考 以下リンクにある plyr::ddply を使うバージョンを参考にした。

確率楕円 (Probability Ellipse)

こっちは ggplot2 1.0.0 以降なら stat_ellipse 一発なので簡単。

p + stat_ellipse()

f:id:sinhrks:20150110205407p:plain

クラスタリング結果への凸包 / 確率楕円の描画

もともとやりたかったのは {ggplot2} + {ggfortify}クラスタリング結果を autoplot するときに凸包 / 確率楕円を描画すること。これを autoplot 時の frame オプションで指定できるようにした。

ついでに {cluster} パッケージの非階層クラスタリングcluster::clara, cluster::fanny, cluster::pamautoplot できるようにした。

# library(devtools)
# install_github('sinhrks/ggfortify')

library(ggplot2)
library(ggfortify)
df <- iris[-5]

autoplot(kmeans(df, 3), original = iris, frame = TRUE)

f:id:sinhrks:20150110205436p:plain

library(cluster)
autoplot(clara(df, 3), frame = TRUE, frame.type = 'norm')

f:id:sinhrks:20150110205446p:plain

ほか、細かい使い方はこちら。

クラスタリング以外の autoplot についてはこちら。

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

R ggplot2 で線形回帰/一般化線形モデルの残差プロット

ggplot2 で出力している他のグラフとデザインを揃えたかったので作ってみた。

インストール

library(devtools)
install_github('sinhrks/ggfortify')

サンプル

デフォルトの plot と (ほぼ) 同じ見た目にしたつもり。

library(ggplot2)
library(ggfortify)
m <- lm(Petal.Width ~ Petal.Length, data = iris)
autoplot(m, which = 1:6)

f:id:sinhrks:20141109010850p:plain

通常の残差プロット

f:id:sinhrks:20141109010905p:plain

一般化線形モデル

data(anorexia, package = "MASS")
m <- glm(Postwt ~ Prewt + Treat + offset(Prewt), family = gaussian, data = anorexia)
autoplot(m, which = 1:6)

f:id:sinhrks:20141109010918p:plain

通常の残差プロット

f:id:sinhrks:20141109010930p:plain

設定できるオプション

  • which : 描画対象のグラフ (1〜6まで、通常の plot と同じ)
  • colour : データの色
  • label.colour : ラベルの色
  • label.size : ラベルの文字サイズ
  • label.n : ラベルを表示する数
  • smooth.colour : 平滑線の色
  • smooth.linetype : 平滑線のスタイル
  • ad.colour : 補助線の色
  • ad.linetype : 補助線のスタイル
  • ad.size : 補助線のサイズ
  • nrow : 複数プロットする際の行数
  • ncol : 複数プロットする際の列数

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

R ggplot2 で monthplot

Rで時系列データの周期特性をちゃっと確認したいとき、monthplotという関数を使うとデータを月次に分割してグラフ作成してくれる。

monthplot(AirPassengers)

f:id:sinhrks:20141027213938p:plain

が、出力がちょっとアレなのと 月次以外の周期性をみたい場合があるので、 ggplot2 で作ってみた。

library(devtools)
install_github('sinhrks/ggfortify')
library(ggfortify)

ggfreqplot(AirPassengers)

f:id:sinhrks:20141027214052p:plain

6ヶ月ごとの周期をみたいとき。

ggfreqplot(AirPassengers, freq = 6)

f:id:sinhrks:20141027214105p:plain

RPubs - Plotting Time Series Statistics with ggplot2 and ggfortify

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

ggplot2でよく使うパッケージを autoplot する

パッケージを書いた。

つかいかた

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

ggplot2でsurvival::survfitさせた生存曲線を描画する

こちらの survival 版: ggplot2でforecastインスタンスを描画する - StatsFragments

同じように survfit 用の fortify を定義すればよい。lung(肺ガンデータ)を使って、男女別のKaplan-Meier曲線を描いてみる。

library(survival)

library(ggplot2)
library(scales)

d.survfit <- survival::survfit(survival::Surv(time, status) ~ sex,
                               data = lung)

fortify.survfit <- function(survfit.data) {
  data.frame(time = survfit.data$time,
             n.risk = survfit.data$n.risk,
             n.event = survfit.data$n.event,
             n.censor = survfit.data$n.censor,
             surv = survfit.data$surv,
             std.err = survfit.data$std.err,
             upper = survfit.data$upper,
             lower = survfit.data$lower,
             strata = rep(names(survfit.data$strata), survfit.data$strata))
}

head(ggplot2::fortify(d.survfit))
#   time n.risk n.event n.censor      surv    std.err     upper     lower strata
# 1   11    138       3        0 0.9782609 0.01268978 1.0000000 0.9542301  sex=1
# 2   12    135       1        0 0.9710145 0.01470747 0.9994124 0.9434235  sex=1
# 3   13    134       2        0 0.9565217 0.01814885 0.9911586 0.9230952  sex=1
# 4   15    132       1        0 0.9492754 0.01967768 0.9866017 0.9133612  sex=1
# 5   26    131       1        0 0.9420290 0.02111708 0.9818365 0.9038355  sex=1
# 6   30    130       1        0 0.9347826 0.02248469 0.9768989 0.8944820  sex=1

y軸はパーセント表示したいので、scale_y_continuous で設定する。

ggplot(data = d.survfit) +
  geom_line(aes_string(x = 'time', y = 'surv', colour = 'strata')) +
  geom_ribbon(aes_string(x = 'time', ymin = 'lower', ymax = 'upper', fill = 'strata'), alpha = 0.5) + 
  scale_y_continuous(labels = scales::percent)

f:id:sinhrks:20141004193545p:plain

censorを描画したい場合は、

fortified <- ggplot2::fortify(d.survfit)
ggplot(data = fortified) +
  geom_line(aes_string(x = 'time', y = 'surv', colour = 'strata')) +
  geom_ribbon(aes_string(x = 'time', ymin = 'lower', ymax = 'upper', fill = 'strata'), alpha = 0.5) +
  geom_point(data = fortified[fortified$n.censor > 0, ],
             aes_string(x = 'time', y = 'surv'), shape = '+', size = 3) + 
  scale_y_continuous(labels = scales::percent)

f:id:sinhrks:20141004194611p:plain

スクリプト: Allow ggplot2 to handle survival::survfit result · GitHub

※survfit用の描画関数を直接定義するやり方もあるが、けっこう大変。

Creating good looking survival curves – the 'ggsurv' function | R-statistics blog

→ 10/20 追記: パッケージ化してみた。

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

ggplot2でforecastインスタンスを描画する

最近 時系列データを forecast パッケージを使って処理している。便利!

library(forecast)

d <- AirPassengers
d.arima <- forecast::auto.arima(d)
d.forecast <- forecast(d.arima, level = c(95), h = 50)

plot(d.forecast)

f:id:sinhrks:20141004134746p:plain

この結果を、総称関数 plot と同じように ggplot で描画したいが、そのまま ggplot に渡すとエラーになる。

library(ggplot2)
ggplot(data = d.forecast) + geom_line(aes(x = x, y = fitted))
# エラー: ggplot2 doesn't know how to deal with data of class forecast

ggplot では内部で fortify という関数を使い、入力データを data.frame に変換している。ので forecast 用にも fortify を定義してやればよい。

forecastには forecast オブジェクト用の as.data.frame があるが、これは予測値/信頼区間のみしか data.frame 化しない。こちらに原系列を追加してやる必要がある。

fortify.forecast <- function(forecast.data) {
  require(dplyr)
  forecasted <- as.data.frame(forecast.data)
  forecasted$Time <- as.Date(time(forecast.data$mean))

  fitted <- data.frame(Time = as.Date(time(forecast.data$fitted)),
                       Original = forecast.data$x,
                       Fitted = forecast.data$fitted)

  rownames(fitted) <- NULL
  rownames(forecasted) <- NULL

  dplyr::rbind_list(fitted, forecasted)
}

head(ggplot2::fortify(d.forecast))
#         Time Original   Fitted Point Forecast Lo 95 Hi 95
# 1 1949-01-01      112 111.9353             NA    NA    NA
# 2 1949-02-01      118 117.9664             NA    NA    NA
# 3 1949-03-01      132 131.9662             NA    NA    NA
# 4 1949-04-01      129 128.9774             NA    NA    NA
# 5 1949-05-01      121 120.9892             NA    NA    NA
# 6 1949-06-01      135 134.9782             NA    NA    NA

日時, 原系列, 推定値を Time, Original, Fitted カラムとして追加するようにした。これで ggplot から通常の data.frame と同じように扱える。スペースを含む列名はバッククオートでエスケープする。

ggplot(data = d.forecast) +
  geom_line(mapping = aes_string(x = 'Time', y = 'Original')) +
  geom_line(mapping = aes_string(x = 'Time', y = '`Point Forecast`'), colour='blue') +
  geom_ribbon(mapping = aes_string(x = 'Time', ymin = '`Lo 95`', ymax = '`Hi 95`'), alpha = 0.5)

f:id:sinhrks:20141004135949p:plain

スクリプト: Allow ggplot2 to handle forecast result · GitHub

→ 10/20 追記: パッケージ化してみた。

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集