6

R 语言制作地区分布图及其动画

 2 years ago
source link: https://cosx.org/2022/07/choropleth-map-animation/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

3 制作地图

3.1 地区分布图

3.1.1 数据准备

首先加载 maps 包,方便后续读取其内置的美国共和党大选数据集 votes.repub 和美国州级地图数据集 state。

library(maps)

加载数据集 votes.repub,并查看数据类型和部分数据。

# 加载数据
data(votes.repub)
# 查看数据
str(votes.repub)
#  num [1:50, 1:31] NA NA NA NA 18.8 ...
#  - attr(*, "dimnames")=List of 2
#   ..$ : chr [1:50] "Alabama" "Alaska" "Arizona" "Arkansas" ...
#   ..$ : chr [1:31] "1856" "1860" "1864" "1868" ...

votes.repub 是一个 50 行 31 列的矩阵,行代表各州,列代表大选年份,进一步查看矩阵数据 votes.repub 的维度信息:

attributes(votes.repub)
# $dim
# [1] 50 31
#
# $dimnames
# $dimnames[[1]]
#  [1] "Alabama"        "Alaska"         "Arizona"        "Arkansas"
#  [5] "California"     "Colorado"       "Connecticut"    "Delaware"
#  [9] "Florida"        "Georgia"        "Hawaii"         "Idaho"
# [13] "Illinois"       "Indiana"        "Iowa"           "Kansas"
# [17] "Kentucky"       "Louisiana"      "Maine"          "Maryland"
# [21] "Massachusetts"  "Michigan"       "Minnesota"      "Mississippi"
# [25] "Missouri"       "Montana"        "Nebraska"       "Nevada"
# [29] "New Hampshire"  "New Jersey"     "New Mexico"     "New York"
# [33] "North Carolina" "North Dakota"   "Ohio"           "Oklahoma"
# [37] "Oregon"         "Pennsylvania"   "Rhode Island"   "South Carolina"
# [41] "South Dakota"   "Tennessee"      "Texas"          "Utah"
# [45] "Vermont"        "Virginia"       "Washington"     "West Virginia"
# [49] "Wisconsin"      "Wyoming"
#
# $dimnames[[2]]
#  [1] "1856" "1860" "1864" "1868" "1872" "1876" "1880" "1884" "1888" "1892"
# [11] "1896" "1900" "1904" "1908" "1912" "1916" "1920" "1924" "1928" "1932"
# [21] "1936" "1940" "1944" "1948" "1952" "1956" "1960" "1964" "1968" "1972"
# [31] "1976"

数据 votes.repub 是 matrix 矩阵类型,为方便后续数据操作,先转化为 data.frame 类型。

# 转为 data.frame 类型
votes_repub <- as.data.frame(votes.repub)

根据数据集 votes.repub 的行名生成两列是为了后续和地图数据集 state 做关联,最终将观测数据映射到地图上。state 列可与 ggplot2::map_data() 生成的地图数据合并,state_name 列可与 tigris 包获取的数据合并。

# 新增 state 和 state_name 列
votes_repub = within(votes_repub, {
  state_name = rownames(votes_repub)
  state = tolower(rownames(votes_repub)) # 州名转小写
})

需要什么格式的州名取决于所能获得的地图数据,一般需要对地图数据有所了解后才能知晓。

接下来是非常重要的一步数据操作:数据重塑。顾名思义,就是将数据集重新组织起来,是为了方便后续用 ggplot2 等包绘图。

# 重塑 votes_repub 数据集
us_votes_repub <- reshape(
  data = votes_repub,
  # 需要转行的列,列名构成的向量
  varying = as.character(seq(1856, 1976, by = 4)),
  times = as.character(seq(1856, 1976, by = 4)), # 构成新列 year 的列值
  v.names = "repub_prop", # 列转行 列值构成的新列,指定名称
  timevar = "year", # 列转行 列名构成的新列,指定名称
  idvar = "state", # 州名
  new.row.names = 1:(31 * 50),
  direction = "long"
)
# 查看重塑后的数据集 us_votes_repub
head(us_votes_repub)
#        state state_name year repub_prop
# 1    alabama    Alabama 1856         NA
# 2     alaska     Alaska 1856         NA
# 3    arizona    Arizona 1856         NA
# 4   arkansas   Arkansas 1856         NA
# 5 california California 1856      18.77
# 6   colorado   Colorado 1856         NA

不难看出,数据集 us_votes_repub 不是很完整,一些历史的大选,有些州没有记录。下面统计一下历届大选中数据缺失的情况,如图 3.1 所示。实际上,阿拉斯加州和夏威夷州是 1959 年正式加入美国的,之前只是美国的领地,没有资格参与美国的大选,其它各州的情况详见网站

us_votes_repub_na <- aggregate(
  data = us_votes_repub,
  state ~ year,
  subset = is.na(repub_prop),
  FUN = length
)
历届大选未收集到得票率数据的州有多少

