HOWTO · R

在 R 中建立分組箱線圖

本文介紹如何在 R 中建立分組箱線圖

本頁內容

在 ggplot2 中,分組箱線圖的關鍵只有一個決定:要用哪個變數把資料切分成您想比較的群組。您可以將該變數對映到 fill 美學屬性,讓 geom_boxplot() 在每個 x 位置內繪製並排的箱子;也可以使用 facet_wrap() 把資料拆分成各自獨立的面板。下列範例使用 gapminder 資料集搭配 dplyr 套件,每種方法都能產生您可以套用在自己欄位上的分組箱線圖。

使用 geom_boxplot() 建立分組箱線圖

ggplot 函式通常會搭配 geom_boxplot 來建構箱線圖物件。ggplot 的第一個引數是資料集,第二個引數是以 aes 建立的美學對映清單。在下列對映中,x 和 y 分別指向 gapminder 的 continent 與 lifeExp 欄位,並以 dplyr 函式篩選出三個選定年份。本節範例的程式碼與圖中標籤仍為英文。

if (requireNamespace("ggplot2", quietly = TRUE) &&
    requireNamespace("gapminder", quietly = TRUE) &&
    requireNamespace("dplyr", quietly = TRUE)) {
  library(ggplot2)
  library(gapminder)
  library(dplyr)

  dat <- gapminder %>%
    filter(year %in% c(1972, 1992, 2007))

  print(ggplot(dat, aes(x = continent, y = lifeExp)) +
    geom_boxplot() +
    scale_y_continuous(name = "Average Life Expectancy") +
    scale_x_discrete(labels = abbreviate, name = "Continent"))
}

這個呼叫會為每個洲別各繪製一個箱子。它已經依 x 變數分組,但每個洲別內的年份仍會合併在一起。下一步是加入第二個分組變數。requireNamespace 防護可讓腳本在未安裝繪圖套件的環境中正常結束;此時會跳過繪圖,因此請將這段程式碼視為語法示範,並以本文稍後呈現的圖形為準。

使用 fill 美學屬性為箱子分組

將欄位對映到 fill,會告訴 geom_boxplot 在每個 x 位置內為該變數的各個群組分別繪製一個箱子。但有一個要注意的地方:fill 只有在被對映的變數為離散(discrete)時,才會產生不同的群組。gapminder 中的 year 欄位是數值型,因此 fill = year 會被視為連續的色彩漸層,圖形不會拆分成各年份的箱子。把它包進 factor 可將數字轉換為類別層級,進而產生預期的並排箱子。您可以直接在 R 中看出這個差別(程式碼與輸出仍為英文):

year <- c(1972, 1992, 2007)

print(class(year))
print(class(factor(year)))
print(levels(factor(year)))
[1] "numeric"
[1] "factor"
[1] "1972" "1992" "2007"

數值型的 year 仍是連續值,而 factor(year) 則帶有具名層級——fill 只會對第二種形式展開成各自獨立的箱子。在 ggplot2 中,同樣的區別會決定圖形的版面配置(下列程式碼的座標軸標籤仍為英文):

if (requireNamespace("ggplot2", quietly = TRUE) &&
    requireNamespace("gridExtra", quietly = TRUE) &&
    requireNamespace("gapminder", quietly = TRUE) &&
    requireNamespace("dplyr", quietly = TRUE)) {
  library(ggplot2)
  library(gridExtra)
  library(gapminder)
  library(dplyr)

  dat <- gapminder %>%
    filter(year %in% c(1972, 1992, 2007))

  p1 <- ggplot(dat, aes(x = continent, y = lifeExp, fill = year)) +
    geom_boxplot() +
    scale_y_continuous(name = "Average Life Expectancy") +
    scale_x_discrete(labels = abbreviate, name = "Continent")

  p2 <- ggplot(dat, aes(x = continent, y = lifeExp, fill = factor(year))) +
    geom_boxplot() +
    scale_y_continuous(name = "Average Life Expectancy") +
    scale_x_discrete(labels = abbreviate, name = "Continent")

  grid.arrange(p1, p2, nrow = 2)
}

