ggplot2で一部の系列だけハイライトするのに便利なパッケージをつくりたい

ggplot2を使っていると、こんな感じのもじゃもじゃしたデータに出会うことがあります。データの系列が多すぎるともう色が見分けられなくてあんまり意味をなしません。

library(tidyverse)

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()

ggplot(d) +
  geom_line(aes(idx, value, colour = type))

f:id:yutannihilation:20170528155613p:plain:w450

こういうときは、例えば、Y軸方向の最大値が20より大きい系列だけをハイライトしてみよう、みたいな気持ちになるでしょう。

正攻法でやればこんな感じでしょう。まず、データを整形します。最大値が20以下の列にはNAを入れてcolourという列を作ります。

d2 <- d %>%
  group_by(type) %>%
  mutate(max_value = max(value),
         colour = if_else(max_value > 20, type, NA_character_))

ちなみにここでなぜconditionを一度max_valueという列に入れているかというと、if_else()conditionがひとつなのにtruefalseが複数あるという状況だとエラーになるからです(さっき気づいた…)。

if_else(TRUE, 0, 1:10)
#> Error: `false` must be length 1 (length of `condition`), not 10

さておきデータはできたのでグラフを描きます。

ggplot(d2, aes(idx, value, group = type, colour = colour)) +
  geom_line()

f:id:yutannihilation:20170528163037p:plain:w450

まあこれでぜんぜんいいんですけど、探索的にいろいろな条件でグラフを描きたいときは、元データを加工する→ggplot()というのをいったりきたりするのがややめんどうです。

ということで、ggplot2だけでできるパッケージをつくろうとしています(まだあんまりちゃんと動かないです。インストールしたいという奇特な方は、次期バージョンのdplyrが必要なのでdevtools::install_github("tidyverse/dplyr")してから使ってください)

こんな風に書くと、

ggplot(d) +
  geom_line(aes(idx, value, colour = type)) +
  scale_highlight_colour(max(value) > 20)

こんなグラフが描けます。

f:id:yutannihilation:20170528163516p:plain

なんですけど、ここで、

max(value) > 20

と書いていますが、ggplot2の内部実装だと、Scaleオブジェクトは元データを知ることができません。なので、普通にやるとvalueってなに?というエラーで死にます。普通にやると。

さて、普通を強調するからには当然の流れですが、今は普通じゃないことをしています。こんな感じで、引数として元データが渡されないので、呼び出し元の環境を這い上がってむりやりデータを取ってくる、というアクロバットをしています。

    # dirty hack
    index_lapply <- rlang::caller_env(5)$i
    df_raw <- rlang::caller_env(6)$layer_data[[index_lapply]]
    mapping <- unclass(rlang::caller_env(6)$layers[[index_lapply]]$mapping)

(https://github.com/yutannihilation/gghighlight/blob/master/R/scale_highlight.R#L43-L46)

なんかいい方法あるんでしょうか…。ggplot2の内部実装にお詳しい方のタレコミをお待ちしています。