ggplot2でヒストグラムを描くときに外枠だけに線を引きたい

タイトルの通り、「ggplot2でヒストグラムを描くときに外枠だけ線を引きたい」という質問を受けました。図にすると以下のような状況です。

f:id:das_Kino:20181227104628p:plain

枠線を付けたいときにパッと思いつくのは、引数colourを指定することですが、そうすると下図・右のようになってしまいます。

library(ggplot2)

bw <- diff(range(faithful$waiting))/20

# 左図 -------------------------------------------------
ggplot(data = faithful, mapping = aes(x = waiting)) +
  geom_histogram(binwidth = bw, fill = "skyblue") +
  labs(title = "通常のヒストグラム")

# 右図 -------------------------------------------------
ggplot(data = faithful, mapping = aes(x = waiting)) +
  geom_histogram(binwidth = bw, fill = "skyblue", colour = "darkblue") +
  labs(title = "全ての棒に枠線を付けたヒストグラム")

f:id:das_Kino:20181227102610p:plain

で、どうやるんだろうと調べてみました。安直に「ggplot2 histogram only outline」というググり方をしましたが、幸いにもすぐにヒットしました。ggplot2の調べ物は英語でググるのが吉です。
参考:can I draw a histogram that has an outline around the entire shape, but not the individual bars?

ヒストグラムを描くときに、Y軸にマッピングする変数を省略するのではなく、mapping = aes(y = ..density..)としてkernel density estimateをマッピングすることを明示したうえで、geom_step()を用いてヒストグラムの輪郭だけを描くんですね。

f:id:das_Kino:20181227110042p:plain

あとはヒストグラムのレイヤを重ねてやれば、見た目上は外枠だけに線が描かれたように見えるはずです。
ただし注意が必要なのは、ヒストグラムはY軸が度数(count)である点です。そこで以下のページを参考に、ひと工夫します。ポイントは、geom_step(mapping = eval(bquote(aes(y=..count..)))というところです。
参考:データのhistogramと確率密度関数を同時に描きたい

勝った、第3部完!

dens <- density(faithful$waiting)
bw <- diff(range(faithful$waiting))/20

ggplot(data = faithful, mapping = aes(x = waiting)) +
  geom_histogram(binwidth = bw, fill = "skyblue") +
  geom_step(mapping = eval(bquote(aes(y=..count..))),
            stat = "bin",
            binwidth = bw,
            colour = "darkblue") +
  xlim(range(dens$x))

f:id:das_Kino:20181227104455p:plain

ずれとるやんけ!

考えてみたらそりゃそうで、geom_step()が描く線の始点は、各階級幅の中央になるので、階級幅の半分だけずれることになります。仕方がないので、引数positionをいじって、X軸のずれを補正します。

dens <- density(faithful$waiting)
bw <- diff(range(faithful$waiting))/20

ggplot(data = faithful, mapping = aes(x = waiting)) +
  geom_histogram(binwidth = bw, fill = "skyblue") +
  geom_step(mapping = eval(bquote(aes(y=..count..))),
            stat = "bin",
            binwidth = bw,
            position = position_nudge(x = -bw/2), #←階級幅の半分だけ左にずらしている
            colour = "darkblue") +
  xlim(range(dens$x))

f:id:das_Kino:20181227104628p:plain

完成。
ただWarning messages出てるんですよね...。うーん、もうちょっと改善の余地がありそうです。もしもっと適切な方法があれば教えていただけたら嬉しいです。

Warning messages:
1: Removed 2 rows containing missing values (geom_bar). 
2: Removed 1 rows containing missing values (geom_path). 

追記:
atusyさん(@Atushi776)から、「stat_bin()を使うとよい & 引数pad = TRUEを書くとよい」とご提案いただきました。ありがとうございます! 確かにこちらのほうがシンプルで良いですね。そしてWarning messagesも出ない!

bw <- diff(range(faithful$waiting))/20

ggplot(data = faithful, mapping = aes(x = waiting)) +
  geom_histogram(binwidth = bw, fill = "skyblue") +
  stat_bin(binwidth = bw,
           colour = "darkblue",
           geom = "step",
           position = position_nudge(x = -bw / 2),
           pad = TRUE)

Enjoy !