3: Rによるデータの可視化

公開

2024年4月24日

更新日

2024年8月5日

はじめに

今回の目的は、ggplot2パッケージを使ってグラフを書く方法を習得することです。 ggplot2パッケージは、前回の講義で利用したdplyr、readrなどのパッケージとともに、 tidyverseというパッケージを構成しています。 そこで今後は、それぞれのパッケージを個別にロードする替わりに、tidyverseをロードすることにします。

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

このように、tidyverseをロードすると、dplyrやreadr、ggplot2などのパッケージ群が一括してロードされます。

さて、今日の演習では、サンプルデータとしてgapminderパッケージのデータも使いますので、初めにロードしておきます。

library(gapminder)

ggplot2入門

初めてのggplot

とりあえず、図を描いてみましょう。データは、ggplot2に含まれるデータセットmpgを使います。

glimpse(mpg)
Rows: 234
Columns: 11
$ manufacturer <chr> "audi", "audi", "audi", "audi", "audi", "audi", "audi", "…
$ model        <chr> "a4", "a4", "a4", "a4", "a4", "a4", "a4", "a4 quattro", "…
$ displ        <dbl> 1.8, 1.8, 2.0, 2.0, 2.8, 2.8, 3.1, 1.8, 1.8, 2.0, 2.0, 2.…
$ year         <int> 1999, 1999, 2008, 2008, 1999, 1999, 2008, 1999, 1999, 200…
$ cyl          <int> 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 8, 8, …
$ trans        <chr> "auto(l5)", "manual(m5)", "manual(m6)", "auto(av)", "auto…
$ drv          <chr> "f", "f", "f", "f", "f", "f", "f", "4", "4", "4", "4", "4…
$ cty          <int> 18, 21, 20, 21, 16, 18, 18, 18, 16, 20, 19, 15, 17, 17, 1…
$ hwy          <int> 29, 29, 31, 30, 26, 26, 27, 26, 25, 28, 27, 25, 25, 25, 2…
$ fl           <chr> "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p…
$ class        <chr> "compact", "compact", "compact", "compact", "compact", "c…

これは、自動車の燃費に関するデータで、1999年から2008年までに発売された234車種についての11項目のデータが含まれています。 詳しい内容を知りたい場合は、mpgのヘルプ記事を参照してください(ヘルプの見方は先週説明しました。覚えていますか?)。

さて、排気量(displ)と高速での燃費(hwy)の散布図を書くには、次のように入力します。

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
  geom_point()

ggplotでは、(1)データに対して、(2)マッピングを設定し、(3)geom関数で作図する、という段階を踏みます。 この時、データはtidyであることが求められます。 マッピングは、x軸とy軸の変数を指定するだけでなく、描画する点の色や大きさに対応する変数などを指定することもできます。 geom関数には、散布図を描くgeom_pointだけでなく、棒グラフを描くgeom_barや折れ線グラフを描くgeom_lineなど、さまざまな関数が用意されています。

tidyデータ

tidyデータとは何かについて、簡単に説明しておきます。 例えば、国ごとの平均寿命のデータが次の表のような形であったとします。

country 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 2002 2007
Afghanistan 28.801 30.332 31.997 34.020 36.088 38.438 39.854 40.822 41.674 41.763 42.129 43.828
Albania 55.230 59.280 64.820 66.220 67.690 68.930 70.420 72.000 71.581 72.950 75.651 76.423
Algeria 43.077 45.685 48.303 51.407 54.518 58.014 61.368 65.799 67.744 69.152 70.994 72.301
Angola 30.015 31.999 34.000 35.985 37.928 39.483 39.942 39.906 40.647 40.963 41.003 42.731
Argentina 62.485 64.399 65.142 65.634 67.065 68.481 69.942 70.774 71.868 73.275 74.340 75.320
Australia 69.120 70.330 70.930 71.100 71.930 73.490 74.740 76.320 77.560 78.830 80.370 81.235

このような表は、スプレッドシート型のデータなどと呼ばれます。 その名のとおり、Excel形式で配布されるデータなどによく用いられる形式です。 このような横に広がった(wide)表は、人間の目で見るにはとてもわかりやすいのですが、 このままではggplotのマッピングには適しません。 どのような形がいいかというと、以下のような縦長の(long)表です。

country year lifeExp
Afghanistan 1952 28.801
Afghanistan 1957 30.332
Afghanistan 1962 31.997
Afghanistan 1967 34.020
Afghanistan 1972 36.088
Afghanistan 1977 38.438

「Afghanistan」という国名が何度も繰り返されていることからもわかるように、tidyデータは最もコンパクトな表現形式ではありません。 人にデータを見せるだけでしたら、スプレッドシート形式の方が優れているので、tidy形式にするメリットはありません。 しかし、tidy形式は、ggplotのマッピング指定など、データ処理のためには優れたデータ形式だと言えます。

