Election Results Visualization with ggplot2
用 R 語言畫幾種常見的選舉圖表!
你也可以在我的 medium publication 上讀到這篇文章。
我在 Taiwan R User Group 分享的簡報。
我在 Taiwan R User Group 分享的 Github 程式碼 。
1 Introduction
想像一下,如果你想知道國民黨總統候選人 2020年大選在各縣市的表現如何,會怎麼開始呢。最直覺的作法就是直接找出他們在各縣市的得票資料,而資料的長相就像底下的圖一樣,台北市幾票、台南市幾票。
## # A tibble: 5 × 6
## place number party name vote per
## <chr> <dbl> <chr> <chr> <dbl> <dbl>
## 1 臺北市 2 中國國民黨 推薦 韓國瑜張善政 685830 0.420
## 2 新北市 2 中國國民黨 推薦 韓國瑜張善政 959631 0.389
## 3 桃園市 2 中國國民黨 推薦 韓國瑜張善政 529113 0.404
## 4 臺中市 2 中國國民黨 推薦 韓國瑜張善政 646366 0.381
## 5 臺南市 2 中國國民黨 推薦 韓國瑜張善政 339702 0.291
因為上面是簡單的表格,雖然直接看得到得票數跟票數占比,但除了得票資料以外,還需要一張以縣市劃分界線的台灣地圖,讓你可以依照得票高低,把對應到或深或淺的顏色填入各個縣市當中。但是,以 dataframe 型態儲存的得票資料,要怎麼「畫」到地圖裡面呢?一個可行的方法是上網找台灣的 SVG ,匯入 Illustrator 後把顏色逐一填入。但如果不只要畫國民黨總統候選人的得票率,連不分區和區域立委選舉也都要一起,甚至,如果你想畫到村里等級的得票率,這就是程式派上用場的好時機了。
用 R 或者其他程式語言畫圖表有幾個優點,第一個優點是正確性,使用程式取代人為填色,可以大幅減少出錯的可能性,以不分區 19 個政黨各自的各縣市得票率地圖,就有 19 * 20 (6直轄 + 11縣 + 3市),一共有380個格子要填,稍有不慎就會看錯數字;第二個優點則是可重複利用性,資料有變化時不用重畫,直接套用前寫好的程式碼就好,應用情境像是跟著開票進度更新圖表;第三個優點則是速度,選好程式碼執行就好,不用再逐一顏色。其實,上面說的的正確、可重複利用、速度,綜合起來就是偷懶,因為自己在 Illustrator 裡面填色填到快瘋掉,所以才決定研究怎麼用 R 實現快速畫出選舉圖表。
文章開始,我會先介紹 R 當中地理資料的常見型態,接下來則會以台灣為例子,帶大家走過一遍取得台灣縣市/鄉鎮/村里圖資,並將資料引入 R 的流程。有了地理圖資以後,我會用 2020 台灣總統與立委選舉的的得票資料為範例,示範如合畫出以下的圖表類型:背景地圖/Background Map、統計地圖/Choropleth、點示地圖/Dot Distribution Map or Bubble Map、示意地圖/Cartogram、國會席次圖/Parliament Plot、六邊型網格圖/Hexmap or Tilegram。
這篇文章改寫自今年年初我在 Taiwan R User Group 的分享,因為篇幅原因,所以這篇文章不會包含常見的散點圖/Scatterplot、堆疊長條圖/Stacked Bar Plot 等,有興趣的人可以 yahoo/google “ggplot2 tutorial”。為了方便讀者參考,我把檔案上傳到 Github repo 上,raw data 和程式碼都在裡面,底下截了幾張分享時的簡報。
部分圖表類型的分類與程式範例參考 R Graph Gallery 以及其他資源,並著重呈現以台灣為範例的呈現。跟著這篇文章走過一遍後,畫出來的圖表可以直接輸出成 PNG/JPG,也可以另外輸出 SVG 到其他軟體後製。就讀者而言,這篇文章預設的讀者是對 R 語言有一定基礎、使用過 ggplot2
套件,並且想知道如何繪製更多樣圖表的人,相對來說則較不適合想聽選情分析,或是想產出設計師等級圖表的人。底下正文開始!
2 R and Spatial Data
在這個章節,我會分別介紹 R 當中的地理資料型態、地理資料的原始格式、如何取得台灣的地理資料並匯入 R 語言。
2.1 Spatial Data in R: sf
在 R 當中,常見的一種空間資料/地理資料(以下將混用空間與地理兩個詞彙)型態是 sf ,它結合了地理特徵與非地理特性,地理特徵像是點、線、多邊形、點與線的組合、線與線的組合,或者是這幾個元素混合在一起,非地理特徵像是地理區的名稱、編號,或者是其他紀錄了人口數量、行政區類型等的欄位。sf 的長相則跟常見的 dataframe 一樣,只是 dataframe 當中地理特性以 list columns 的形式儲存。在 R 裡面處理 sf 資料的套件就是 library(sf)
,官網中有一系列教學。
其實,除了 sf 以外,若上網查詢在 R 中應該如何處理地理資料,會看到很多人使用 library(sp)
,sp 是另一種常見的空間資料型態,只是與 sp 相比,sf 的資料型態易懂且好操作,譬如說可以直接用 st_join()
串接地理資料,而且生態系完整,像是 sf 物件也支援 tidyverse 底下 library(dplyr)
的操作,再加上清楚易懂的官方教學,讓 library(sf)
逐漸熱門,也因此逐漸取代 sp。若對 sp 與 sf 有興趣,可以參考 Should I learn sf or sp for spatial R programming? 還有Spatial R – Moving from SP to SF,另外,Geocompuations with R 裡面介紹 sf 的段落也值得一看,書中直接提到 “sf largely supersedes the sp ecosystem.”,因此本文才主要介紹 sf。
2.2 Spatial Data in R: raster
raster (網格資料) 的長相和 sf 有很大的不同,它由數個 cells/pixels (格子)所組成,每個格子都代表一個區域,而格子都有自己對應到的值,拿 raster 資料來繪圖時,格子當中值的大小可以表現出不同的顏色或是單色的深淺,藉此表示不同的地理特徵,像是地勢高低或是土地類型。若對 raster 資料有興趣,Intro to Raster Data in R 有大量的插圖幫助理解。
因為應用學門不同,資料型態也不同,所以這次教學不會使用 library(raster)
,將會以 library(sf)
為主,底下還是有稍微呈現一下在 R 中 raster 資料的長相,以及利用 plot()
畫出的圖。
# aut <- raster::getData("alt", country = "AUT", mask = TRUE)
# aut %>% write_rds(str_c(dirpath_data, "geo_raw/aut.rds"))
# aut <- read_rds(str_c(dirpath_data, "geo_raw/aut.rds"))
# aut
# plot(aut)
::include_graphics(str_c(dirpath_img, "raster.png")) knitr
上面介紹了 R 當中兩種常見的地理資料型態 - sf & raster,也就是 sf & 底下則要帶大家看,import 進 R 之前,這些地理資料的原始長相是什麼,底下會以 GeoJSON 和 Shapefile 為主。
2.3 Raw Spatial Data: GeoJSON
GeoJSON 是一種結合地理特徵與非地理特性的 JSON 檔案,你可以用 library(geojsonR)
讀取 GeoJSON,套件的官網有一篇教學告訴你要如何處理 GeoJSON 資料。將 GeoJSON 匯入到 R 裡面之後,他會以 nested lists 的型態儲存,我們可以透過函數再將它轉成 sf 物件。
另外,有一種資料型態叫做 TopoJSON,它是 GeoJSON 的延伸,因為能夠有效率的消除冗餘(redundancy),因此可以減少原始檔案的大小。可以參考topojson 的 github 網站。
2.4 Raw Spatial Data: Shapefile
Shapefile 是一種非常普及的開放空間資料格式,已經被大量使用多年,也有不少 GIS 軟體支援,上網搜尋台灣的地理圖資,有很多資料就是用 Shapefile 的形式儲存。若想了解更多,可以參考 Open and Plot Shapefiles in R。
2.5 Raw Spatial Data: kml
KML 是一種奠基於 xml 形式且用途廣泛的地理資料格式,舉例來說,可以將 KML 資料匯入到 Google Earth 裡面,另外,它可以儲存特徵/變數,也可以保有 raster 元素,可以參考 What is KML?。
3 Taiwan Geographical Data
上面談了 R 當中的空間資料型態,以及空間資料的原始長相,底下則是要帶大家一起取得台灣地理資料並匯入到 R 裡面。步驟其實很簡單:下載原始資料、簡化資料、輸入至 R 環境中。
3.1 Get Taiwan Data
想獲取台灣的地理資料有許多來源,也有不同的資料格式可以使用。以台灣的縣市為例子,政府資料開放平臺提供了直轄市與縣市界線的圖資、鄉鎮市區行政區域界線圖資、全國村里界線,下載便可以看到 Shapefile 檔案;此外,code for america 也有提供台灣的 GeoJSON 檔案;也有人準備了這份臺灣 GIS 資料來源整理。這邊會以政府資料開放平台的縣市界線圖資作為例子。
3.2 Simplify Data
以村里界線的原始資料來說,若在解壓縮後將 Shapefile 引入到 R 裡面,需要花上很多時間,而且因為檔案略大的緣故,操作也不太順利,因此在這裡參考Data Man 的資料視覺化筆記的介紹,你可以利用 mapshaper 工具簡化檔案。
以上面提過的縣市界線圖資來說,下載後你會看到一個 .zip 檔案,解壓縮後有 .dbf, .shx, .shp, .prj 等多個檔案,但是先別急著刪除 .zip ,因為 mapshaper 支援直接上傳 .zip 檔,上傳後你會看到網站如下圖。
點擊 import 後,可以看到以鄉鎮市為界線的台灣地理圖資,接著點擊右上角的 Simplify ,依照自己的需求選擇要勾選的項目後,就可以調整想要簡化的比例了。可以將簡化的比例拉到最右端的 0% ,再來往左端慢慢增加簡化的 % 數,在簡化檔案大小與表達清楚之間取得平衡。
簡化完畢後,點擊 Export ,可以看到好幾種檔案格式,除了前面介紹過的 Shapefile, GeoJSON, TopoJSON 以外,也有 csv 與 SVG 的選擇。底下的示範會以 Shapefile 和 GeoJSON 為主,所以可以先下載這兩種檔案。
如果你對新聞業實際在畫地理圖表前如何處理資料有興趣,可以參考前面提過的 Data Man 所寫的 天下雜誌2018台灣選舉地圖製作分享:技術的部分。接下來就可以開始畫圖表了!
4 Visualizing Election Results with R
底下先從載入需要的套件開始:
# Package names
<- c('tidyverse','sf','jsonlite','ggparliament','maps',
packages 'geojson','geojsonio','geojsonsf','cartogram')
# Install packages not yet installed
# installed_packages <- packages %in% rownames(installed.packages())
# if (any(installed_packages == FALSE)) {
# install.packages(packages[!installed_packages])
# }
# Packages loading
invisible(lapply(packages, library, character.only = TRUE))
# devtools::install_github("olihawkins/clhex")
library(clhex)
4.1 Background Map
第一個要介紹的圖表類型非常簡單,就是背景地圖,你可看到我先讀取了剛剛下載得到的 Shapefile 與 GeoJSON 檔案,再將它們分別讀入,讀檔後利用 geom_sf()
畫圖。不同類型的圖表大部分都以背景地圖為基底,加上其他的特徵例如人口數或者分區得票率。
### read simplified shp
= st_read(str_c(dirpath_data, "geo_raw/COUNTY_MOI_1081121/COUNTY_MOI_1081121.shp")) sf_county_shp_small
## Reading layer `COUNTY_MOI_1081121' from data source
## `/Users/macuser/Documents/GitHub/Dennis_R_Data_News/content/post/2020-03-15-election-results-visualization-with-ggplot2/data/geo_raw/COUNTY_MOI_1081121/COUNTY_MOI_1081121.shp'
## using driver `ESRI Shapefile'
## Simple feature collection with 22 features and 4 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: 116.7166 ymin: 20.69729 xmax: 123.4891 ymax: 26.383
## Geodetic CRS: TWD97
%>%
sf_county_shp_small ggplot() + geom_sf()
上面讀了剛剛下載下來的 Shapefile,畫圖結果沒什麼大問題,若你在意背景怎麼灰灰的,底下會告訴你應該怎麼調整。
### read geojson
<- read_json(str_c(dirpath_data, "geo_raw/tawian_county.json"))
county_json_raw <- county_json_raw %>% as.json() %>% geojson_sf() #%>%
sf_county_geojson_simplified # mutate(COUNTYNAME = iconv(COUNTYNAME, "UTF-8", "BIG-5") )
%>%
sf_county_geojson_simplified ggplot() + geom_sf()
### add longitude and latitude constraint
%>%
sf_county_geojson_simplified st_crop(xmin=119,xmax=123,ymin=20,ymax=26) %>%
ggplot() + geom_sf()
這部分則是讀了 GeoJSON,如大家所見,因為第二張地圖涵蓋了離島,所以台灣本島不很清楚,因此我利用了st_crop()
加上了經緯度的限制,將繪圖的範圍限制到台澎金馬,畫圖的效果就好很多了。後續的其他圖表會以這邊的 sf_county_geojson_simplified
為基礎。
4.2 Choropleth
第二個要介紹的圖表類型是統計地圖,它是一種依照變數數值替不同尺度的區域上色的地圖,舉例來說,台北市各行政區的人口密度圖就是一種統計地圖,這裡所關照的變數是人口密度,而區域的尺度則是行政區。
底下使用預先計算好的 2020 總統得票作為範例,繪製出以縣市為尺度的國民黨得票率統計地圖。將 raw data 輸入到 R 當中後,我篩選了國民黨候選人的號次,接著回到原先的台灣地理圖資,我取了每個縣市的 centroid 的座標,因為接下來畫圖的時候想標示實際得票率。這部分的操作可能比較難懂,但在後續繪圖的時候就能看出效果了。對 centroid 有興趣的人可以參考 官網的說明,簡單來說就是把各個縣市的中心抓出來,後面貼標籤的時候拿來用。
### read rds
<- "data/"
dirpath_data <- read_csv(str_c(dirpath_data, "2020/df_president_county_result_raw.csv")) %>%
df_president_county_result ::select(place, number, party, name, vote, per, vote_place)
dplyr
### filter KMT presidential vote
<-
df_president_county_result_blue %>%
df_president_county_result filter(number == 2)
### add centroid column for further use
=
sf_county_centroid %>%
sf_county_geojson_simplified st_centroid(of_largest_polygon = T)
=
df_coordination %>%
sf_county_centroid st_coordinates()
接著,我用dplyr::left_join()
把地理圖資和剛剛過濾得到的國民黨各縣市得票率串在一起,用來 join 的 key 是 c(“COUNTYNAME” = “place”),也就是縣市的名稱。處理好資料後,就進入畫圖的階段。先看第一張圖,顏色越深代表國民黨總統候選人的得票率越高,以花東兩個縣市最高,但從圖中我們沒辦法看出實際的得票率數值,因此,我在第二張圖以 geom_text()
補上文字標籤,這些文字標籤的位置就是剛剛抓的縣市 centroid,也就是說,我把得票數字放在每個縣市的 centroid 了。此外,我也用 scale_fill_gradientn()
微調,讓顏色更貼近國民黨的官方色。到這邊基礎的統計地圖就大致完成了!
### join vote & Taiwan sf data
<-
sf_county_president %>%
sf_county_geojson_simplified left_join(df_president_county_result_blue, by = c("COUNTYNAME" = "place")) %>%
mutate(rn = row_number()) %>%
mutate(x_centroid = df_coordination[,"X"],
y_centroid = df_coordination[,"Y"])
### raw plot
%>%
sf_county_president st_crop(xmin=119,xmax=123,ymin=20,ymax=26) %>%
ggplot()+
geom_sf(size = 0.2, aes(fill = per))+
scale_fill_gradient(low = "#56B1F7", high = "#132B43")
### add text label
%>%
sf_county_president st_crop(xmin=119,xmax=123,ymin=20,ymax=26) %>%
ggplot()+
geom_sf(size = 0.2, aes(fill = per)) +
geom_text(
aes(
x=x_centroid,y=y_centroid,
label = round(per, 2)
size=3
), +
) scale_fill_gradientn(colors = c("white","#CCCCEE","#9999DD","#4141EF")) +
coord_sf(datum = NA)+
::theme_map() ggthemes
如果你覺得奇怪,怎麼看起來有點醜沒有很好看,你可以把利用 ggsave()
將圖表輸出成 SVG ,再丟到別的地方編輯,因為是向量檔,可以盡量縮放。
<-
plot_kmt_president %>%
sf_county_president st_crop(xmin=119,xmax=123,ymin=20,ymax=26) %>%
ggplot()+
geom_sf(size = 0.2, aes(fill = per)) +
geom_text(
aes(
x=x_centroid,y=y_centroid,
label = round(per, 2)
size=3
), +
) scale_fill_gradientn(colors = c("white","#CCCCEE","#9999DD","#4141EF")) +
coord_sf(datum = NA)+
::theme_map()
ggthemes
### use ggsave to save plots
# ggplot2::ggsave(filename = str_c(dirpath_data, "plot_kmt_president.svg"),plot = plot_kmt_president)
4.3 Dot Distribution Map/Bubble Map
第三個要介紹的圖表類型是點示地圖,如果說統計地圖是將不同變數以特定尺度為單位化約為區域的值,那麼點示地圖就是利用點來表示變數。以剛剛的例子來說,台北市各行政區人口密度圖屬於統計地圖(每個行政區填上代表人口密度的顏色),而台北市各行政區人口分布就是點示地圖(每個行政區上散落著許多點點,每個點都代表五千人)。
底下我會呈現台灣各縣市的城鎮分佈,因為手上沒有城鎮人口的資料,所以我借用了 library(maps)
的 world.cities
資料,裡面包含的城市的名稱、人口、經度緯度。我總共畫了兩張圖,兩張圖都是以前面仔入的台灣縣市地圖為背景地圖,再利用 geom_point()
加上城鎮分佈,第一張圖是單純呈現資料當中的城鎮分佈,第二張圖則是考慮到人口多寡,利用 size 參數調整每個點點的大小。
### get data
<- world.cities %>%
df_city_taiwan filter(country.etc=="Taiwan") %>% as_tibble()
%>% head(3) df_city_taiwan
## # A tibble: 3 × 6
## name country.etc pop lat long capital
## <chr> <chr> <int> <dbl> <dbl> <int>
## 1 Changhwa Taiwan 227178 24.1 121. 0
## 2 Chaochou Taiwan 58839 22.6 121. 0
## 3 Chengkung Taiwan 18647 24.2 121. 0
### plotting: each point equals a city
%>%
sf_county_president st_crop(xmin=119,xmax=123,ymin=20,ymax=26) %>%
ggplot() +
geom_sf(size = 0.2) +
geom_point(data=df_city_taiwan, aes(x=long, y=lat)) +
theme_void()
### plotting: city population size matters
%>%
sf_county_president st_crop(xmin=119,xmax=123,ymin=20,ymax=26) %>%
ggplot() +
geom_sf(size = 0.2) +
geom_point(data=df_city_taiwan, aes(x=long, y=lat, size = pop)) +
theme_void()
4.4 Cartogram
第四個要介紹的圖表類型是示意地圖,它的長相和統計地圖類似,差別之處在於繪圖者扭曲了示意地圖當中的區域,用以代表該區域的性質,譬如底下我會扭曲台灣的各縣市,將面積依照該地區的實際投票數大小縮放。
這邊我們使用了library(cartogram)
繪製示意地圖,理想上應該可以利用函數處理 sf 物件,但我的原始資料需要做一些轉換,可參考 Error with Plotting Cartogram ,使用 st_transform()
調整 CRS 後再丟到 cartogram_cont()
,就能夠跟上面一樣利用 geom_sf()
畫出好看的示意地圖了。
### create sp data
<- sf_county_president %>%
sf_county_president_carto st_crop(xmin=119,xmax=123,ymin=20,ymax=26) %>%
st_transform(crs = 3828)
### use cartogram package
library(cartogram)
<- cartogram_cont(sf_county_president_carto, weight = "vote_place", itermax=5)
president_cartogram
### plot
%>%
president_cartogram ggplot() +
geom_sf(aes(fill = per) , size=0, alpha=0.9) +
scale_fill_gradientn(colors = c("white","#CCCCEE","#9999DD","#4141EF")) +
coord_sf() +
theme_void()
4.5 Parliament Plot
第五個要介紹的圖表類型沒有專有名詞,我自己稱為議會席次圖,如果你不確定我在說什麼,可以直接參考我要使用的套件 library(ggparliament)
的官網,最常見的呈現形式就是將議會/立法院的各黨派人數分布以馬蹄形呈現。
這個套件的 input 簡單易懂,底下我使用了 2020 台灣立法委員的選舉結果資料,這個 dataframe 的欄位包含黨派、黨派縮寫、席次,它的原理也很簡單,以馬蹄形的議會圖為例,套件當中的 parliament_data()
函數會利用提供的黨派與席次,計算出繪製馬蹄形所需的點座標與角度。使用glimpse()
看處理後的資料,就可以發現新的 df_parliament_final
已經從原本 aggregated data 推回 individual data 了。
我選擇半圓形(semicircle)作為繪製的席次分佈形狀,除此之外還有馬蹄形、圓形等多元的選擇,也有幫忙你計算各政黨席次占比的橫條圖、強調過半政黨的函數,作者實在用心良苦,幸好沒有錯放你的手。
library(ggparliament)
### get data
<- read_rds(str_c(dirpath_data, "2020/df_parliament_summary_raw.rds"))
df_parliament_summary_raw # df_parliament_summary_raw <- read_csv(str_c(dirpath_data, "2020/df_parliament_summary_raw.csv"))
### take a look
%>% head(5) df_parliament_summary_raw
## year country house party_long party_short seats
## 1 2020 Taiwan Legislative Yuan Democratic Progressive Party DPP 61
## 2 2020 Taiwan Legislative Yuan Kuomintaing KMT 38
## 3 2020 Taiwan Legislative Yuan Taiwan Statebuilding Party TSP 1
## 4 2020 Taiwan Legislative Yuan New Power Party NPP 3
## 5 2020 Taiwan Legislative Yuan Taiwan People Party TPP 5
## government colour
## 1 1 #1b9431
## 2 0 #000095
## 3 0 #a73f24
## 4 0 #fbbe01
## 5 0 #28c8c8
### use ggparliament::parliament_data to create cooridinate system & theta
<- parliament_data(election_data = df_parliament_summary_raw,
df_parliament_final type = "semicircle",
parl_rows = 6,
party_seats = df_parliament_summary_raw$seats)
%>% head(5) %>%
df_parliament_final ::select(party_long,party_short,seats,government,colour,x,y,row,theta) dplyr
## party_long party_short seats government colour x
## 1 Democratic Progressive Party DPP 61 1 #1b9431 -2.0
## 1.1 Democratic Progressive Party DPP 61 1 #1b9431 -1.8
## 1.2 Democratic Progressive Party DPP 61 1 #1b9431 -1.6
## 1.3 Democratic Progressive Party DPP 61 1 #1b9431 -1.4
## 1.4 Democratic Progressive Party DPP 61 1 #1b9431 -1.2
## y row theta
## 1 2.449294e-16 6 3.141593
## 1.1 2.204364e-16 5 3.141593
## 1.2 1.959435e-16 4 3.141593
## 1.3 1.714506e-16 3 3.141593
## 1.4 1.469576e-16 2 3.141593
### plot: simple
%>%
df_parliament_final ggplot(aes(x, y, colour = party_short)) +
geom_parliament_seats(size = 5)
### plot: complete
%>%
df_parliament_final ggplot(aes(x, y, colour = party_short)) +
geom_parliament_seats(size = 5) +
geom_highlight_government(government == 1) +
geom_parliament_bar(colour = colour, party = party_long) +
draw_majoritythreshold(n = 57, label = TRUE, type = 'semicircle')+
theme_ggparliament() +
#other aesthetics
labs(colour = NULL,
title = "Taiwan Legislative Yuan",
subtitle = "Party that controls the Legislative Yuan highlighted.") +
scale_colour_manual(values = unique(df_parliament_final$colour),
limits = unique(df_parliament_final$party_short))
#其他可以選的類型
#type = "horseshoe", "semicircle", "circle", "classroom", "opposing_benches"
4.6 Hexmap/Tilegram
第六個要介紹的圖表類型是六角型地圖(hexmap),或是稱為 tiled cartogram,是示意地圖的延伸。顧名思義,tiled cartogram 由許多 tiles(瓦片)組成,每個 tiles 都代表一個均等的區域,可以是人口數上的均等,也可以是其他特定變數,譬如美國選舉人團的均等。
使用 hexmap 的圖表好處在於,不會因為某些地理特性而誤解區域的影響力,以選區來說,雖然台北市的面積小於花蓮,但台北的區域立委席次較多,若直接用區域面積繪製立委得票,無法呈現台北市立委的影響力,相對而言使用以選區為單位的 hexmap,可以直接看到各縣市在選舉中產生多少位立委。
若你覺得聽起來抽象,不妨參考一個用 JavaScript 寫的 tiled maps 生成器,底下我會簡單說明繪製這種圖表的方法,並以台灣 2020 分區立委選舉的選區實作。
目前我看到繪製 tilegrams 的方法主要有兩種,第一種是直接繪製出特定格式的 JSON,第二種是用數學方法將地理資料切分出多個 tiles。就前者而言,tilegramsR 套件上就有整理前人的多樣作品,而 R Graph Gallery 上也有教學,它會請你先下載某個 GeoJSON,載入 R 裡面就已經是 hexmap 的形式了,但遺憾的是這上面都沒有台灣的資料,所以我等一下會用其他方法畫一遍。就後者而言,你可以參考 Stackoverflow 上的 how to draw tilegram/hexagon map in R,有位熱心人士提供自己寫的測試套件,我有測了一下確定可行,只是把台灣切成數的 tiles 的效果沒有那麼好而已。
底下我使用的套件是 library(clhex)
,這個套件可以產生 hexJSON,以台灣為例,我先產生一個包含 2020 區域立委選舉選區的 dataframe,接著利用 create_hexjson()
產生 hexJSON,再將它丟到 hexjson editor 編輯。
### create hexJSON based on constituency
library(clhex)
<- read_rds(str_c(dirpath_data, "2020/df_legis_constituency.rds"))
df_legis_constituency <- create_hexjson(df_legis_constituency)
hex_test
### 我已經create過了
# create_and_save_hexjson(df_legis_constituency, str_c(dirpath_data, "2020/output.hexjson"))
在 hexjson editor 上的編輯過程是純手動拖拉,請參考下圖。選擇上傳後有問你一些設定的問題,可以參考我的作法填寫,編輯模式開啟後可以將選區依照地理位置移動,移動方法是選取 hex 然後點選要更換位置的另一個 hex,各點擊一次就可以交換位置了,我用了一個奇怪的形狀當範例,畫好後就可以選擇下載 GeoJSON 了。
按照上面的步驟,將檔案載入 R 後丟到 geom_sf()
裡面就可以畫圖囉!
### download data from https://olihawkins.com/project/hexjson-editor/
### import data
<- geojson_read(str_c(dirpath_data, "2020/hexmap.geojson"))
json_2020 <- json_2020 %>% as.json() %>% geojson_sf()
sf_2020
### plot
%>%
sf_2020 ggplot() +
geom_sf(size = 0, aes(fill = value)) +
scale_fill_manual(values=c("#4141EF", "#33AE33", "#808080")) +
coord_sf(datum = NA) +
::theme_map() +
ggthemestheme(legend.position = "null")
你可能會想問兩個問題,第一個是為什麼要那麼麻煩產出 hexJSON ,不乾脆用 Illustrator 拉就好,第二個是為什麼 ggplot2
的結果有點歪。第二個問題我自己沒有解答,目前作法也是丟到 Illustrator 調整比例;針對第一題,我的回答跟前面的一樣,如果只是一次性的任務那用畫的很快,但如果你要畫台灣歷次分區立委選舉的結果,倘若你有選區名稱跟結果,就可以直接跟這個 hexJSON 串不用自己慢慢填色。
5 Conclusion
5.1 Some thoughts and recommended books
每次看到紐約時報、華盛頓郵報、彭博社在美國選舉期間多樣的數據圖表,或簡潔或繁複,都會驚嘆於不同敘事方式背後編輯們的深刻設想。2018年台灣五都與地方縣市首長選舉時,天下雜誌、關鍵評論網、中央社等媒體也都在視覺化呈現選舉結果上下了很多苦工,參加 HH Taipei 的分享後,我在心裡想,自己也要做一樣的事情,2020 選舉時因緣際會畫了一些選舉圖表,在事前規劃的過程中累積了上面的探索跟程式碼,也因此產出了這篇文章。
除了長條圖、散點圖以外,很多類型的選舉圖表始終離不開地圖,因此如果你想要深入這個領域,可以參考這兩本書,第一本是 Geocomputation with R,教你怎麼靈活的操作地理資料,並且有跨領域的應用,個人非常推薦這本書;第二本則是臺北大學林茂廷老師撰寫的授課用書,因為是用中文寫的所以比較親民。
5.2 A short review
在這篇文章當中,我利用 R 語言繪製了六種類型的選舉圖表,包含 Background Map、Choropleth、Dot Distribution Map、Cartogram、Parliament Plots、Hexmap/Tilegram,希望你會喜歡!若你看到有趣的圖表,也歡迎和我說。特別感謝照真老師的課堂,還有 Tzu-Lun 跟我一起畫圖表,也謝謝 Summit 邀請我分享才催生這篇文。