[R語言圖表]用ggplot畫克里夫蘭點圖 Cleveland dot plot

5

Last Updated on 2023-10-31

Home » R語言教學 » 資料視覺化 » [R語言圖表]用ggplot畫克里夫蘭點圖 Cleveland dot plot

你想畫克里夫蘭點圖(Cleveland dot plot)嗎?用 ggplot2 帶你畫!


想畫克里夫蘭點圖,但是不知道怎麼呈現時間變化嗎?透明度怎麼調整?點點之間怎麼連線?點點的順序又要怎麼排?

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

克里夫蘭點圖 Cleveland dot plot 是什麼?

克里夫蘭點圖(Cleveland dot
plot)是一種相對沒那麼常見、卻是非常實用的圖表,它利用同一類型點點的位置變化,用來表示隨著時間變動的數值,舉例來說,它可以用來呈現某支籃球隊每個球員兩場比賽的得分變化、台灣各個縣市十年前和現在的人口位移、台灣各個政黨上屆和這屆議會中的席次等。

以克里夫蘭點圖呈現籃球隊隊員兩輪表現變化的例子以克里夫蘭點圖呈現籃球隊隊員兩輪表現變化的例子
 

一般來說,在繪製克里夫蘭點圖的時候,事物類型會映射(mapping,指的是想呈現的變量對應到圖表中的表示方法)到 x 軸,事物的數值則會映射到 y 軸,政黨以顏色表示,接著再利用透明度,表示不同的時間。

上面舉的例子都只有兩個時間點,因為人在閱讀圖表時的認知資源有限,我們當然可以用三個點,例如2014、2018、2022三屆的各黨議會席次變化,然而,若各黨在3年的席次變化方向不同,可能A黨先增後降,B黨先降後增,這時候利用顏色與透明度變化讀起來,會顯得吃力,一下往前一下又往後,所以我們通常都只在克里夫蘭點圖納入兩個時間點。

若是想要比較更多時間點的數值,則可以用坡度圖/斜線圖(slope
chart),之後會再寫文章介紹。

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

實作篇

資料長相

我們先來匯入資料。從底下的表格可以看到,這份資料包含2018年與2022年九合一選舉中小黨的議會席次,它的形式為長表格(long table),如果是寬表格(wide table)的話,欄位會是政黨、2018年席次、2022年席次。

library(tidyverse)
df_viz_council <- read_csv("../data/ggplot-cleveland-dot-plot.csv")
df_viz_council
#> # A tibble: 26 x 3
#>    party         year     n
#>    <chr>        <dbl> <dbl>
#>  1 台灣民眾黨    2022    14
#>  2 台灣民眾黨    2018     0
#>  3 無黨團結聯盟  2022     7
#>  4 無黨團結聯盟  2018     5
#>  5 時代力量      2022     6
#>  6 時代力量      2018    16
#>  7 台灣團結聯盟  2022     3
#>  8 台灣團結聯盟  2018     5
#>  9 台灣基進      2022     2
#> 10 台灣基進      2018     0
#> # … with 16 more rows

從點點開始畫

接著,我們利用library(ggplot2)開始畫圖。這張圖想呈現的是台灣小黨在兩屆議會中席次的變化,政黨映射到 x 軸,席次映射到 y 軸,年份變化映射到透明度,所以我們在 ggplot() 裡面代表美學(aesthetics)的aes()中放 入政黨(party)作為 x 軸以及席次(n)作為 y 軸,另外也將代表顏色的 color 填入政黨、代表透明度的 alpha 填入時間。

就像前一篇介紹如何用R語言繪製長條圖的文章一樣,我們使用主題
theme_clean(),並在 element_text() 中設定中文字體(family),並以
legend.position = "none"隱藏圖例。此外,我們活用 coord_flip()
翻轉座標軸免得文字重疊,還有 scale_x_()/scale_y_() 的相關函數,讓座標軸的相關訊息更加清晰,最後,還有指定透明度的 scale_alpha_continuous(),免得透明度太低什麼都看不到。

df_viz_council %>% 
  ggplot(aes(x = party, y = n, color = party, alpha = year)) + 
  geom_point(size = 3) +
  coord_flip() +
  scale_y_continuous(limits = c(0,16), breaks = seq(0,16,2), name = "席次") +
  scale_x_discrete(name = "政黨") +
  scale_alpha_continuous(range = c(0.6,1)) +
  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_fill_manual() 指定政黨顏色,不過,可以發現在色票最後,有2個小黨都是灰色,因為他們2022年沒有拿下任何席次,在這裡繪圖者做出的主觀判斷是,今年有拿下席次的政黨才是重點,所以2022年沒有席次的政黨一律以灰色呈現。

color_party % 
  ggplot(aes(x = party, y = n, color = party, alpha = year)) + 
  geom_point(size = 3) +
  coord_flip() +
  scale_y_continuous(limits = c(0,16), breaks = seq(0,16,2), name = "席次") +
  scale_x_discrete(name = "政黨") +
  scale_alpha_continuous(range = c(0.6,1)) +
  scale_color_manual(values = color_party) +
  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")

排序的艱難

若是想要依照議會席次將長條排序,同樣會利用 as_factor()
將政黨轉換成因子、fct_reorder() 把政黨按照席次排序。不過,這份資料的難點在於,同一個政黨一次會有2年的資料,如果說我們想依照2022年的席次排序,直接使用fct_reorder(party, n),R其實會有點不知所措,因為同時有2018的數字,導致結果不如我們的預期。

要怎麼解決呢?這時候我們可以活用 left_join(),這是 SQL 常見的資料串接(join)語法,library(dplyr) 裡面引入了相關概念。實作上其實很容易,我們另外創建一個只有2022年的資料表,並在其中加上順序欄位,再把它串回去原先的資料表,就能夠解決問題了。

df_viz_council_order <- df_viz_council %>% filter(year == 2022) %>% 
  arrange(n) %>% mutate(order = row_number()) %>% select(party, order)
df_viz_council_order
#> # A tibble: 13 x 2
#>    party          order
#>    <chr>          <int>
#>  1 民國黨             1
#>  2 中華民族致公黨     2
#>  3 正神名黨           3
#>  4 社會民主黨         4
#>  5 勞動黨             5
#>  6 新黨               6
#>  7 綠黨               7
#>  8 台灣基進           8
#>  9 親民黨             9
#> 10 台灣團結聯盟      10
#> 11 時代力量          11
#> 12 無黨團結聯盟      12
#> 13 台灣民眾黨        13

如大家在程式碼區塊中所見,我們先用 filter() 留下2022年的資料,接著以 arrange() 把資料由小到大排列(因為有 coord_flip(),然後以 row_number() 加上順序欄位,最後加上 filter(),最後再用 left_join()
串接資料。

df_viz_council %>% 
  left_join(df_viz_council_order, by = "party") %>%
  mutate(party = as_factor(party)) %>%
  mutate(party = fct_reorder(party, order)) %>%
  ggplot(aes(x = party, y = n, color = party, alpha = year)) + 
  geom_point(size = 3) +
  coord_flip() +
  scale_y_continuous(limits = c(0,16), breaks = seq(0,16,2), name = "席次") +
  scale_x_discrete(name = "政黨") +
  scale_alpha_continuous(range = c(0.6,1)) +
  scale_color_manual(values = color_party) +
  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_line() 連接2個點點,就完成了。這個步驟比較彈性,也可以選擇不要加。比較值得一提的是,如果沒有在 geom_line() 當中指定顏色,則它會比照 ggplot() 裡面填入的參數,以政黨將線條上色。不過,將線條上色反而會讓點點變得不清楚,所以我們手動指定灰色;另外 geom_line() 放在 geom_point()上,才不會讓線條蓋住點點。

df_viz_council %>% 
  left_join(df_viz_council_order, by = "party") %>%
  mutate(party = as_factor(party)) %>%
  mutate(party = fct_reorder(party, order)) %>%
  ggplot(aes(x = party, y = n, color = party, alpha = year)) + 
  geom_line(aes(group = party), color = "grey") +
  geom_point(size = 3) +
  coord_flip() +
  scale_y_continuous(limits = c(0,16), breaks = seq(0,16,2), name = "席次") +
  scale_x_discrete(name = "政黨") +
  scale_alpha_continuous(range = c(0.6,1)) +
  scale_color_manual(values = color_party) +
  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")

小結

在這篇文章中,我們嘗試畫了一張克里夫蘭點圖(Cleveland dot
plot),從基本的圖表畫起、加上顏色、長條排序、加上連接點點的線條等。希望你喜歡這篇文章,也能夠增添對於 ggplot2 的認識,並且學到東西。

5 Comments

  1. Avatar gateio token
    2023-05-20 at 23:31 // Reply

    I am a website designer. Recently, I am designing a website template about gate.io. The boss’s requirements are very strange, which makes me very difficult. I have consulted many websites, and later I discovered your blog, which is the style I hope to need. thank you very much. Would you allow me to use your blog style as a reference? thank you!

  2. 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.

  3. 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.

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

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

Leave a Reply