用R在地图上绘制网络图的三种方法

作者:严涛浙江大学作物遗传育种在读研究生(生物信息学方向)伪码农,R语言爱好者,爱开源

又到了亲爱的严涛老师时间:

诺奖文章里面的动图布局教程来了!!

R包ggseqlogo |置换序列分析图

ggplot2高效实用指南(可视化脚本,工具,套路,配色)

ComplexHeatmap |理解绘图逻辑布局热图

R语言可视化学习笔记之ggridges包

ggplot2学习笔记之图形排列

网络地理图产品与传统的网络图产品不同,引用当地理位置进行节点网络可视化时,需要将这些节点放置在地图上,然后绘制他们之间的连结。马库斯康拉德的帖子(https://datascience.blogwzb.eu/2018/05/31/three-ways-of-visualizing-a-graph-on-a-map/),非常赞。其中他的部分思路对于我们学习可视化很有帮助。

首先准备需要的R包,当需要一次性加载多个R包时,我们可以利用pacman,它整合了library包中的一些相关函数,利用pacman包中的p_load函数可以自动加载需要的R包,如果没有找到替代自动安装缺失的R包。这样我们就不用写很多行library命令了,从而使代码变得简单些。

library(pacman)
p_load(assertthat,tidyverse,ggraph,igraph,ggmap)

为了方便大家练习,仅挑出部分国家地理位置,如下:

country_coords_txt <- "
 1     3.00000  28.00000       Algeria
 2    54.00000  24.00000           UAE
 3   139.75309  35.68536         Japan
 4    45.00000  25.00000 'Saudi Arabia'
 5     9.00000  34.00000       Tunisia
 6     5.75000  52.50000   Netherlands
 7   103.80000   1.36667     Singapore
 8   124.10000  -8.36667         Korea
 9    -2.69531  54.75844            UK
10    34.91155  39.05901        Turkey
11  -113.64258  60.10867        Canada
12    77.00000  20.00000         India
13    25.00000  46.00000       Romania
14   135.00000 -25.00000     Australia
15    10.00000  62.00000        Norway"

nodes <- read.table(text = country_coords_txt, header = FALSE, quote = "'",sep = "\t",col.names = c("id","lon","lat","name"))

现在我们有了15个国家的地理坐标(LON和LAT)和国家名称,这些就是之后要在地图中展现的例程,下面我们需要在这些队列之间随机创建一些链接,方便之后将不同国家连起来。

# 生成随机数种子,保证结果的重复性
set.seed(42)  
min <- 1
max <- 4
n_categories <- 4

# edges:建立国家之间的随机连结
edges <- map_dfr(nodes$id, function(id){
  n <- floor(runif(1,min,max+1))
  to <- sample(1:max(nodes$id),n ,replace = FALSE)
  to <- to[to!=id]
  categories <- sample(1:n_categories,length(to), replace = TRUE)
  weight <- runif(length(to))
  data_frame(from=id, to=to, weight=weight, category=categories)
})
edges <- edges%>%mutate(category=as.factor(category))

上面我们已经创建好了例程(node)以及连接(edge),并且还生成了连结之间的类别(categories)和权重(weight),下面就进行可视化。

生成图形结构

下面创建一个预测边缘的数据框架。

(g <- graph_from_data_frame(edges, directed = FALSE, vertices = nodes))

此外,还需要再额外定义四列并入替换的初始位置。

edges_for_plot <- edges%>%
  inner_join(nodes%>%select(id, lon, lat),by=c("from"="id"))%>%
  rename(x=lon, y=lat)%>%
  inner_join(nodes%>%select(id,lon,lat),by=c("to"="id"))%>%
  rename(xend=lon,yend=lat)
assert_that(nrow(edges_for_plot)==nrow(edges))

# 给每个节点一个权重(weight)值,在之后的绘图中将反应在节点的大小上
nodes$weight <- degree(g)

再下面定义以下GGPLOT2主题用来绘制地图。

# 定义主题
maptheme <- theme(
  panel.grid = element_blank(),
  axis.text = element_blank(),
  axis.ticks = element_blank(),
  axis.title = element_blank(),
  legend.position = "bottom",
  panel.background = element_rect(fill="#596673"),
  plot.margin = unit(c(0,0,0.5,0),"cm")
)

# 指定`data=map_data("world")`保证每个节点共享同一世界地图中的坐标系
country_shape <- geom_polygon(aes(x=long, y=lat, group=group),
                              data=map_data("world"),
                              fill="#CECECE", color="#515151",size=0.1)

# coord_fixed函数可以改变xy轴的范围
mapcoords <- coord_fixed(xlim=c(-150,180), ylim=c(-55,80))

方法一:ggplot2

除了需要世界地图(country_shape)中国家边界外,我们还需要三个几何对象:

  1. geom_point:重新排序;

  2. geom_text:添加上游的标签名字;

  3. geom_curve:预测上游间的连线(edge)。

此外我们需要定义aesthetic来规定数据如何可视化地映射在地图上

  1. 对于股东(节点):将各个地理坐标映射到画板的x,y位置,以及路由器的大小取决于权重大小;

  2. 对于连线(边缘):使用edges_for_plot数据集,xendyend指定连线的起始和重点,按照category着色,根据weight来指定连线的粗细。

注意: geoms的顺序很重要,因为它定义了先放置该对象,先放置的将被后面的层叠覆盖。因此我们先行放置了连线(edges),然后重新布局了(nodes),最后重新布局的标签(labels)。

ggplot(nodes)+country_shape+
  geom_curve(aes(x=x,y=y,xend=xend,yend=yend,color=category,size=weight),
             data=edges_for_plot,curvature = 0.33,alpha=0.5)+
  scale_size_continuous(guide = FALSE,range = c(0.25,2))+  # scale for edge widths
  geom_point(aes(x=lon,y=lat,size=weight),  # draw nodes
             shape=21,fill="white",color="black",stroke=0.5)+
  scale_size_continuous(guide = FALSE, range = c(1,6))+ # scale for node size
  geom_text(aes(x=lon,y=lat,label=name), # draw text labels
            hjust=0,nudge_x = 1,nudge_y = 4,
            size=3,color="white",fontface="bold")+
  mapcoords+maptheme
image

方法二:ggplot2 + ggraph

ggplot2有一个名叫gggraph的扩展包(点我了解更多的ggplot2扩展包)专门为网络图的替换添加了geoms美学,它可以帮助我们对电容器和接线使用单独的标度(scales)。

nodes_pos <- nodes%>%
  select(lon,lat)%>%
  rename(x=lon,y=lat)
lay <- create_layout(g,"manual",node.position=nodes_pos)
assert_that(nrow(lay)==nrow(nodes))

# add node degree for scaling the node sizes
lay$weight <- degree(g)

# 使用gggraph包中的geom_edge_arc和geom_node_point函数进行绘图
ggraph(lay)+
  country_shape+
  geom_edge_arc(aes(color=category,edge_width=weight,circular=FALSE),
                data = edges_for_plot,curvature = 0.33,alpha=0.5)+
  scale_edge_width_continuous(range = c(0.5,2),guide=FALSE)+
  geom_node_point(aes(size=weight),shape=21,fill="white",color="black",stroke=0.5)+
  scale_size_continuous(range = c(1,6),guide = FALSE)+
  # 指定repel = TRUE来分发各个节点的标签
  geom_node_text(aes(label=name),repel = TRUE, size=3,color="white",fontface="bold")+
  mapcoords+maptheme
image

方法三:图形叠加

图形叠加需要一个透明背景,可通过下面的命令创建。

theme_transp_overlay <- theme(
  panel.background = element_rect(fill="transparent",color=NA),
  plot.background = element_rect(fill="transparent",color=NA)
)

在透明的背景上添加地图。

这里介绍一个技巧,我们可以将绘图代码放置在()中,运行一句命令即可将图形显示在你的RStudio中,而不需要再次运行p_base

(p_base <- ggplot() + country_shape + mapcoords + maptheme)
image

下面创建第一个需要覆盖在地图上的层叠-各节点之间的连线(边)。

(p_edges <- ggplot(edges_for_plot)+
  geom_curve(aes(x=x,y=y,xend=xend,yend=yend,color=category,size=weight), # draw edges as arcs
             curvature = 0.33,alpha=0.33)+
  scale_size_continuous(guide = FALSE, range = c(0.5, 2)) + # scale for edge widths
  mapcoords + maptheme + theme_transp_overlay +
  theme(legend.position = c(0.5, -0.1),
        legend.direction = "horizontal"))
image

然后是排除第二个需要重叠的附件-节点(节点)

(p_nodes <- ggplot(nodes) +
  geom_point(aes(x = lon, y = lat, size = weight),
             shape = 21, fill = "white", color = "black",   # draw nodes
             stroke = 0.5) +
  scale_size_continuous(guide = FALSE, range = c(1, 6)) +   # scale for node size
  geom_text(aes(x = lon, y = lat, label = name),            # draw text labels
            hjust = 0, nudge_x = 1, nudge_y = 4,
            size = 3, color = "white", fontface = "bold") +
  mapcoords + maptheme + theme_transp_overlay)
image

需要求最后用annotation_custom(ggplotGrob)p_edges状语从句:p_nodes添加到p_base上,三个图形就叠加在一起了。还之后需要手动多次调整p_edges状语从句:p_nodes在垂直方向上的位置。

p <- p_base+
  annotation_custom(ggplotGrob(p_edges), ymin = -74)+
  annotation_custom(ggplotGrob(p_nodes), ymin = -74)
print(p)
image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容