データ系列が多すぎるとき、いい感じに一部をハイライトするためのパッケージgghighlightをつくりました
ggplot2で可視化しようとして、データ系列が多すぎてこんなもじゃもじゃになってしまう、みたいなことないでしょうか。
これを、一部だけを色付けしてこんな感じのプロットにしてくれるパッケージをつくりました。
インストール
GitHub上からインストールできます。
devtools::install_github("yutannihilation/gghighlight")
gghiglightがやっていること
gghiglightの説明をする前に、まずは上のグラフが何をしているのか、まずはふつうのtidyverseでやってみます。
データはこんな感じのやつです。
library(dplyr, warn.conflicts = FALSE) set.seed(1) d <- tibble( idx = 1:10000, value = runif(idx, -1, 1), type = sample(letters, size = length(idx), replace = TRUE) ) %>% group_by(type) %>% mutate(value = cumsum(value)) %>% ungroup()
これを、value
の最大値が20を超える系列だけに色を付けたいとすると、dplyr::filter()
とかでそのデータだけ抜き出して、ggplot2のレイヤーを重ねるというのが正攻法でしょう。
# 色付けする用のデータだけを抜き出す d_filtered <- d %>% dplyr::group_by(type) %>% dplyr::filter(max(value) > 20) library(ggplot2) ggplot() + # 元のデータはグレーで描画 geom_line(aes(idx, value, group = type), d, colour = alpha("grey", 0.7)) + # 絞り込んだデータは色付きで描画 geom_line(aes(idx, value, colour = type), d_filtered)
まあこれだけのコードではあるんですが、じゃあ最大値が10のやつにしよう、別の値で絞り込もう、とか思うとこのコードをまた上から打ち直すことになります。ちょっとめんどくさいなと思ってパッケージにしました。
gghighlightパッケージなら、上の処理を1行で書けます。
library(gghighlight) gghighlight_line(d, aes(idx, value, colour = type), max(value) > 20)
gghighlightが返すのはggplot2のオブジェクト
この結果はggplot2のオブジェクトなので、テーマを重ねたりfacetしたりできます。
library(ggplot2) gghighlight_line(d, aes(idx, value, colour = type), max(value) > 20) + theme_minimal()
gghighlight_line(d, aes(idx, value, colour = type), max(value) > 20) + facet_wrap(~ type)
適切な閾値が思いつかない時
さて、上ではデータの絞り込みに、
max(value) > 20
という表現を使っていましたが、この20
という閾値はどこからきたのでしょう。これは、私がデータを何度かグラフにしてみて「この辺かな?」というさじ加減で決めたものです。
このように、いい感じの閾値というのは試行錯誤の中で決まってくるものであって、可視化に先立って決められるものではなかったりします。
そこで、gghighlight_line()
にはTRUE
/FALSE
を返す表現だけでなく数値や文字列を返す表現も指定することができます。結果の値が上位の方から、デフォルトだと5つのデータ系列をハイライトします。
gghighlight_line(d, aes(idx, value, colour = type), max(value))
この系列数はmax_highlight
引数で変えることもできます。
gghighlight_line(d, aes(idx, value, colour = type), max(value), max_highlight = 2L)
geomの種類
今のところ、サポートしているgeomは、
- line
- point
の2つです。
gghighlight_point()
は以下のように使います。
d2 <- data.frame( x = c( 1, 1, 1, 2, 2, 2, 3, 3, 3), y = c( 1, 2, 3, 1, 2, 3, 1, 2, 3), value = c( 1, 2, 3, 9, 8, 4, 5, 7, 6), category = letters[1:9], stringsAsFactors = FALSE ) gghighlight_point(d2, aes(x, y), value > 5) #> Warning message: #> In gghighlight_point(d2, aes(x, y), value > 5) : #> Using category as label for now, but please provide the label_key explicity!
ラベルを明示的に指定しない場合は文字列型の列の一つが適当に選ばれますが、label_key
で明示的に指定しておくのがおすすめです(このデータの場合↓でも結果は同じ)。
gghighlight_point(d2, aes(x, y), value > 5, label_key = category)
Grouped vs ungrouped
上のgghighlight_point()
の例で、gghighlight_line()
との絞り込みの表現の違いに気づいたでしょうか?
gghighlight_line()
に指定した表現ではmax()
が使われていますが、これはgghighlight_line()
ではグループ単位での計算(具体的には内部でdplyr::group_by()
とdplyr::summarise()
を使っている)が行われるからです。グループごとに返す値が1つでないと、以下のようなエラーになります。
gghighlight_line(d, aes(idx, value, colour = type), value > 20) #> Error in summarise_impl(.data, dots): Column `predicate..........` must be length 1 (a summary value), not 387
この挙動はuse_group_by
引数で変えることができますが、グループ化されていない折れ線グラフに今のところ意味を見いだせていません。
gghighlight_line(d, aes(idx, value, colour = type), value > 20, use_group_by = FALSE) #> Warning message: #> In gghighlight_line(d, aes(idx, value, colour = type), value > 20, : #> No grouped vars or label_key. #> Falling back to a usual legend...
一方、gghighlight_point()
の場合、デフォルトでは、グループ化されていない計算になっています。こちらもuse_group_by
引数で挙動を変えることができます。折れ線グラフとは違って、こっちは意味のある可視化ができるかもしれません。
set.seed(10) d2 <- sample_n(d, 50) gghighlight_point(d2, aes(idx, value, colour = type), max(value), use_group_by = TRUE, label_key = type)
注意点
gghiglightはこんな感じで便利っぽい(と個人的には思ってるんですがどうでしょう?)んですが、あまり効率的ではありません。データの絞り込みからggplotのビルドまでが毎回走ることになるからです。ある程度すばやく手元で可視化するのには便利ですが、見るべきデータのめどが付いたら自分でコードを書きましょう。
ggplot2は便利なので何でもその中でやりたくなっちゃうんですが、一応、以前にkohskeさんにいただいたこの警句をいつも心に留めているつもりです。
@yutannihilation 効率もありますし、何でもggplot2でやろうとしてハマるのは時間のむだだと思います。外で計算しておく、というのが基本。
— kohske (@kohske) 2015年10月11日
さいごに
基本的に自分の手元で使うためにつくったみたいなパッケージなので、他の人のユースケースと合うのかいまいちわからないんですが、気が向いたら使ってもらえると嬉しいです。
バグ報告、要望などあれば、Twitter(日本語)、このブログのコメント欄(日本語)、GitHubのissues(英語)までお願いします。