[R語言圖表]用ggplot畫密度圖 density plot

5

Last Updated on 2023-10-31

Home » R語言教學 » 資料視覺化 » [R語言圖表]用ggplot畫密度圖 density plot

你想畫密度圖(density plot)嗎?用 ggplot2 帶你畫!


想畫密度圖 density plot,但是不知道怎麼在R語言中使用相關函數嗎?要怎麼調整帶寬?跟直方圖 histogram 有什麼差異?要怎麼替圖表增加細節?

我會在這篇文章介紹如何活用R語言的套件ggplot2,配上實際程式碼,帶你畫密度圖。

密度圖 density plot 是什麼?

密度圖(density plot)是一種用來呈現變數分布(distribution)情形的圖表,它跟直方圖(histogram)很像,只是直方圖有按照特定區間將數值分組,例如以10歲為單位,將人口分成0-9歲、10-19歲、20-29歲…等組別。直方圖的廣泛運用,和它在計算上的輕便有關,只要有紙筆就可以快速算出每組有多少個項目。

但現在電腦已經普及,我們不用再受到運算能力的限制,可以直接以連續平滑的曲線表示變數的分布,這就是密度圖出場的時機。同樣是人口的年齡分布,我們可以直接將年齡視作連續的數值變數,並利用核密度估計(kernel
density estimation),估計手上的資料中,特定變數的密度函數,意思就是說我們可以估計「年齡 = 60 歲者」的可能性有多高。

以密度圖呈現村里長候選人年齡分布的例子以密度圖呈現村里長候選人年齡分布的例子

在繪製密度圖的時候,我們想檢視分布情形的變數會會映射(mapping,指的是想呈現的變量對應到圖表中的表示方法)到 x 軸,估計出的密度則會自動到 y 軸,不用另外指定。另外,有一個參數叫做帶寬(bandwidth),它的大小決定機率密度函數有多平滑,當帶寬越大,密度圖的曲線會越平滑,但可能會讓分布的細節消失;帶寬越小,密度圖的曲線會越尖銳,雖然相對不好看,但能保有細部的分布。

在 R 語言中,我們可以快速地利用 ggplot2 套件輕鬆簡單的畫出密度圖。底下會一步一步帶大家實作。

實作篇

資料長相

我們先來匯入資料。從底下的表格可以看到,這份資料2022年九合一選舉中議員候選人的基本資料,欄位有選區、姓名、政黨、性別、生日、年齡、年齡組等。

library(tidyverse)
df_council_candidate_age <- read_csv("../data/ggplot-density-plot.csv")
df_council_candidate_age
#> # A tibble: 1,018 x 7
#>    areaName        name   party      gender birth        age age_group
#>    <chr>           <chr>  <chr>      <chr>  <date>     <dbl>     <dbl>
#>  1 連江縣第1選舉區 曹丞君 中國國民黨 女     1976-07-23  46.3        40
#>  2 連江縣第1選舉區 劉浩晨 民主進步黨 男     1991-08-21  31.3        30
#>  3 連江縣第1選舉區 陳書建 中國國民黨 男     1957-12-20  64.9        60
#>  4 連江縣第1選舉區 林明揚 中國國民黨 男     1963-10-31  59.1        50
#>  5 連江縣第1選舉區 楊清宇 中國國民黨 男     1974-06-16  48.4        40
#>  6 連江縣第2選舉區 周瑞國 中國國民黨 男     1969-04-05  53.6        50
#>  7 連江縣第2選舉區 陳如嵐 中國國民黨 男     1970-12-01  52.0        50
#>  8 連江縣第2選舉區 陳玉發 中國國民黨 男     1960-11-05  62.1        60
#>  9 連江縣第3選舉區 陳貽斌 中國國民黨 男     1969-11-16  53.0        50
#> 10 連江縣第4選舉區 張永江 中國國民黨 男     1960-06-23  62.4        60
#> # … with 1,008 more rows

從直方圖開始

接著,我們利用library(ggplot2)開始畫圖。這張圖想呈現的是台灣議員候選人的年齡分布,年齡映射到 x 軸,分布密度或者次數則映射到 y 軸,所以我們在 ggplot() 裡面代表美學(aesthetics)的aes()中放入年齡(age)作為 x 軸即可。

我們先從直方圖開始,晚點可以比較直方圖和密度圖的差異。在 library(ggplot) 裡面,我們會用 geom_histogram() 畫直方圖。直方圖也有類似帶寬的參數,叫做 binwidth,它的用途是決定每個長條的寬度,也就是要容納 x 軸上各個組別的範圍。

df_council_candidate_age %>% 
  ggplot(aes(x = age)) + 
  geom_histogram(fill = "red", color = "red", alpha = 0.3) +
  ggthemes::theme_clean() +
  theme(axis.text = element_text(family = "Noto Sans TC Regular", size = 20),
        axis.title = element_text(family = "Noto Sans TC Regular", size = 20),
        legend.position = "none")

回到密度圖

重新回到密度圖,我們改用 geom_density(),參數的名字是 bandwidth 的簡稱 bw,因為預設帶寬參數呈現出來的結果很好,所以此處沒有修改。可以看到,直方圖是不連續的,密度圖則是連續的,何者比較好?若是你有切分資料的需求,又或者你非常需要知道某個級距的狀況,則你應該選擇直方圖,其餘狀況則可以用密度圖。

df_council_candidate_age %>% 
  ggplot(aes(x = age)) + 
  geom_density(fill = "red", color = "red", alpha = 0.3) +
  ggthemes::theme_clean() +
  theme(axis.text = element_text(family = "Noto Sans TC Regular", size = 20),
        axis.title = element_text(family = "Noto Sans TC Regular", size = 20),
        legend.position = "none")

調整座標軸

接著使用 scale_x/y_continuous() 加上座標軸的細節,包含名稱與刻度。

df_council_candidate_age %>% 
  ggplot(aes(x = age)) + 
  geom_density(fill = "red", color = "red", alpha = 0.3) +
  scale_x_continuous(limits = c(20,80), breaks = seq(20,80,10), name = "年紀") +
  scale_y_continuous(name = "密度") +
  ggthemes::theme_clean() +
  theme(axis.text = element_text(family = "Noto Sans TC Regular", size = 20),
        axis.title = element_text(family = "Noto Sans TC Regular", size = 20),
        legend.position = "none")


輔助線

為了讓圖表更有意義,不只有密度曲線而已,我們額外補上四分位數(quartile)的輔助線,讓讀者可以知道年齡的第一四分位數、第二四分位數(也就是中位數)、第三四分位數位置落在哪裡。

vector_quartile <- unname(quantile(df_council_candidate_age$age))
vector_quartile
#> [1] 24.44384 40.37740 49.54247 59.07466 79.84384

得到四分位數的值以後,我們利用 geom_vline() 加上垂直線,vline 的 v 就是指 vertical,因此需要加上參數 xintercept。大家應該可以想像,若要加上水平線,則使用的函數與參數分別為 geom_vline()yintercept。如此一來,就完成了!

df_council_candidate_age %>% 
  ggplot(aes(x = age)) + 
  geom_density(fill = "red", color = "red", alpha = 0.3) +
  geom_vline(aes(xintercept = vector_quartile[3]), color = "blue", linetype = "dashed") +
  geom_vline(aes(xintercept = vector_quartile[2]), color = "darkblue", linetype = "dashed") +
  geom_vline(aes(xintercept = vector_quartile[4]), color = "darkblue", linetype = "dashed") +
  scale_x_continuous(limits = c(20,80), breaks = seq(20,80,10), name = "年紀") +
  scale_y_continuous(name = "密度") +
  ggthemes::theme_clean() +
  theme(axis.text = element_text(family = "Noto Sans TC Regular", size = 20),
        axis.title = element_text(family = "Noto Sans TC Regular", size = 20),
        legend.position = "none")


調整帶寬

我們也實際調整帶寬,讓大家看看效果。參數的名字是 bandwidth 的簡稱
bw

df_council_candidate_age %>% 
  ggplot(aes(x = age)) + 
  geom_density(bw = 1, fill = "red", color = "red", alpha = 0.3) +
  geom_vline(aes(xintercept = vector_quartile[3]), color = "blue", linetype = "dashed") +
  geom_vline(aes(xintercept = vector_quartile[2]), color = "darkblue", linetype = "dashed") +
  geom_vline(aes(xintercept = vector_quartile[4]), color = "darkblue", linetype = "dashed") +
  scale_x_continuous(limits = c(20,80), breaks = seq(20,80,10), name = "年紀") +
  scale_y_continuous(name = "密度") +
  ggthemes::theme_clean() +
  theme(axis.text = element_text(family = "Noto Sans TC Regular", size = 20),
        axis.title = element_text(family = "Noto Sans TC Regular", size = 20),
        legend.position = "none")


df_council_candidate_age %>% 
  ggplot(aes(x = age)) + 
  geom_density(bw = 3, fill = "red", color = "red", alpha = 0.3) +
  geom_vline(aes(xintercept = vector_quartile[3]), color = "blue", linetype = "dashed") +
  geom_vline(aes(xintercept = vector_quartile[2]), color = "darkblue", linetype = "dashed") +
  geom_vline(aes(xintercept = vector_quartile[4]), color = "darkblue", linetype = "dashed") +
  scale_x_continuous(limits = c(20,80), breaks = seq(20,80,10), name = "年紀") +
  scale_y_continuous(name = "密度") +
  ggthemes::theme_clean() +
  theme(axis.text = element_text(family = "Noto Sans TC Regular", size = 20),
        axis.title = element_text(family = "Noto Sans TC Regular", size = 20),
        legend.position = "none")


小結

在這篇文章中,我們嘗試畫了一張密度圖(density plot),從直方圖開始繪圖,接續比較直方圖與密度圖的差別,然後調整座標軸、加上輔助線、調整帶寬等。希望你喜歡這篇文章,也能夠增添對於 ggplot2 的認識,並且學到東西。

5 Comments

  1. I may need your help. I’ve been doing research on gate io recently, and I’ve tried a lot of different things. Later, I read your article, and I think your way of writing has given me some innovative ideas, thank you very much.

  2. The point of view of your article has taught me a lot, and I already know how to improve the paper, thank you.

  3. The point of view of your article has taught me a lot, and I already know how to improve the paper, thank you.

  4. I have read your article carefully and I agree with you very much. This has provided a great help for my thesis writing, and I will seriously improve it.

  5. I may need your help. I tried many ways but couldn’t solve it, but after reading your article, I think you have a way to help me. I’m looking forward for your reply. Thanks.

Leave a Reply