图 3.1: 历届大选未收集到得票率数据的州有多少

接下来,先挑一个年份的大选数据,绘制一张静态的地区分布图,不妨以 1976 年的数据为例:

us_votes_repub_1976 <- subset(x = us_votes_repub, subset = year == "1976")

ggplot2 包 [3] 是基于数据框来作图的,地图也不例外,因此,需要先对 maps 包内置的 state 数据集做转化,ggplot2 包的 map_data() 函数可将 maps 包和 mapdata 包内置的 map 类型地图数据转化为 data.frame 类型地图数据。

library(ggplot2)
# 获取州级地图数据
usa_map <- map_data(map = "state")

map 类型的 state 数据集转化后,如下:

head(usa_map)
#     long   lat group order  region subregion
# 1 -87.46 30.39     1     1 alabama      <NA>
# 2 -87.48 30.37     1     2 alabama      <NA>
# 3 -87.53 30.37     1     3 alabama      <NA>
# 4 -87.53 30.33     1     4 alabama      <NA>
# 5 -87.57 30.33     1     5 alabama      <NA>
# 6 -87.59 30.33     1     6 alabama      <NA>

每一对经度 long 和纬度 lat 对应州的多边形边界上的一个点,按一定顺序 order 连接起来,就能围成一个多边形区域 region, 即州的行政区划边界,usa_map 是州级地图数据,不包含各郡的边界,故而 subregion 列都缺失。

接着,将观测数据以左关联的方式合并到地图数据上。注意将地图数据放左边,观测数据放右边,左侧地图数据最好不要含有缺失值,右侧观测数据并不完整。

# 合并观测数据和地图数据
usa_voting_df <- merge(
  x = usa_map,
  y = us_votes_repub_1976,
  by.x = "region", # region 首字母小写的各州名称
  by.y = "state",
  all.x = TRUE,
  sort = FALSE
)

地图上的每个区域都是有编号顺序的,不能乱,上面合并数据的操作打乱了地图数据的原有顺序,因此,需要恢复一下顺序,重新按照序号 order 列排序,否则地图是乱的,读者可试试看不排序的效果。

usa_voting_df <- usa_voting_df[order(usa_voting_df$order), ]

3.1.2 数据展示

经过上面一系列的数据操作,终于到画图的环节了。与普通的图形不同,绘制地图需要注意底图和坐标投影,ggplot2 提供 geom_map()coord_map() 两个函数实现相应配置。如图 3.2 所示,是一张根据 maps 包内置的北美州级地图数据生成的多边形区域填充图,灰黑色填充各州区域,浅灰色绘制各州边界。

ggplot(data = usa_voting_df, aes(long, lat, group = subregion)) +
  geom_map(
    map = usa_map, aes(map_id = region),
    color = "gray80", fill = "gray30", size = 0.3
  )
北美州级地图

图 3.2: 北美州级地图

接着,设置坐标投影。maps 包内置的 state 数据集,其经纬度坐标来自常见 WGS 84 参考系,即所谓的世界大地测量系统 World Geodetic System,于 1984 制定,全球定位系统 GPS 即采用此坐标参考系。

为了在二维地图上更加形象地体现地球的球型特点,利用 mapproj 包采用以北级为中心的方位角投影,平行线是同心圆,经线是等距径向线,详细描述见帮助文档?mapproj::mapproject,效果如图 3.3

ggplot(data = usa_voting_df, aes(long, lat, group = subregion)) +
  geom_map(
    map = usa_map, aes(map_id = region),
    color = "gray80", fill = "gray30", size = 0.3
  ) +
  coord_map(
    projection = "orthographic",
    orientation = c(latitude = 39, longitude = -98, rotation = 0)
  )
北美州级地图

图 3.3: 北美州级地图

接着,利用多边形图层 geom_polygon() 添加共和党得票率数据,填充颜色采用红蓝对比色。最后,设置极简主题去掉灰色背景,添加主标题、图例标题,设置字体样式。

1976年共和党在各州的得票率

图 3.4: 1976 年共和党在各州的得票率

显示代码

ggplot(data = usa_voting_df, aes(long, lat, group = subregion)) +
  geom_map(
    map = usa_map, aes(map_id = region),
    color = "gray80", fill = "gray30", size = 0.3
  ) +
  coord_map("orthographic", orientation = c(39, -98, 0)) +
  geom_polygon(aes(group = group, fill = repub_prop / 100), color = "black") +
  scale_fill_gradient2(
    low = "blue", mid = "white", high = "red",
    midpoint = 0.5, labels = scales::label_percent()
  ) +
  theme_minimal() +
  labs(
    title = "1976 年共和党在各州的得票率",
    x = "", y = "", fill = "得票率"
  ) +
  theme(
    plot.title = element_text(size = 16, face = "bold", color = "red3"),
    legend.position = "bottom"
  )

