初めに
Excelではy軸が左右にある2軸プロットを簡単に作れるが、ggplot2では簡単には作れない。
現在の業務では2軸グラフだけではなく、3~4軸の多軸なグラフが要求されることがある。
これまでは黒魔術と化したExcelテンプレートでなんとかしてきているが、黒魔術な上、100以上のデータセットに適応しなければならない未来が見えているため、どうにか冬休み中には解法を考えたい。
Baseのプロットを使ったり力技で書く方法もあるが、ggplot2の簡便できれいな図に魂を売った身としてはggplot2で簡単になんとかしたい。
ここに心拍数[bpm]と歩数[steps/min]と速度[m/min]がそろっている1分毎の経時データがある。これをモデルとしてつかっていく。速度(Speed)を折れ線グラフ、心拍数(HeartRate)を、歩数(Steps)を棒グラフで示す。
DateTime | Speed | HeartRate | Steps |
---|---|---|---|
2022-12-01 12:00:00 | 93.41503 | 85.43951 | 107 |
2022-12-01 12:01:00 | 87.36342 | 91.94734 | 110 |
2022-12-01 12:02:00 | 94.70084 | 88.93372 | 97 |
2022-12-01 12:03:00 | 84.55080 | 94.74760 | 97 |
2022-12-01 12:04:00 | 84.42936 | 83.16780 | 99 |
## データセット
df <- tibble(
DateTime = seq(lubridate::ymd_hms("2022-12-01 12:00:00", tz="Asia/Tokyo"), by= 60, length.out=120),
Speed = rnorm(120, mean = 90, sd = 5),
HeartRate = c(rnorm(40, mean = 90, sd = 5),rnorm(40, mean = 110, sd = 10),rnorm(40, mean = 130, sd = 15)),
Steps = floor(c(rnorm(40, mean = 100, sd = 5),rnorm(40, mean = 110, sd = 10),rnorm(40, mean = 120, sd = 15))),
)
グラフを並べる方法について
ggplot2のグラフを並べる方法は、こちらの記事にあるようにいろいろ方法はあるが、x軸を共通にしたいという要望が強いため(できないことはないが)少し目的と外れる。
今回の解法:Facet_wrap
多軸プロットにしたいということは、共通のx軸を持ったグラフを縦方向で比較したいということである。
Facet_wrapを使って、縦一列に並べてあげればいい。
df %>%
pivot_longer(cols = c(Speed, HeartRate, Steps)) %>%
ggplot(aes(x = DateTime, y = value)) +
geom_point(data = ~ filter(.x, name == "HeartRate")) +
geom_line(data = ~ filter(.x, name == "Speed")) +
geom_bar(data = ~ filter(.x, name == "Steps"), stat = "identity") +
facet_wrap(~ name, ncol = 1, scale = "free_y")
facet_wrap(~ name, ncol = 1, scale = "free_y")
ではncol=1で縦一列を指定し、scale = "free_y"でy軸の範囲を適切にしている。ここでは、geom_***(data = ~ filter(.x, name == "***"))
としてあげることで、縦持ちのデータから必要なものだけをを個別にプロットすることができる。
微調整
並び順
グラフの並び順は2通りあるが、1つめの前処理がおすすめである。
- 前処理で対応する方法
縦持ちに変換した後の属性列(name)をFactor化する。この時のlevelsを縦に並べたい順にしておく。df %>% pivot_longer(cols = c(Speed, HeartRate, Steps)) %>% mutate(across(name, factor, levels=c("Speed", "HeartRate", "Steps"))) %>% ggplot(aes(x = DateTime, y = value)) + geom_point(data = ~ filter(.x, name == "HeartRate")) + geom_line(data = ~ filter(.x, name == "Speed")) + geom_bar(data = ~ filter(.x, name == "Steps"), stat = "identity") + facet_wrap(~name, ncol = 1, scale = "free_y")
- facet_wrapの段階でコントロールする方法(非推奨)
原理は一緒であるが、Factor化する場所の違いである。非推奨.Rdf %>% pivot_longer(cols = c(Speed, HeartRate, Steps)) %>% ggplot(aes(x = DateTime, y = value)) + geom_point(data = ~ filter(.x, name == "HeartRate")) + geom_line(data = ~ filter(.x, name == "Speed")) + geom_bar(data = ~ filter(.x, name == "Steps"), stat = "identity") + facet_wrap(~factor(name, levels=c("Speed", "HeartRate", "Steps")) , ncol = 1, scale = "free_y")
ラベルのタイトルと位置
ここまでやれば、最低限はできていますがラベルのタイトルに単位を入れたり、位置を変えた方が見やすいかもしれません。この時facet_wrapでコントロールすると、labeller内での表記が難しくなります。
df %>%
pivot_longer(cols = c(Speed, HeartRate, Steps)) %>%
mutate(across(name, factor, levels = c("Speed", "HeartRate", "Steps"))) %>%
ggplot(aes(x = DateTime, y = value)) +
geom_point(data = ~ filter(.x, name == "HeartRate")) +
geom_line(data = ~ filter(.x, name == "Speed")) +
geom_bar(data = ~ filter(.x, name == "Steps"), stat = "identity") +
facet_wrap(~ name,
ncol = 1,
scale = "free_y",
strip.position = "right",
labeller = labeller(
name =
c(
"HeartRate" = "HeartRate [BPM]",
"Speed" = "Speed [m/min]",
"Steps" = "Steps [steps/min]"
)
))
後は、Y軸の調整がありますが、12/25までに投稿にはここまでが限界でした。 ggh4x::facetted_pos_scalesがうまく動かない。