挖掘社会关系网络,助你洞若观火。

需求

最近有个学生问我,如何绘制交互式社会网络图(Interactive Social Network Graph)?

之所以一定要交互式,是因为他的应用场景,是演示给客户。

他解释说,如果客户有选项,可以根据需要来缩放图形、聚焦类别,甚至是可以拖拽图形元素,以不同视角来查看,那展示效果显然会更好。

我对学生这次把握客户需求的能力,还是比较赞赏的。确实,数据可视化的目的就是为了与人沟通。而人是需要控制感的。

在数据科学里,这个需求,属于网络可视化(Network Visualization)范畴。应用上,除了描绘社会网络外,这种可视化的对象还可以包括引文网络、贸易网络、信息(或疾病)传播网络等。甚至,网络可视化操作还能与时序数据结合,例如 Maximilian Noichl 绘制的这张古代哲学家的动态关系网络图。

网络可视化的工具,是非常多的。

然而,一旦涉及了交互式,选项就大幅减少了。特别是,他还要求免费易学格式开放、演示环境易于部署……

这条件,够苛刻了吧?

还好,我还真就帮他找到了。

虽然易学,可是由于他是文科生,我觉得还是给他写个教程,比较妥帖。

教程写完之后,我觉得你可能也会用得上,所以一并分享给你。

演示数据,我自然不能用他的。否则有泄露商业机密的风险。这里我采用的,是斯坦福大学开放课程《数据库基础》中的一个简单数据样例。我在国际班讲的英文数据库课程,一直用它作为练习数据。

这个演示数据的特点,就是简单。

有多简单?往下看。

数据

该数据刻画的,是某高中里4个年级部分学生之间的社交关系。

我们一共需要用到3张数据表格,分别是学生信息、朋友关系和“喜欢”关系。

先看学生信息表。我已经把它上传到了这个 github 链接,你可以点击查看。

表格包含3列,分别是学号、姓名和年级。

然后是朋友关系表。请点击这个链接查看。

除去表头,一共20行。包含两列,代表了关系两端的两个学生的学号。

注意“朋友”关系是一种双向(或者无向)关系。两个人都认可的,才算是朋友。我在人群中,一眼就能认出某影视巨星。可惜,人家不认识我,这显然就不算朋友关系。

再后面,是“喜欢”(likes)关系表。我放在了这个链接

注意这里虽然也描述了关系的起点和终点,但是“喜欢”关系是一种有向关系。张三喜欢李四,李四可能并不喜欢张三。

好了,这就是我们需要用到的全部数据了。下面我们来看看运行环境。

环境

我们使用的,是 R 的集成开发环境(integrated development environment, IDE) RStudio 。

你可以在本机安装 R 以及 RStudio 。我已经把安装和设置步骤写在了《如何用 R 快速了解科研领域?》一文中。

配套的代码和数据,我放在了这个 github 仓库中。你可以下载使用。

然而,此处为了你更快捷地上手,我直接为你准备了云端的运行环境。你只需要点击这个链接http://t.cn/EJ5U8o6),就可以直接在浏览器里面开启一个 RStudio 。其中,已包含了我为你准备好的数据和代码。

双击右下方目录区域里面的 demo.Rmd,你就能看到对应的笔记本了。

怎么样?够方便吧?

其实,你自己也可以构建这样的云端环境。具体的方法,我已经在《如何用iPad运行Python代码?》一文中为你详细介绍了。如果你感兴趣,可以在学过本篇教程后,尝试练练手。

下面我们开始介绍代码了。请你根据我的介绍,逐步点击代码模块旁边的运行按钮,查看运行的结果。

代码

首先,我们需要读入本教程中最重要的软件包,也就是 R 环境下的网络交互可视化工具—— visNetwork

visNetwork 基于 Javascript 可视化工具库 vis.js 开发,为 R 用户提供了简单易用的界面,而且功能也很强大。

我们使用 library 命令来读入它。

library('visNetwork')

下面我们来读入数据。

首先,我们观察“朋友”关系。读入学生信息表和朋友关系表。

nodes <- read.csv("data/Highschooler.csv", header=T, as.is=T)
friends <- read.csv("data/Friend.csv", header=T, as.is=T)

注意这里的参数:

  • header=T 是指我们读入的数据里包含了表头,需要让 R 注意,不要把表头也当成了数据来处理;
  • as.is=T 是指读入的字符串数据,不要默认按照因子来转换。这其实是一种技巧。因为如果大量数据默认做转换,可能会导致读取效率很低。当然,对于我们的例子来说,因为数据量很小,实际上效率差别不大。但是好习惯还是需要养成的。

下面我们要在节点上生成一些属性。我们希望把鼠标挪到某个节点上的时候,显示该学生属于“某年级”;而生成图像的时候,直接在节点旁边标明学生的姓名。

因此,我们就需要分别对 nodes$titlenodes$label 赋值。

之后,用 visNetwork 可视化我们的节点和关系连线。

nodes$title <- paste0("grade", nodes$grade)
nodes$label <- nodes$name
visNetwork(nodes, friends)

结果如下图所示:

注意,这可不是一张静态图,你把鼠标悬浮到 Alexis 人名上试试看。

年级属性就出现了。

你还可以拖动任意一个学生节点,感受一下什么叫做“牵一发而动全身”。

有趣吧?

但是现在所有的节点,都是一样的颜色。我们希望依据不同的年级,重新绘制节点颜色,这样看得会更清晰。

我们依据年级属性(nodes$grade),来定义节点的背景颜色(nodes$color.background)。

然后再次调用 visNetwork,进行绘制。

nodes$color.background <- c("orange", "pink", "yellow", "blue")[nodes$grade]
visNetwork(nodes, friends)

结果是……

不对呀,说好的颜色变化呢?

别着急。

如果让 R 根据不同属性来区分颜色,我们首先需要保证该属性类型是因子(factor)。可是我们读取的时候,为了效率,没让 R 自动转换。

怎么办?

手动来做吧。

nodes$grade <- as.factor(nodes$grade)
nodes$color.background <- c("orange", "pink", "yellow", "blue")[nodes$grade]
visNetwork(nodes, friends)

这次,我们再看看结果:

嗯,感觉好极了。

这里节点很少,全部同时显示,也能看得清晰。但是假设我们需要处理一所真正学校中的朋友关系,可以想象那会有成百上千个节点。如果我们希望聚焦,那就得给用户更多的交互功能。

这里我给你介绍其中一个选项,就是利用 selectedBy ,指定我们让用户在哪一个属性上进行分组选择。

你需要把它放在 visOptions 中。

visNetwork(nodes, friends) %>%
  visOptions(selectedBy = "grade")

运行效果是这样的:

看起来,跟刚刚区别不大,只是多了一个下拉框而已。

我们尝试选择一下:

交互选项,让当前分组保持高亮,其他分组变灰暗,于是我们的注意力就可以集中。

尝试着玩儿一下,看看你能否发现什么有趣的关系模式?

我发现了一个。

大部分情况下,同一个年级的学生间,总会可以关联起来。但是,11年级的 Austin ,跟本年级的其他3名同学,没有任何直接联系。反而,跟其他年级的学生,保持了朋友关系。

如果你是他的班主任,要不要关注一下?

下面,我们来看看另外一种关系——“喜欢”(likes)。

还是读入 csv 文件,到 likes 数据框里面。参数跟刚才一样。

likes <- read.csv("data/Likes.csv", header=T, as.is=T)

先来绘制看看。

visNetwork(nodes, likes)

我们一下子就发现了,这次的整体图形,不再是全连通的。出现了孤立节点。

这些人,既没有“喜欢”别人,也没有“被喜欢”。你自己找找看,都包括哪几个学生?

注意这个图形,是有问题的。

前面提到过,“喜欢”关系是一种有向关系。因此关系的方向很重要。但是目前这张图里面,方向是缺失的。

没关系,只需要给 likes 加入一个属性 arrow 就好。

likes$arrows <- "to"
visNetwork(nodes, likes)

这次就对了。

下面我们来玩儿一个更有趣的——把两个关系合并,一起观察。

我们需要记录一下,关系来自于哪张关系表格。所以分别把表名称作为属性,填到 relation 字段中。

friends$relation <- c("friend")
likes$relation <- c("likes")

看看这时候的 friends 朋友关系表。

friends

好了,下面我们需要合并两张表格。我们需要用到 tidyverse 软件包中的按行合并(bind_rows)功能。所以,需要首先载入tidyverse 软件包。

library(tidyverse)

然后,把原先的两张关系表,合并成一张,起名叫做 links

links <- bind_rows(friends, likes)
links

再次绘制,注意这次,关系位置上,我们放的是合并的 links

visNetwork(nodes, links)

两种关系,确实都绘制好了。可问题是,关系展示的颜色是一样的蓝色,看着不是很清晰。

仿照刚才对节点的颜色赋值,我们把关系连接的颜色也设置一下。

links$color <- c("green", "red")[links$relation]
visNetwork(nodes, links)

呃……好像没有变化嘛。

聪明的你,一定可以举一反三,发现还是由于“关系”(links$relation)属性不是“因子”导致的。

我们把 relation 属性转换成因子类型。

links$relation <- as.factor(links$relation)
links$color <- c("green", "red")[links$relation]
visNetwork(nodes, links)

太棒了!

下面,我们还是以年级作为交互选项,加入进来。

links$relation <- as.factor(links$relation)
links$color <- c("green", "red")[links$relation]
visNetwork(nodes, links) %>%
  visOptions(selectedBy = "grade")

我们选择一下年级,拖动节点看看:

以上,是样例代码中,出现的内容解释。

小结

通过本文的学习,相信你已经掌握如何把社会网络的表格数据,用交互可视化的方法展现出来。

对于不同的元素,你也已经学会了基本的展现方式。

但请你一定不要止步于此。

首先,你需要继续查看文档。里面还有很多选项参数,此处我们没有涉及。例如说,这里我们只绘制了“关系”(边),但是却没有在其上进行任何文字标记。

如果你需要生成非常严肃的数据可视化作品,这些内容很可能用得上。请你点击这个链接,查看完整的文档。从中选择自己感兴趣的部分深入研读。

另外,这个工具,也绝不仅仅可以帮助你绘制社会网络图。只要是适合用网络图展现的内容,它都可以发挥作用。

希望你充分发挥自己举一反三的能力,把这项新技能用好。

祝学习愉快!

延伸阅读

你可能也会对以下话题感兴趣。点击链接就可以查看。

喜欢请点赞和打赏。还可以微信关注和置顶我的公众号“玉树芝兰”(nkwangshuyi)

如果你对 Python 与数据科学感兴趣,不妨阅读我的系列教程索引贴《如何高效入门数据科学?》,里面还有更多的有趣问题及解法。