实际上,1976 年的大选,共和党在阿拉斯加和夏威夷是有得票数据的,因 maps 包内置的 state 地图数据集缺少这两个州,所以上图 3.4 未能展示,因此,要从美国人口调查局获取完整的地图数据。另外,图中还缺少北纬 / 南纬、东经 / 西经的方向指示。因此,接下来采用 sf 包加强的 ggplot2 来制作更加完善的地图。

Kyle Walker 开发的 tigris 包封装了地图数据的下载接口,下面获取 2019 年发布的比例尺为 1:20000000 的州级地图数据,并将阿拉斯加州、夏威夷州和波多黎各适当移动至连续的 48 州下方。

us_state_map <- tigris::states(cb = T, year = 2019, resolution = "20m", class = "sf")
# 移动离岸的地区
us_state_map <- tigris::shift_geometry(us_state_map, geoid_column = "GEOID", position = "below")

下载整理后保存到本地文件 us_state_map.rds,先查看下地图数据情况。

us_state_map
# Simple feature collection with 52 features and 9 fields
# Geometry type: MULTIPOLYGON
# Dimension:     XY
# Bounding box:  xmin: -3112000 ymin: -1698000 xmax: 2258000 ymax: 1559000
# Projected CRS: USA_Contiguous_Albers_Equal_Area_Conic
# First 10 features:
#    STATEFP  STATENS    AFFGEOID GEOID STUSPS         NAME LSAD     ALAND
# 1       53 01779804 0400000US53    53     WA   Washington   00 1.721e+11
# 2       46 01785534 0400000US46    46     SD South Dakota   00 1.963e+11
# 3       39 01085497 0400000US39    39     OH         Ohio   00 1.058e+11
# 4       01 01779775 0400000US01    01     AL      Alabama   00 1.312e+11
# 5       05 00068085 0400000US05    05     AR     Arkansas   00 1.348e+11
# 6       35 00897535 0400000US35    35     NM   New Mexico   00 3.142e+11
# 7       48 01779801 0400000US48    48     TX        Texas   00 6.767e+11
# 8       06 01779778 0400000US06    06     CA   California   00 4.037e+11
# 9       21 01779786 0400000US21    21     KY     Kentucky   00 1.023e+11
# 10      13 01705317 0400000US13    13     GA      Georgia   00 1.495e+11
#       AWATER                       geometry
# 1  1.255e+10 MULTIPOLYGON (((-2e+06 1535...
# 2  3.383e+09 MULTIPOLYGON (((-633753 865...
# 3  1.027e+10 MULTIPOLYGON (((1081987 544...
# 4  4.593e+09 MULTIPOLYGON (((708460 -598...
# 5  2.956e+09 MULTIPOLYGON (((122656 -111...
# 6  7.278e+08 MULTIPOLYGON (((-1226428 -5...
# 7  1.899e+10 MULTIPOLYGON (((-998044 -56...
# 8  2.031e+10 MULTIPOLYGON (((-2066285 -2...
# 9  2.373e+09 MULTIPOLYGON (((571925 -842...
# 10 4.420e+09 MULTIPOLYGON (((939223 -230...

地图数据的坐标参考系是 USA Contiguous Albers Equal Area Conic,感兴趣读者可以点开网页链接了解技术细节。一个州可能有多个独立的地区,比如夏威夷群岛,因此 Geometry 类型是 MULTIPOLYGON 而不是简单的 POLYGON。NAME 列表示各州或领地名称。图 3.5 是这份地图数据的效果图,图中阿拉斯加以一定比例缩小了,而夏威夷和波多黎各以一定比例放大了。

美国州级地图

图 3.5: 美国州级地图

地图数据有了后,类似之前的操作,将观测数据以左关联的方式合并到地图数据上。

usa_voting_map_1976 <- merge(
  x = us_state_map,
  y = us_votes_repub_1976,
  by.x = "NAME",
  by.y = "state_name",
  all.x = TRUE
)

在 sf 包的加持下,地理几何图层 geom_sf() 可以替代 geom_map(),坐标投影图层 coord_sf() 可以替代 coord_map()。整个绘图的过程极大地简化下来了,核心部分只有两行,如下:

ggplot() +
  geom_sf(data = usa_voting_map_1976, aes(fill = repub_prop / 100))

最后,添加颜色映射和主题等细节,如图 3.6 所示。

1976年共和党在各州的得票率

图 3.6: 1976 年共和党在各州的得票率

显示代码
ggplot() +
  geom_sf(data = usa_voting_map_1976, aes(fill = repub_prop / 100)) +
  scale_fill_gradient2(
    low = "blue", mid = "white", high = "red",
    midpoint = 0.5, labels = scales::label_percent()
  ) +
  theme_minimal() +
  labs(
    title = "1976 年共和党在各州的得票率",
    x = "", y = "", fill = "得票率"
  ) +
  theme(
    plot.title = element_text(size = 16, face = "bold", color = "red3"),
    legend.position = "bottom"
  )

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK