用 R 來玩玩地圖吧!起手式基礎篇
本文目錄(點擊可跳轉)
library(magrittr)
library(data.table)
1. 基本地圖資料:套件 sp
套件 sp 提供了空間資料的 classes 和 methods,讓空間資料可以呈現 points 、 lines 、 polygons 和 grids 的特性1-1. 空間資料的資料結構
點資料(SpatialPointsDataFrame)線資料與多邊形(SpatialLines and SpatialPolygons)
Grid 資料
2. 基本地圖資料處理:套件rgdal
Geospatial Data Abstraction Library(GDAL) 是由 Open Source Geospatial Fundation 所開發與維護的套件,而 rgdal 正是將其與 R 綁在一起的 R 語言套件。2-1. 載入資料
library(rgdal)
2-1-1. 載入經緯度資料文件並繪製地圖
TPE = read.table("data/TWN/tpedata.csv", header=TRUE, sep=",")
XCOOR = TPE$X
YCOOR = TPE$Y
#設定參考坐標系統
proj <- CRS("+proj=tmerc +lat_0=0 +lon_0=121 +k=0.9999 +x_0=250000 +y_0=0 +ellps=aust_SA +units=m +no_defs")
利用 sp::SpatialPointsDataFrame 函式,將資料轉為可做進一步空間分析的 SpatialPointsDataFrame 資料格式data.sp <- SpatialPointsDataFrame(coords=cbind(x=XCOOR,y=YCOOR), data=TPE, proj4string=proj)
# 看一下 data.sp 的 structure 長什麼樣子
data.sp %>% str
## Formal class 'SpatialPointsDataFrame' [package "sp"] with 5 slots
## ..@ data :'data.frame': 63 obs. of 3 variables:
## .. ..$ WEEK: int [1:63] 4 42 31 8 50 33 13 26 24 23 ...
## .. ..$ X : int [1:63] 305197 306471 305461 305946 304515 305057 305413 303234 307774 307974 ...
## .. ..$ Y : int [1:63] 2763877 2764321 2764674 2764799 2765329 2765560 2765936 2766068 2766142 2766376 ...
## ..@ coords.nrs : num(0)
## ..@ coords : num [1:63, 1:2] 305197 306471 305461 305946 304515 ...
## .. ..- attr(*, "dimnames")=List of 2
## .. .. ..$ : NULL
## .. .. ..$ : chr [1:2] "x" "y"
## ..@ bbox : num [1:2, 1:2] 296621 2763877 310825 2781440
## .. ..- attr(*, "dimnames")=List of 2
## .. .. ..$ : chr [1:2] "x" "y"
## .. .. ..$ : chr [1:2] "min" "max"
## ..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slot
## .. .. ..@ projargs: chr "+proj=tmerc +lat_0=0 +lon_0=121 +k=0.9999 +x_0=250000 +y_0=0 +ellps=aust_SA +units=m +no_defs"
畫成地圖來看看plot(data.sp,pch=1, col="blue")
2-1-2. 載入 shp 地圖資料
地圖資料包含有數個檔案,以範例圖檔 country 為例,圖檔資料夾包括 country.dbf 、 country.prj 、 country.shp 和 country.shx,因此在讀入檔案時要先指向資料路徑,在 layer 參數中寫入圖檔的名字World <- readOGR(dsn = "data/World", layer = "country")
## OGR data source with driver: ESRI Shapefile
## Source: "data/World", layer: "country"
## with 251 features
## It has 11 fields
WorldCity <- readOGR(dsn = "data/World", layer = "CITIES")
## OGR data source with driver: ESRI Shapefile
## Source: "data/World", layer: "CITIES"
## with 606 features
## It has 4 fields
# 看一下他們的 structure 長什麼樣子,因為結構龐大,所以看到第二層就好
World %>% str(max.level=2)
## Formal class 'SpatialPolygonsDataFrame' [package "sp"] with 5 slots
## ..@ data :'data.frame': 251 obs. of 11 variables:
## ..@ polygons :List of 251
## .. .. [list output truncated]
## ..@ plotOrder : int [1:251] 15 192 36 233 42 30 12 87 127 106 ...
## ..@ bbox : num [1:2, 1:2] -180 -90 180 83.6
## .. ..- attr(*, "dimnames")=List of 2
## ..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slot
WorldCity %>% str(max.level=2)
## Formal class 'SpatialPointsDataFrame' [package "sp"] with 5 slots
## ..@ data :'data.frame': 606 obs. of 4 variables:
## ..@ coords.nrs : num(0)
## ..@ coords : num [1:606, 1:2] 33.1 40.6 30.5 150.8 56.2 ...
## .. ..- attr(*, "dimnames")=List of 2
## ..@ bbox : num [1:2, 1:2] -165.3 -53.2 177.1 78.2
## .. ..- attr(*, "dimnames")=List of 2
## ..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slot
可以看到 World 的資料格式是 SpatialPolygonsDataFrame 而 WorldCity 則是 SpatialPointsDataFrame
#看一下 圖檔的資料概況
summary(World)
## Object of class SpatialPolygonsDataFrame
## Coordinates:
## min max
## x -180 180.0000
## y -90 83.6236
## Is projected: FALSE
## proj4string :
## [+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0]
## Data attributes:
## FIPS_CNTRY GMI_CNTRY CNTRY_NAME SOVEREIGN
## AA : 1 UMI : 6 Afghanistan : 1 United Kingdom: 16
## AC : 1 ISR : 3 Albania : 1 France : 13
## AF : 1 REU : 3 Algeria : 1 United States : 13
## AG : 1 NOR : 2 American Samoa: 1 Australia : 5
## AJ : 1 SJM : 2 Andorra : 1 New Zealand : 4
## AL : 1 ABW : 1 Angola : 1 Norway : 4
## (Other):245 (Other):234 (Other) :245 (Other) :196
## POP_CNTRY SQKM_CNTRY SQMI_CNTRY CURR_TYPE
## Min. : -99999 Min. : 2 Min. : 1 Dollar : 29
## 1st Qu.: 111033 1st Qu.: 717 1st Qu.: 277 Franc : 16
## Median : 3084641 Median : 61937 Median : 23914 CFA Franc: 13
## Mean : 22696879 Mean : 583961 Mean : 225467 Pound : 9
## 3rd Qu.: 11316975 3rd Qu.: 350769 3rd Qu.: 135432 EC Dollar: 8
## Max. :1281008318 Max. :16851940 Max. :6506534 (Other) :151
## NA's : 25
## CURR_CODE LANDLOCKED COLOR_MAP
## USD : 12 N:208 5 :35
## XOF : 11 Y: 43 4 :34
## FRF : 8 1 :33
## XCD : 8 8 :32
## AUD : 4 7 :31
## (Other):165 3 :30
## NA's : 43 (Other):56
summary(WorldCity)
## Object of class SpatialPointsDataFrame
## Coordinates:
## min max
## coords.x1 -165.27 177.1302
## coords.x2 -53.15 78.2000
## Is projected: FALSE
## proj4string :
## [+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0]
## Number of points: 606
## Data attributes:
## NAME COUNTRY POPULATION CAPITAL
## Hyderabad: 2 US : 49 Min. : -99 N:442
## San Jose : 2 Russia : 37 1st Qu.: 243744 Y:164
## Tripoli : 2 China : 36 Median : 710447
## Valencia : 2 Canada : 22 Mean : 1411999
## Abidjan : 1 India : 20 3rd Qu.: 1536250
## Abu Zaby : 1 Brazil : 19 Max. :23620000
## (Other) :596 (Other):423
2-2. 參考坐標系統(Coordinate Reference Systems)
在網站 Spatial Reference 中,可以查到各種座標系統的數學公式,且為了各種地圖平台使用方便,包括了諸多不同格式,非常方便。以台灣慣用的平面座標系統 TWD97-TM2 為例, sp 套件中處理座標系統的函式 CRS 必須使用 proj4 格式
點選之後可以看到 TWD97 座標系統的公式是:
+proj=tmerc +lat_0=0 +lon_0=121 +k=0.9999
+x_0=250000 +y_0=0 +ellps=GRS80 +units=m +no_defs
proj = tmerc
投影方法為橫麥卡托lon_0 = 121
以經度 121 為地圖中央線k = 0.9999
中央經線尺度放大倍數為0.9999 中央經線與圓柱面相切密合,所以尺度為 1 會造成圖面其它地方被放大。為讓尺度變化較為均勻,於是將投影座標乘以某一常數,讓中央經線的尺度略小於1,逐漸往兩側放大,到投影帶中間某一部分尺度約為 1,投影帶邊緣則略大於 1,這個乘常數,稱做「中央經線尺度」x_0 = 250000
橫座標平移量為250000 為避免讓中央經線西側座標出現負數,而將投影座標加一個常數,即為「橫座標平移量」。ellps = GRS80
橢球參數使用橢球體 GRS80 地球真實形狀其實不是正圓,也不是完美的橢圓,而是為赤道地帶略為膨脹、兩極地區略成扁平接近,因此參考橢球體即為最接近地球真實外型的規則幾何形狀。而 GRS80 是 1980 年國際大地測量學與地球物理學協會(International Union of Geodesy and Geophysics,簡稱為IUGG)公布之參考橢球體(GRS80),其橢球參數為: 長半徑 a = 6378137 公尺,扁率 f = 1/298.257222101
2-2-1. 查看並修改投影參數
我們可以修改參考坐標系統的內容。以下舉例:World@proj4string
## CRS arguments:
## +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
# Robinson projection
wrld.rob <- spTransform(World, CRS("+proj=robin"))
plot(wrld.rob)
# conical equidistant projection
wrld.eqdc <- spTransform(World, CRS("+proj=eqdc +lat_0=0 +lon_0=0 +lat_1=90 +lat_2=90 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs"))
plot(wrld.eqdc)
# Azimuthal Equidistant projection
wrld.aeqd <- spTransform(World, CRS("+proj=aeqd +lat_0=-90 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs"))
plot(wrld.aeqd)
3. 繪製面量圖:套件 GISTools
GISTools 提供許多協助繪製面量圖的工具library(GISTools)
3-1. 檢視地圖資料的屬性表格
# @data 可以看到地圖的屬性表格
World@data %>% head
## FIPS_CNTRY GMI_CNTRY CNTRY_NAME SOVEREIGN POP_CNTRY
## 0 AA ABW Aruba Netherlands 67074
## 1 AC ATG Antigua and Barbuda Antigua and Barbuda 65212
## 2 AF AFG Afghanistan Afghanistan 17250390
## 3 AG DZA Algeria Algeria 27459230
## 4 AJ AZE Azerbaijan Azerbaijan 5487866
## 5 AL ALB Albania Albania 3416945
## SQKM_CNTRY SQMI_CNTRY CURR_TYPE CURR_CODE LANDLOCKED COLOR_MAP
## 0 182.926 70.628 Florin AWG N 1
## 1 462.378 178.524 EC Dollar XCD N 2
## 2 641869.188 247825.703 Afghani AFA Y 3
## 3 2320972.000 896127.312 Dinar DZD N 3
## 4 85808.203 33130.551 Manat <NA> Y 4
## 5 28754.500 11102.110 Lek ALL N 6
# 現在要針對屬性資料中的人口密度 POP_CNTRY 欄位來做面量圖
wrld.pop <- World@data$POP_CNTRY
# 處理錯誤資料
wrld.pop[which(wrld.pop==0)]=NA
wrld.pop[which(wrld.pop==-99999)]=NA
wrld.pop %<>% na.omit
3-2. 資料處理
3-2-1. Select by Attributes
在台灣鄉鎮地圖上,選取出屬於 6 個直轄市且面積大於 90 平方公里的區這邊應用 magrittr 和 data.table 套件做資料選取, 當然也可以用更傳統的方式來做選取
# 讀取台灣地圖資料
TWN <- readOGR(dsn = "data/TWN", layer = "Townships", encoding="big5")
## OGR data source with driver: ESRI Shapefile
## Source: "data/TWN", layer: "Townships"
## with 368 features
## It has 5 fields
TWN@data %<>% as.data.table
plot(TWN)
# 選取 6 個直轄市各區,面積大於 90 平方公里的區
sel1 = TWN@data %>%
.[,.I[County=="臺北市" | County=="新北市" | County=="桃園市" | County=="臺中市" | County=="臺南市" | County=="高雄市"] ]
sel2 = TWN@data %>% .[,.I[Area>90]]
plot(TWN[intersect(sel1, sel2),] ,col="red", add=TRUE)
TWN[intersect(sel1, sel2),]@data
## UNI_ID Town County Area Code
## 1: 6 新店區 新北市 120 6
## 2: 9 三峽區 新北市 192 9
## 3: 19 石碇區 新北市 144 19
## 4: 20 坪林區 新北市 171 20
## 5: 25 雙溪區 新北市 146 25
## 6: 26 貢寮區 新北市 100 26
## 7: 29 烏來區 新北市 321 29
## 8: 99 東勢區 臺中市 117 99
## 9: 115 霧峰區 臺中市 98 115
## 10: 116 太平區 臺中市 121 116
## 11: 118 和平區 臺中市 1038 118
## 12: 199 安南區 臺南市 107 199
## 13: 204 白河區 臺南市 126 204
## 14: 207 東山區 臺南市 125 207
## 15: 216 七股區 臺南市 110 216
## 16: 225 楠西區 臺南市 110 225
## 17: 226 南化區 臺南市 172 226
## 18: 254 田寮區 高雄市 93 254
## 19: 262 旗山區 高雄市 95 262
## 20: 263 美濃區 高雄市 120 263
## 21: 264 六龜區 高雄市 194 264
## 22: 265 甲仙區 高雄市 124 265
## 23: 266 杉林區 高雄市 104 266
## 24: 267 內門區 高雄市 96 267
## 25: 268 茂林區 高雄市 194 268
## 26: 269 桃源區 高雄市 929 269
## 27: 270 那瑪夏區 高雄市 253 270
## UNI_ID Town County Area Code
3-2-2. Select by Locations
Floods 圖層是淹水潛勢區域地圖(Polygon), Schools 則是學校分布地圖(Points),想要找出位於淹水潛勢範圍中的學校。
Flood <- readOGR(dsn = "data/TWN", layer = "flood50", encoding="big5")
## OGR data source with driver: ESRI Shapefile
## Source: "data/TWN", layer: "flood50"
## with 5103 features
## It has 5 fields
Schools <- readOGR(dsn = "data/TWN", layer = "tpecity_school", encoding="big5")
## OGR data source with driver: ESRI Shapefile
## Source: "data/TWN", layer: "tpecity_school"
## with 198 features
## It has 3 fields
# 先確保兩圖層的投影參數一樣
Flood@proj4string = CRS("+proj=tmerc +lat_0=0 +lon_0=121 +k=0.9999 +x_0=250000 +y_0=0 +ellps=aust_SA +units=m +no_defs")
Schools@proj4string = CRS("+proj=tmerc +lat_0=0 +lon_0=121 +k=0.9999 +x_0=250000 +y_0=0 +ellps=aust_SA +units=m +no_defs")
# 投影參數相同才能進行選取
Schools.Flood = Schools[Flood,]
plot(Flood, col='cyan')
plot(Schools, col='yellow', pch=21, add=TRUE)
plot(Schools.Flood, col='red', pch=4, add=TRUE)
# 有哪些學校位處淹水區域?
(Schools.Flood@data)
## TEXTSTRING ID STUDENTS
## 1 士林國中 1 95
## 2 士林國小 2 93
## 8 明德國中 33 113
## 9 文林國小 39 213
## 13 富安國小 48 63
## 26 士東國小 300 52
## 42 大湖國小 681 92
## 52 百齡國小 730 163
## 61 新湖國小 837 24
## 67 民生國中 849 35
## 72 中山國中 863 200
## 73 五常國中 864 91
## 74 五常國小 865 55
## 75 吉林國小 882 91
## 78 劍潭國小 903 204
## 80 蓬萊國小 913 119
## 81 雙蓮國小 915 24
## 82 日新國小 916 201
## 83 民權國中 917 186
## 84 大同國小 918 25
## 87 太平國小 924 91
## 89 大橋國小 926 161
## 91 華江國小 979 99
## 98 龍山國小 1051 87
## 100 仁愛國小 1059 56
## 101 仁愛國中 1060 133
## 106 長春國小 1085 48
## 119 永吉國中 1199 101
## 123 木柵國中 1247 118
## 124 國光劇校 1248 68
## 131 興隆國小 1276 63
## 134 建安國小 1287 43
## 138 忠孝國小 1306 152
## 149 雙園國小 1362 110
## 174 興福國中 1575 85
## 175 溪口國小 1576 168
# 總共會影響多少學生?
sum(Schools.Flood@data$STUDENTS)
## [1] 3722
其中繪圖基本參數設定可參考此連結3-2-3. Join Attributes
有人口資料 .csv 文件檔案,想要根據地區代號「unicode」, 和台灣地圖屬性資料中的地區代號「UNI_ID」互相 join 在一起# 讀入人口資料 csv 檔案
Popn<-read.table("data/TWN/Townships_PopStat.csv", header=TRUE, sep=",") %>% data.table
head(Popn)
## unicode towncode Male Female Age25 Age34 Age44 Age54 Age64 Age.L65
## 1: 1 101 6878 5146 205 1345 2433 3381 2747 1913
## 2: 2 102 4735 3464 122 948 1681 2277 1831 1340
## 3: 3 103 5267 3881 162 1110 1899 2488 2063 1426
## 4: 4 104 3057 2576 171 797 1204 1384 1162 915
## 5: 5 105 4768 3618 148 1115 2087 2414 1736 886
## 6: 6 106 3760 2766 128 631 1382 1826 1383 1176
## Popn
## 1: 12024
## 2: 8199
## 3: 9148
## 4: 5633
## 5: 8386
## 6: 6526
head(TWN@data)
## UNI_ID Town County Area Code
## 1: 1 板橋區 新北市 23 1
## 2: 2 三重區 新北市 16 2
## 3: 3 中和區 新北市 20 3
## 4: 4 永和區 新北市 6 4
## 5: 5 新莊區 新北市 20 5
## 6: 6 新店區 新北市 120 6
# 要Join 之前,須確保要 join 的欄位名稱相同、型態也相同
# 這邊透過 data.frame 來修改欄位名稱與轉換型態
# 但 dplyr::left_join 只能使用 data frame 資料格式,所以還要轉回來
TWN@data %<>% .[,.(UNI_ID=UNI_ID %>% as.character,
Town, County, Area, Code)] %>% as.data.frame
Popn %<>% .[,.(UNI_ID=unicode %>% as.character,
towncode, Male, Female, Age25, Age44, Age54,
Age64, Age.L65, Popn)] %>% as.data.frame
TWN@data<- dplyr::left_join(TWN@data, Popn)
TWN@data %>% head
## UNI_ID Town County Area Code towncode Male Female Age25 Age44 Age54
## 1 1 板橋區 新北市 23 1 101 6878 5146 205 2433 3381
## 2 2 三重區 新北市 16 2 102 4735 3464 122 1681 2277
## 3 3 中和區 新北市 20 3 103 5267 3881 162 1899 2488
## 4 4 永和區 新北市 6 4 104 3057 2576 171 1204 1384
## 5 5 新莊區 新北市 20 5 105 4768 3618 148 2087 2414
## 6 6 新店區 新北市 120 6 106 3760 2766 128 1382 1826
## Age64 Age.L65 Popn
## 1 2747 1913 12024
## 2 1831 1340 8199
## 3 2063 1426 9148
## 4 1162 915 5633
## 5 1736 886 8386
## 6 1383 1176 6526
3-2-4. Spatial Join
計算各台灣鄉鎮(Polygon)中所擁有的景點(Point)數量Tour <- readOGR(dsn = "data/TWN", layer = "TWN_Tour")
## OGR data source with driver: ESRI Shapefile
## Source: "data/TWN", layer: "TWN_Tour"
## with 943 features
## It has 3 fields
Tour@proj4string = CRS("+proj=tmerc +lat_0=0 +lon_0=121 +k=0.9999 +x_0=250000 +y_0=0 +ellps=aust_SA +units=m +no_defs")
# selecting pts over polygon
Tour <- Tour[TWN, ]
# 計算總數
# 這邊使用的函式 aggregate 是由 sp 套件所提供,專門處理 spatil objects
# aggregate 函式中所填入的 FUN 可以是計算個數的 length,也可以填入 mean, sd, max, min....或者自訂 function。
Tour_agg<-aggregate(x = Tour["Mark_ID"], by = TWN, FUN = length)
summary(Tour_agg@data$Mark_ID)
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 1.000 1.000 2.000 4.293 4.000 59.000 153
TWN$Tour_Count <- Tour_agg$Mark_ID
head(TWN@data)
## UNI_ID Town County Area Code towncode Male Female Age25 Age44 Age54
## 1 1 板橋區 新北市 23 1 101 6878 5146 205 2433 3381
## 2 2 三重區 新北市 16 2 102 4735 3464 122 1681 2277
## 3 3 中和區 新北市 20 3 103 5267 3881 162 1899 2488
## 4 4 永和區 新北市 6 4 104 3057 2576 171 1204 1384
## 5 5 新莊區 新北市 20 5 105 4768 3618 148 2087 2414
## 6 6 新店區 新北市 120 6 106 3760 2766 128 1382 1826
## Age64 Age.L65 Popn Tour_Count
## 1 2747 1913 12024 2
## 2 1831 1340 8199 NA
## 3 2063 1426 9148 NA
## 4 1162 915 5633 NA
## 5 1736 886 8386 NA
## 6 1383 1176 6526 7
# 處理 NA 值
sel = TWN@data %>% data.table %>% .[,.I[is.na(Tour_Count)]]
TWN@data[sel,]$Tour_Count = 0
TWN@data %>% head
## UNI_ID Town County Area Code towncode Male Female Age25 Age44 Age54
## 1 1 板橋區 新北市 23 1 101 6878 5146 205 2433 3381
## 2 2 三重區 新北市 16 2 102 4735 3464 122 1681 2277
## 3 3 中和區 新北市 20 3 103 5267 3881 162 1899 2488
## 4 4 永和區 新北市 6 4 104 3057 2576 171 1204 1384
## 5 5 新莊區 新北市 20 5 105 4768 3618 148 2087 2414
## 6 6 新店區 新北市 120 6 106 3760 2766 128 1382 1826
## Age64 Age.L65 Popn Tour_Count
## 1 2747 1913 12024 2
## 2 1831 1340 8199 0
## 3 2063 1426 9148 0
## 4 1162 915 5633 0
## 5 1736 886 8386 0
## 6 1383 1176 6526 7
3-2-5. Export to SHP
使用 rgdal 套件提供的 writeOGR 函式將剛才做出來的圖層存成 ESRI Shapefile 格式。 dsn 是資料夾路徑, layer 則填入圖檔名稱writeOGR(TWN, dsn="data/output", layer="New_TWN", driver="ESRI Shapefile")
3-3. 繪製面量圖
3-3-1. 繪製面量圖與新增 legend、指北針與比例尺
新增 legend 之前要先設定 legend 要放在地圖的哪個位置, 執行locator(1)
指令後點選一下剛才匯出的地圖, 會在 Console 中顯示剛才滑鼠點選的圖片 x 、 y 位置, 使用這個位置就可以指定 legend 和 指北針的位置PDen = TWN$Popn/TWN$Area
# GISTools 提供的 auto.shading 函式可以將數值切成指定的分位數
shades <- auto.shading(PDen, cutter = quantileCuts,
n = 5, cols = brewer.pal(5, "RdBu"))
# 看一下 shades 的 structure ,可以看到其中已定義了 colors
shades %>% str
## List of 2
## $ breaks: Named num [1:4] 15 29 53 99
## ..- attr(*, "names")= chr [1:4] "20%" "40%" "60%" "80%"
## $ cols : chr [1:5] "#CA0020" "#F4A582" "#F7F7F7" "#92C5DE" ...
## - attr(*, "class")= chr "shading"
# 使用 choropleth 函式,讀入剛才設定的分層 shades 就可以畫出地圖
choropleth(TWN, PDen, main="Taiwan Population Density")
# locator(1)
choro.legend(343556.7,2590112, shades,cex=0.6,
fmt="%4.1f",title='Population Density (/km2)')
north.arrow(357257.5, 2644915, "N", len=8000, col="light gray")
map.scale(35288.13,2482789,100000,"100 km", 1, subdiv=1, tcol='black',scol='gray', sfcol='black')
3-3-2. 資料分級方法
在地圖學中,面量圖的資料如何分級是一門深奧的學問, 如果沒有根據資料的狀況做適當的分組, 在地圖著色之後可能會因為視覺效果影響,造成閱讀者判斷錯誤。基本上有以下幾種分組方法: Defined Interval, Equal Interval, Quantile, Standard Deviation 和 Natural Break (Jenks)
我們可以透過 classInt 套件所提供的工具 classIntervals 來取得這些分級的結果
3-3-2-1. Defined Interval
自定義分級。通常要對數據分佈有一定了解,或者想在視覺效果上做**操弄**才會使用自定義分級。library(classInt)
interval.DI = PDen %>% classIntervals(., 4, style="fixed",
fixedBreaks=c(0, 1000, 1500,2000 ,2557))
q <- cut(PDen, breaks= interval.DI$brks, include.lowest=T)
my_color = brewer.pal(9, "YlOrRd")[c(1,3,6,9)]
clr <- as.character(factor(q, labels = my_color))
plot(TWN, col = clr)
lenTEXT =
c(paste0("<", interval.DI$brks[2]), paste0(interval.DI$brks[2],"-",interval.DI$brks[3]),
paste0(interval.DI$brks[3],"-",interval.DI$brks[4]),
paste0(interval.DI$brks[4],"-",interval.DI$brks[5]))
legend(legend = paste0(lenTEXT), fill = my_color, title="Pop Density", "topright")
3-3-2-2. Equal Interval
等距分級。固定組數,每組跨距相同。 例如 0~100 的數據,指定其分為 4 級,則間隔為 25。interval.EI = PDen %>% classIntervals(., 4, style="equal")
q <- cut(PDen, breaks= interval.EI$brks, include.lowest=T)
clr <- as.character(factor(q, labels = my_color))
plot(TWN, col = clr)
interval.EI$brks %<>% round(digits = 1)
lenTEXT =
c(paste0("<", interval.EI$brks[2]), paste0(interval.EI$brks[2],"-",interval.EI$brks[3]),
paste0(interval.EI$brks[3],"-",interval.EI$brks[4]),
paste0(interval.EI$brks[4],"-",interval.EI$brks[5]))
legend(legend = paste0(lenTEXT), fill = my_color, title="Pop Density", "topright")
3-3-2-3. Quantile
Quantile 也就是前述 auto.shading 所提供的方法。 Quantile 分級適合用於線性分佈的資料。 但它不考慮數值大小,很可能將兩個大小相近的值分到不同的類別中,也可能一樣的數據被分在不同的級距中。 在 Quantile 分級方法中,每一級距中所含的資料數量是一樣的。這邊也一樣用 classIntervals 示範一次 Quantile 分級方法。
interval.QU = PDen %>% classIntervals(., 4, style="quantile")
q <- cut(PDen, breaks= interval.QU$brks, include.lowest=T)
clr <- as.character(factor(q, labels = my_color))
plot(TWN, col = clr)
interval.QU$brks %<>% round(digits = 1)
lenTEXT =
c(paste0("<", interval.QU$brks[2]), paste0(interval.QU$brks[2],"-",interval.QU$brks[3]),
paste0(interval.QU$brks[3],"-",interval.QU$brks[4]),
paste0(interval.QU$brks[4],"-",interval.QU$brks[5]))
legend(legend = paste0(lenTEXT), fill = my_color, title="Pop Density", "topright")
3-3-2-4. Standard Deviation
標準差分級。用**平均加減幾個標準差**來對資料做分級。interval.SD = PDen %>% classIntervals(., n=3, style="sd")
q <- cut(PDen, breaks= interval.SD$brks, include.lowest=T)
clr <- as.character(factor(q, labels = my_color[1:3]))
plot(TWN, col = clr)
interval.SD$brks %<>% round(digits = 1)
lenTEXT =
c(paste0("<", interval.SD$brks[2]), paste0(interval.SD$brks[2],"-",interval.SD$brks[3]),
paste0(interval.SD$brks[3],"-",interval.SD$brks[4])
)
legend(legend = paste0(lenTEXT), fill = my_color, title="Pop Density", "topright")
classIntervals 所提供的 standard deviation 分組方法,是設定好組數之後,系統自動計算要 mean±幾個sd 才能符合指定的組數。以上面設定的 3 組來說,各個斷點其實是 (
μ-5*σ
-μ
)、( μ
-μ+5*σ
)、( μ+5*σ
- μ+10*σ
) 這個以 5σ 為倍數的做法,只是為了達到指定組數為 3 的目的而設計, 因此如果要讓組距是傳統比較常看到的 μ±1*σ
或 μ±0.5*σ
可以用我寫的以下的函式 getSD:getSD = function(data, nsd=1 ){
u = mean(data)
SD = sd(data)
a = c(seq(from = 0, to = 20, by=nsd))
stdUP = vector()
for(i in 1:length(a)){
stdUP[i]= u +SD*a[i+1]
if( stdUP[i] > max(data) )
break;
}
if (stdUP[length(stdUP)]>max(data)){
stdUP = stdUP[1:(length(stdUP)-1)]
}else{
stdUP=stdUP
}
n.upper = 1:length(stdUP)
stdDW = vector()
for(i in 1:length(a)){
stdDW[i]= u -SD*a[i+1]
if( stdDW[i] < min(data) )
break;
}
if (stdDW[length(stdDW)] < min(data)){
stdDW = sort(stdDW[1:(length(stdDW)-1)])
}else{
stdDW=sort(stdDW)
}
n.lower = 1:length(stdDW)
std=c(stdDW, u, stdUP)
brks = paste0(c(-n.lower*nsd, 0,n.upper*nsd), "σ")
brks[brks=="0σ"]="μ"
return(list("std"=std, "brks"=brks))
}
在getSD 函式中,nsd參數 即指定 σ 以 nsd 為倍數和 μ 做加減, 預設 nsd=1。 但要注意的是,當 nsd 設定得太小,可能會造成組數過多,不利閱讀。 在地圖學中建議組數不要超過 7 組。std = getSD(PDen, nsd=1.5)$std
brks = getSD(PDen, nsd=1.5)$brks
q <- cut(PDen, breaks= std, include.lowest=T)
clr <- as.character(factor(q,
labels = brewer.pal(n = length(std)-1, "YlOrRd")))
plot(TWN, col = clr)
lenTEXT =
c(paste0(brks[1], " - ", brks[2]),
paste0(brks[2], " - ", brks[3]),
paste0(brks[3], " - ", brks[4]),
paste0(brks[4], " - ", brks[5]),
paste0(brks[5], " - ", brks[6]),
paste0(brks[6], " - ", brks[7]),
paste0(brks[7], " - ", brks[8]))
legend(legend = paste0(lenTEXT),
fill = brewer.pal(n = length(std)-1, "YlOrRd"),
title="Pop Density", "topright")
3-3-2-5. Natural Breaks (Jenks)
自然分類法。在 GIS 權威軟體 ArcGIS 中提供此分級選項,所以此分類法成為地圖學分級方法主流。interval.NB = PDen %>% classIntervals(., 4, style="jenks")
q <- cut(PDen, breaks= interval.NB$brks, include.lowest=T)
clr <- as.character(factor(q, labels = my_color))
plot(TWN, col = clr)
interval.NB$brks %<>% round(digits = 1)
lenTEXT =
c(paste0("<", interval.NB$brks[2]), paste0(interval.NB$brks[2],"-",interval.NB$brks[3]),
paste0(interval.NB$brks[3],"-",interval.NB$brks[4]),
paste0(interval.NB$brks[4],"-",interval.NB$brks[5]))
legend(legend = paste0(lenTEXT), fill = my_color, title="Pop Density", "topright")
3-3-3. 如何選擇適合的資料分級方法
如何判斷適合該資料的分級方法呢? 應該先判斷該資料的分佈狀態。hist(PDen)
boxplot(PDen)
可以從 histrogram 資料分佈中看到, PDen 屬於右偏態,也就是大多資料集中在左側 我們也可以從 boxplot 中看到,不管是下四分位數、中位數及上四分位數, 都非常集中在數值較低的區域。
再來看看前述介紹幾種分級的方法的分組狀況:
3-3-3-1. Equal Interval
plot(interval.EI, pal=my_color, main="Equal Interval", xlab="PDen", ylab="")
Equal Interval 適合用在例如叫為人所知的資料,例如成績 (1-100分)、台灣氣溫 (0-40℃)、百分比(0-100%)等等。 但如果資料的分佈呈現 M 型分佈時,會導致某一塊區間出現空白。
而以 PDen 台灣人口密度此一資料而言,Equal Interval 的分級方法對閱讀者而言,這些分組斷點是「不具有任何統計意義」的, 因此雖然可以很好理解,我就是把人口密度從最小 0.929 到最大 2557 平均切成四份,但是對於解釋分組依據時,並沒有任何統計上的意義。
3-3-3-2. Quantile
plot(interval.QU, pal=my_color, main="Quantile", xlab="PDen", ylab="")
Quantile 分級方法,是將數據排名第25%、50%、75%的數值設為分組斷點, 也就是說每組中所擁有的資料數量是一樣的。例如 PDen 資料:
interval.QU
## style: quantile
## [0.9,19.2) [19.2,38) [38,80.7) [80.7,2557]
## 92 91 93 92
可以看到每組中的資料數量都是 92 個。 但是,它卻忽略了各組內的差異,例如最後一組 (80.7-2557) ,明明人口密度 80 人/km2 和 2557 人/km2 是超級巨大的差異,可是 Quantile 卻把他們編到為同一組了。3-3-3-3. Standard Deviation
plot(interval.SD, pal=my_color, main="Standard Deviation", xlab="PDen", ylab="")
Standard Deviation 適合用在偏向常態分布的資料,其斷點具有很漂亮的統計意義。
3-3-3-4. Natural Breaking
plot(interval.NB, pal=my_color, main="Natural Breaks", xlab="PDen", ylab="")
Natural Break 所應用的就是最重要的分類原理:「讓組內差異最小」。
所謂的「差異」其實就是透過 variance 來表示,簡單的說, Natural Break 方法即是計算各種分類方法的 variance 總和,而 variance 總和最小的那種分類方法,就是最佳分類結果。 此方法計算量極大,詳細算法不在此贅述。
在示範資料 PDen 人口密度中,可以看到 Natural Break 可以呈現比較漂亮的結果,不會太集中於切割較低的數值,在高數值的呈現上,也不會讓組內的差異過大,是比較好的呈現方法。
本次分享到這邊,之後再繼續看看怎麼用 R 做更多樣化的地圖,還有更深入的空間分析方法。
hi,寫的很好.很實用,謝謝你的文章分享
回覆刪除我碩班畢業後,也好幾年沒用R了
現在R的功能真的很強大
最近也學了shiny,真的太好用了.
緊接著學如何用MarkDown
哇,本站第一位留言者,謝謝你的鼓勵喔!
刪除R海無涯,一起加油吧!
推個,最近剛碰地圖,摸索leaflet套件中...
回覆刪除分級方法講得很仔細,我是用自己設定的切法,以排名25、50、75、83、91去切,目的是希望針對前25%,也就是PR75以上的分區去投入資源,不知大大有何看法?