比較將連續 year 對映到 fill 與將 factor(year) 對映到 fill 的分組箱線圖,只有 factor 版本會繪製各自獨立的箱子

上方面板保留 year 的數值型態,因此維持未分組的狀態;下方面板使用 factor(year),每個洲別顯示三個箱子,分別對應一個選定年份。

使用 facet_wrap() 將群組拆分成多個面板

facet_wrap 函式是另一種依指定變數繪製分組箱線圖的方式,這裡它會為每個年份各建立一個面板。facet_wrap 不需要 fill 對映也能運作,但保留 fill = factor(year) 會為箱子上色,讓各面板更容易區分。下列程式碼的座標軸標籤仍為英文。

if (requireNamespace("ggplot2", quietly = TRUE) &&
    requireNamespace("gapminder", quietly = TRUE) &&
    requireNamespace("dplyr", quietly = TRUE)) {
  library(ggplot2)
  library(gapminder)
  library(dplyr)

  dat <- gapminder %>%
    filter(year %in% c(1972, 1992, 2007))

  p3 <- ggplot(dat, aes(x = continent, y = lifeExp, fill = factor(year))) +
    geom_boxplot() +
    facet_wrap(~year) +
    scale_y_continuous(name = "Average Life Expectancy") +
    scale_x_discrete(labels = abbreviate, name = "Continent")

  print(p3)
}

每年一個面板的分面箱線圖,各面板共用相同的 x 與 y 軸刻度

預設情況下,facet_wrap 產生的每個面板都共用相同的刻度,讓數值可在各面板之間直接比較。傳入 scales = "free" 會讓每個面板依自身包含的資料調整座標軸;這種做法能在資料範圍較窄的面板中呈現更多細節,但也會使跨面板比較產生誤導,因為相同的高度不再代表相同的數值。下列程式碼的座標軸標籤仍為英文。

if (requireNamespace("ggplot2", quietly = TRUE) &&
    requireNamespace("gapminder", quietly = TRUE) &&
    requireNamespace("dplyr", quietly = TRUE)) {
  library(ggplot2)
  library(gapminder)
  library(dplyr)

  dat <- gapminder %>%
    filter(year %in% c(1972, 1992, 2007))

  p4 <- ggplot(dat, aes(x = continent, y = lifeExp, fill = factor(year))) +
    geom_boxplot() +
    facet_wrap(~year, scales = "free") +
    scale_y_continuous(name = "Average Life Expectancy") +
    scale_x_discrete(labels = abbreviate, name = "Continent")

  print(p4)
}

每個年度面板各自調整座標軸刻度的分面箱線圖

選擇版面配置並檢查結果

當群組數量不多、且希望在每個 x 類別內直接比較時,請使用 fill 分組;當群組很多、或每個子集都值得擁有獨立面板時,請使用 facet_wrap。您也可以交換兩者的角色,讓某個變數負責 fill 分組,另一個變數負責分面。下面的範例回到未經篩選的 gapminder 資料集,將 continent 對映到 fill,並讓 facet_wrap 處理所有可用的年份。下列程式碼的座標軸標籤仍為英文。

if (requireNamespace("ggplot2", quietly = TRUE) &&
    requireNamespace("gapminder", quietly = TRUE)) {
  library(ggplot2)
  library(gapminder)

  p5 <- ggplot(gapminder, aes(x = continent, y = lifeExp, fill = continent)) +
    facet_wrap(~year) +
    geom_boxplot() +
    scale_y_continuous(name = "Average Life Expectancy") +
    scale_x_discrete(labels = abbreviate, name = "Continent")

  print(p5)
}

以完整 gapminder 資料集繪製、依年份分面,並將 continent 對映到 fill 色彩的箱線圖

無論選擇哪種版面配置,都請檢查繪製出的圖形,確認每個群組具有預期數量的箱子或面板。如果數值欄位產生的是平滑的色彩圖例而非各自獨立的箱子,請先用 factor 轉換;如果各面板的形狀看起來相同但位置不同,請先確認刻度是共用的,再從圖表上讀取數值。