tidyデータについてもっと詳しく理解したい人は、 西原史暁(2017)「整然データとは何か」『情報の科学と技術』67(9)pp. 448—453 を読んでみてください。

マッピング

ggplot2のmapping引数には、aes関数の結果を与えます(aesはaestheticの略で、審美的な/視覚的な、といういうような意味です)。 aes関数の中には、x座標やy座標だけでなく、色や大きさなども指定することができます。

ggplot(mpg, aes(x = displ, y = hwy, color = class)) + 
  geom_point()

ggplot(mpg, aes(x = displ, y = hwy, size = cyl)) + 
  geom_point()

ggplot(mpg, aes(x = displ, y = hwy, color = class, size = cyl)) + 
  geom_point()

ggplot関数の第1引数はdata、第2引数はmappingです。 ここでは、data =あるいはmapping =という記述を省略しています。

折れ線グラフ

折れ線グラフを書くには、geom_line関数を使います。 gapminderデータを使った例を示します。 gapminderには、たくさんの国のデータが入っていますので、ここでは日本・韓国・中国のデータを抜き出したものを用意します。

gapminder_cjk <- 
  filter(gapminder, country %in% c("Japan", "Korea, Rep.", "China"))
glimpse(gapminder_cjk)
Rows: 36
Columns: 6
$ country   <fct> "China", "China", "China", "China", "China", "China", "China…
$ continent <fct> Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, …
$ year      <int> 1952, 1957, 1962, 1967, 1972, 1977, 1982, 1987, 1992, 1997, …
$ lifeExp   <dbl> 44.00000, 50.54896, 44.50136, 58.38112, 63.11888, 63.96736, …
$ pop       <int> 556263527, 637408000, 665770000, 754550000, 862030000, 94345…
$ gdpPercap <dbl> 400.4486, 575.9870, 487.6740, 612.7057, 676.9001, 741.2375, …

折れ線グラフを書くには、geom_line関数を使います。 x軸に年、y軸に一人当たりGDPをとった折れ線グラフを描いてみましょう。

ggplot(gapminder_cjk, aes(x = year, y = gdpPercap)) +
  geom_line()

なんだか表示がおかしいですね。 これは、3つの国のデータであるにもかかわらず、1種類のデータしかないと見なされるために、全てのデータを一筆書きのように繋いでしまっています。 うまく3カ国それぞれの折れ線グラフを描画するには、aes関数の中で、group引数を指定する必要があります。

ggplot(gapminder_cjk, aes(x = year, y = gdpPercap, group = country)) +
  geom_line()

マッピングで線の色や種類など指定することができます。

ggplot(gapminder_cjk, aes(x = year, y = gdpPercap)) +
  geom_line(aes(color = country, linetype = country))

棒グラフ

個数カウントによる棒グラフ

mpgに、車のタイプ(class)ごとにどれくらいのデータが含まれているかを棒グラフにしてみます。 geom_bar関数を使うと、データの個数カウントによるグラフを書くことができます。

ggplot(mpg, aes(x = class)) +
  geom_bar()

さらに、駆動の種類(drv)によって色分けしてみます。 いわゆる積み上げ棒グラフですね。

ggplot(mpg, aes(x = class)) +
  geom_bar(aes(fill = drv))

100%の積み上げ棒グラフにするには、geom_bar関数のposition引数に”fill”を指定します。 同時に、y軸の軸ラベルを%表示にして(scalesパッケージのparcent関数を使う)、 さらにylab関数を使って、軸のテキストを「percent」に変更しています。

ggplot(mpg, aes(x = class)) +
  geom_bar(aes(fill = drv), position = "fill") +
  scale_y_continuous(labels = scales::percent) +
  ylab("percent")

値による棒グラフ

値による棒グラフというのは、棒の長さとして使う数字をあらかじめ持っているデータを使って描く棒グラフのことです(言葉で説明しても分かりにくいと思うので、以下の実例を見てください)。

gapminder_asia <- 
  filter(gapminder, continent == "Asia", year == 2007, gdpPercap > 20000)
ggplot(gapminder_asia) + aes(x = country, y = gdpPercap) + geom_col()

ヒストグラム

ヒストグラムを書くには、geom_histgramを使います。 diamondsデータを使った例を示します。

glimpse(diamonds)
Rows: 53,940
Columns: 10
$ carat   <dbl> 0.23, 0.21, 0.23, 0.29, 0.31, 0.24, 0.24, 0.26, 0.22, 0.23, 0.…
$ cut     <ord> Ideal, Premium, Good, Premium, Good, Very Good, Very Good, Ver…
$ color   <ord> E, E, E, I, J, J, I, H, E, H, J, J, F, J, E, E, I, J, J, J, I,…
$ clarity <ord> SI2, SI1, VS1, VS2, SI2, VVS2, VVS1, SI1, VS2, VS1, SI1, VS1, …
$ depth   <dbl> 61.5, 59.8, 56.9, 62.4, 63.3, 62.8, 62.3, 61.9, 65.1, 59.4, 64…
$ table   <dbl> 55, 61, 65, 58, 58, 57, 57, 55, 61, 61, 55, 56, 61, 54, 62, 58…
$ price   <int> 326, 326, 327, 334, 335, 336, 336, 337, 337, 338, 339, 340, 34…
$ x       <dbl> 3.95, 3.89, 4.05, 4.20, 4.34, 3.94, 3.95, 4.07, 3.87, 4.00, 4.…
$ y       <dbl> 3.98, 3.84, 4.07, 4.23, 4.35, 3.96, 3.98, 4.11, 3.78, 4.05, 4.…
$ z       <dbl> 2.43, 2.31, 2.31, 2.63, 2.75, 2.48, 2.47, 2.53, 2.49, 2.39, 2.…

53,940個のダイヤモンドの、重さあたりの価格をヒストグラムにしてみます。

ggplot(diamonds, aes(x = price / carat)) +
  geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

縦軸をカウントではなく密度にするには、マッピングでy = after_stat(density)とします。

ggplot(diamonds, aes(x = price / carat, y = after_stat(density))) +
  geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

重み付きヒストグラムを書くには、マッピングにおいてweight引数として重みに使いたいデータを指定します。

ggplot(diamonds, aes(x = price / carat, y = after_stat(density))) +
  geom_histogram(aes(weight = carat))
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

密度プロット

geom_density関数を使えば、カーネル密度推定を行った結果を描画します。 要するに、ヒストグラムを平滑化したようなグラフが描けます。

ggplot(diamonds, aes(x = price/carat, y = after_stat(density))) + 
  geom_histogram(fill = "grey") + geom_density()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

箱ひげ図

データの分布の様子を図示するときに、箱ひげ図(ボックスプロット)を使うことがあります。 箱ひげ図では、データの四分位数から図を描画します。 ggplot2では、geom_boxplot関数を使って箱ひげ図を描きます。

ggplot(mpg, aes(x = class, y = displ)) +
  geom_boxplot()

近年では、箱ひげ図にかわって、バイオリンプロットやジッタープロットが使われることも増えてきました。 geom_violingeom_jitter関数を使った結果を重ねて表示します。 どのようにデータの分布が要約されるかが分かりやすいと思います。

ggplot(mpg, aes(x = class, y = displ)) +
  geom_violin() + geom_jitter(height = 0, width = 0.1)

テキストの描画

ここではmtcarsデータを使って説明します。 このデータは1974年の「Motor Trend US」という雑誌から抜き出した32車種の燃費のデータです。

mtcars
                     mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Mazda RX4           21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag       21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
Datsun 710          22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive      21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout   18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
Valiant             18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
Duster 360          14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
Merc 240D           24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
Merc 230            22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
Merc 280            19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
Merc 280C           17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
Merc 450SE          16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
Merc 450SL          17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
Merc 450SLC         15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3
Cadillac Fleetwood  10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4
Lincoln Continental 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4
Chrysler Imperial   14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4
Fiat 128            32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
Honda Civic         30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
Toyota Corolla      33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
Toyota Corona       21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1
Dodge Challenger    15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2
AMC Javelin         15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2
Camaro Z28          13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4
Pontiac Firebird    19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2
Fiat X1-9           27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
Porsche 914-2       26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
Lotus Europa        30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
Ford Pantera L      15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4
Ferrari Dino        19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6
Maserati Bora       15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8
Volvo 142E          21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2

さて、プロットした点にラベルをつけたいという場面はよくあります。 そのような時には、geom_text関数を使うことができます。 引数のvjustは、縦方向(vertical)の位置揃え(justification)を調整するものです(引数として与える数字をいろいろ変えてみて、文字の位置がどのように動くか試してみてください)。 同様に、横方向の位置揃えはhjust引数で調節できます。

以下の例は、mtcarsデータを使った散布図です。 横軸は重量(千ポンド)、縦軸は燃費(米マイル/ガロン)です。

ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point() + 
  geom_text(aes(label = rownames(mtcars)), vjust = -0.5)

点の密度が高いので、どうしても文字が重なってしまいます。 このような時には、geom_text関数の引数check_overlapにTRUEを与えることで、重なりを解消することができますが、先に書かれていた文字列が消えてしまします。

ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point() + 
  geom_text(aes(label = rownames(mtcars)), vjust = -0.5,
            check_overlap = TRUE)

ggrepelパッケージのgeom_text_repel関数を使えば、自動的に重なりを考慮して文字列を配置してくれます。

library(ggrepel)
ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point() + 
  geom_text_repel(aes(label = rownames(mtcars)))

点と文字列との距離が遠くなると、自動的に引出し線を書いてくれるなど、 とても便利なパッケージです。

その他よく使うgeom関数

図に補助線を書き入れたいことがよくあります。 切片と傾きを指定した直線を描くgeom_abline、 水平(horizontal)な直線を描くgeom_hline、 垂直(vertical)な直線を描くgeom_vlineなどの関数があります。

ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point() +
  geom_abline(slope = -2, intercept = 30, color = "red") +
  geom_hline(yintercept = 25, color = "green") +
  geom_vline(xintercept = 3, color = "blue") 

軸の入れ替え

縦軸と横軸を入れ替えるには、coord_frip関数を使います。 横棒グラフを描くときなどによく使います。

ggplot(mpg, aes(x = class)) +
  geom_bar(aes(fill = drv)) +
  coord_flip()

対数軸

軸を対数軸にするには、scale_x_log10scale_y_log10を使います。

ggplot(gapminder_cjk, aes(x = year, y = gdpPercap)) +
  geom_line(aes(color = country)) 

ggplot(gapminder_cjk, aes(x = year, y = gdpPercap)) +
  scale_y_log10() + geom_line(aes(color = country)) 

ファセット

gapminderデータには、たくさんの国のデータが入っています。 これを全て表示すると、ごちゃごちゃした図になってしまいます。 全体的な傾向はわかりますが、個別の傾向を見るには不適切な図になってしまいます。

ggplot(gapminder, aes(x = year, y = gdpPercap)) +
  scale_y_log10() +
  geom_line(aes(group = country)) 

このような時には、ファセットを使って、データをグループ化し、グルプーごとの図を並べて表示することが有効です。 ggplot2には、そのためのfacet_関数群が容易されています。

facet_wrap関数は、1つの変数によって分割された図のパネルを並べます。

ggplot(gapminder, aes(x = year, y = gdpPercap)) +
  scale_y_log10() +
  geom_line(aes(group = country)) +
  facet_wrap(vars(continent))

facet_grid関数は、rows引数とcols引数にそれぞれ変数を指定することで、 図のパネルをマトリックス状に並べることができます。

ggplot(diamonds, aes(x = carat, y = price)) +
  geom_point() +
  facet_grid(rows = vars(cut), cols = vars(clarity))

テーマ

グラフの見た目を気にする人は、テーマを変えてみるのがよいかもいれません。 ggplot2には、いくつかのテーマが備えられていますし、ggthemeなどのパッケージを導入すれば、いろいろな見た目のグラフをつくれます。 また、テーマを自作することもできます(が、この講義では触れません)。

ここでは、ggplot2に入っているテーマをいくつか紹介します。

mtcars2 <- within(mtcars, {
  vs <- factor(vs, labels = c("V-shaped", "Straight"))
  am <- factor(am, labels = c("Automatic", "Manual"))
  gear <- factor(gear)
})

デフォルトのテーマは、theme_greyです。

p <- ggplot(mtcars2) +
  geom_point(aes(x = wt, y = mpg, color = gear)) +
  facet_grid(vars(vs), vars(am))
p + theme_grey()

theme_bwは白黒で割とスッキリしたテーマです。

p + theme_bw()

theme_lightは軽い感じのテーマです。

p + theme_light()

theme_linedrawは、くっきりしたテーマです。

p + theme_linedraw()

theme_minimalは、余分な装飾がない、最小限のテーマです。

p + theme_minimal()

自分の好みに合ったテーマを探してみるのも、楽しいと思います。

ggplotにはいくかのカラーパレットが用意されています。 また、RColorBrewerやcolorspaceなどのパッケージには、 より多くのカラーパレットがあります。

p + scale_color_brewer(type = "qual")

p + scale_color_viridis_d()

p + colorspace::scale_color_discrete_qualitative()

図のファイル保存

ggplotで作成した図を、png形式などのファイルで保存するには、ggsave関数が便利です。 この関数を実行すると、最後に描画したグラフを画像ファイルとして保存することができます。

ggsave("my_plot.png")

図のサイズを指定することもできます。

ggsave("my_plot2.png", width = 960, height = 540, units = "px")

おわりに

ggplot2によるグラフ描画は、最初はとっつきにくいかもしれませんが、 システムに慣れてくるととても便利に感じます。 ぜひいろんなグラフを描いてみてください。