[R語言圖表]用ggplot畫克里夫蘭點圖 Cleveland dot plot
5Last Updated on 2023-10-31
你想畫克里夫蘭點圖(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
的認識,並且學到東西。
2023-05-20 at 23:31 //
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!
2023-05-25 at 18:28 //
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.
2023-06-01 at 13:04 //
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.
2023-06-04 at 12:20 //
The point of view of your article has taught me a lot, and I already know how to improve the paper, thank you.
2023-06-06 at 16:44 //
The point of view of your article has taught me a lot, and I already know how to improve the paper, thank you.