まずは蝋の翼から。

学んだことを書きながら確認・整理するためのメモブログ。こういうことなのかな?といったことをふわっと書いたりしていますが、理解が浅いゆえに的はずれなことも多々あると思うのでツッコミ歓迎

Rで任意の範囲でヒストグラムを作りたい

問題

ggplotではgeom_histgramを用いるとヒストグラムを作成できる。また、オプションとしてbinwidthを指定するとビン幅を決めることができる。

library(tidyverse)

data <- c( -1, 1, 11, 27, 31)

data %>% 
  tibble() %>% 
  ggplot(aes(x = .)) +
  geom_histogram(binwidth=10, color='white') 

f:id:chito_ng:20200410183326p:plain

上図の各棒は左から 「-5 ~ 5」「5 ~ 10」「10~ 15」,... を区間として値となっている個数を積み上げている。
だが、例えばビン幅10で「-10 ~ 0」「0 ~ 10」「10 ~ 20」...で個数を積み上げたい場合はどうすればいいだろうか?

r-walkalangなどで質問したところ、geom_histgram では任意の区間で作成することは無理で、どの区間かを表す列をカテゴリカルで作成して棒グラフを作らないといけないようだ。

実装

前述の、「どの区間かを表す列」の作成はcut系の関数を使うと作成できる。

cut function | R Documentation

data-hacker.blogspot.com

webbeginner.hatenablog.com

また、cut関数を使いやすくしたcut_interval, cut_width, cut_numberも存在する。

それぞれ、どういった指定で区間カテゴリカルデータを作るかで使い分ける

  • cut_interval : n個の区間を作りたい場合(幅は同じ)
  • cut_width : 幅を揃えた区間を作る(幅は同じ)
  • cut_number : 区間毎に所属する観測値数を揃える(幅は違う)

ggplot2.tidyverse.org

data <- c( -1, 1, 11, 27, 31)

# 5個の区間を作成
#最小-1 最大31の32幅を5で割った6.4が全区間の幅となる
cut_interval(data, n = 5)
# [1] [-1,5.4]   [-1,5.4]   (5.4,11.8] (24.6,31]  (24.6,31] 
# Levels: [-1,5.4] (5.4,11.8] (11.8,18.2] (18.2,24.6] (24.6,31]

# 幅10.0の区間を作る
cut_width(data, width = 10.0)
# [1] [-5,5]  [-5,5]  (5,15]  (25,35] (25,35]
# Levels: [-5,5] (5,15] (15,25] (25,35]

# 幅10.0の区間を作る
# 幅10.0のうち区間の端の位置を0 + 10n にする
# なお、boundryを使わずにcenter = 5.0にすると、中心5.0 + 10n とした区間が作成されるため同じ意味となる。
cut_width(data, width = 10.0, boundary = 0.0)
# [1] [-10,0] (0,10]  (10,20] (20,30] (30,40]
# Levels: [-10,0] (0,10] (10,20] (20,30] (30,40]

cut_width(data, width = 10.0, center = 5.0)
# [1] [-10,0] (0,10]  (10,20] (20,30] (30,40]
# Levels: [-10,0] (0,10] (10,20] (20,30] (30,40]

# 各区間内に2つ入るようにする
# データ数は5なので、2個と3個がそれぞれ入る
cut_number(data, n = 2)
# [1] [-1,11] [-1,11] [-1,11] (11,31] (11,31]
# Levels: [-1,11] (11,31]

これを用いると、『ビン幅10で「-10 ~ 0」「0 ~ 10」「10 ~ 20」...で個数を積み上げ』をおこなえる。 なお、表示される()[<]>を指す。 つまり、[-10,0] (0,10]-10 < x < 0, 0 ≦ x < 10 となる。

data %>% 
  tibble(x = .) %>%
  mutate(x = cut_width(x, width = 10.0, boundary = 0.0)) %>% # 0 + 10nを端
  ggplot(aes(x = x)) + # ヒストグラムではなく棒グラフとして出力
  geom_bar()

f:id:chito_ng:20200410191656p:plain

また、このときx軸のラベル表示を(0,10]形式ではなく、擬似的にヒストグラムのようにしたい場合はcut_...関数のlabelsを使う。

data %>% 
  tibble(x = .) %>%
  mutate(x = cut_width(x, width = 10.0, boundary = 0.0, labels = c(-10, 0, 10, 20, 30))) %>% # 0 + 10nを端 # label指定
  ggplot(aes(x = x)) + # ヒストグラムではなく棒グラフとして出力
  geom_bar()

f:id:chito_ng:20200410192427p:plain

注意点として、geom_histgramのように、値が存在しない区間は表示をされないため注意。

#data <- c( -1, 1, 11, 20, 31)
data2 <- c( -1, 1, 11, 31)

data2 %>% 
  tibble(x = .) %>%
  mutate(x = cut_width(x, width = 10.0, boundary = 0.0, labels = c(-10, 0, 10, 20, 30))) %>% # 0 + 10nを端 # label指定
  ggplot(aes(x = x)) + # ヒストグラムではなく棒グラフとして出力
  geom_bar()

f:id:chito_ng:20200410192300p:plain

追記

cut_widthlabelをリストで渡すと動的になにかしたいときはlistを作成して渡すことになる。正直面倒なので、cut_widthで生成されたfactor型を文字列変換して起点の値を取得してそれで上書きした方が楽かも。

例えば、

  mutate(value_range = cut_width(hoge, width = 0.05, boundary = 0.0),
         value_range = as.numeric(str_match(as.character(value_range), '([0-9].[0-9]+)')[,1]), # 起点の小数部分のみでfactorからnumericにする
         value_range = if_else(is.na(value_range), 0, value_range), # 0は正規表現にひっかからないのでreplace

的な。
正規表現部分が、 [0, 0.1) みたいなときに0は0.0とかじゃないので上手く取れなくてNAになるので無理やり0にreplaceしてるので、 [(から,までの部分を取る風にした方がきれいです。ちょっとぱっと書けなかったので手抜きですが。。。