前三章中列出的大多数示例代码都很短,并没有涉及到复杂的操作。从本章开始将会把前面介绍的数据结构组合起来,构成真正的程序。大部分程序是由条件语句和循环语句控制,R 语言中的条件语句(if-else)和 C 语言中类似此处就不再介绍,循环语句包括 forwhile 控制块。循环是社交网络分析的主旋律,比如使用 for 循环遍历分析网络中的每一个节点。当网络规模足够大时,并行处理又变得十分必要。熟练掌握本章的内容后,你的程序将会优雅而自然。

循环语句

while

while 循环作为最简单的一种循环,只要满足条件(condition 为 TRUE),循环将会一直进行。

while (condition) {
# TODO
}

在 R 语言中还存在特殊的关键字 repeat,在 repeat 控制块内的语句将会无限的执行。下面的示例代码效果是等价的:

repeat {
# TODO
} while (TRUE) {
# TODO
}

for

R 语言中的 for 循环更像某些语言中的 foreach,本质上就是遍历向量(或其他数据结构)中的元素:

for (name in vector) {
# TODO
}

下面的示例将会输出向量中的元素:

> v <- c("a", "b", "c")
> for (item in v) {
+ print(item)
+ }
[1] "a"
[1] "b"
[1] "c"

循环控制

有时当满足条件时,需要使用 break 退出循环:

while (TRUE) {
# TODO
if (condition) {
break
}
}

或者使用 next 退出当前循环(类似其他语言的 continue):

for (name in vector) {
# TODO
if (condition) {
next
}
}

apply() 系列函数

R 语言中循环语句的执行效率是无法忍受的,这是因为循环语句是基于 R 语言本身来实现的,而向量操作是基于 C 语言实现的,所以应避免使用显式循环,使用 apply() 系列函数进行替代。举个例子,对一个矩阵的行求和,并封装一个函数,使用 for 循环应该是这样:

func1 <- function(matrix) {
row_sum <- c()
for (i in 1: nrow(matrix)) {
row_sum[i] <- sum(matrix[i, ]) # 对每一行求和
} return(row_sum)
}

使用 sapply() 可以这样简化代码:

func2 <- function(matrix) {
return(sapply(1: nrow(matrix), function(i) { return(sum(matrix[i, ])) }))
}

下面测试一下两种方法的性能消耗:

> m <- matrix(c(1: (10000 * 10000)), nrow = 10000)  # 10000x10000 的方阵
> system.time(func1(m))
用户 系统 流逝
0.79 0.00 0.79
> system.time(func2(m))
用户 系统 流逝
0.72 0.00 0.72

上面的例子说明使用 for 循环不仅代码冗余,而且 for 循环实现的计算是耗时最长的,这就是为什么要了解 apply() 系列函数的原因。apply() 系列函数本身就是解决数据循环处理的问题,为了面向不同的数据类型,不同的返回值,apply() 函数组成了一个函数族。一般使用最多的是对矩阵处理的函数 apply() 以及对向量处理的函数 sapply()

apply() 系列函数[1]

apply()

apply() 函数用于多维数据的处理,比如矩阵。其本质上是对 for 循环的进一步封装,并不会加快计算速度apply() 函数的定义如下:

apply(X, MARGIN, FUN)

提示

要查看函数的文档可以在 R 终端中键入“?函数名”,比如查看 apply() 的文档输入 ?apply

其中 X 是要循环处理的数据,即矩阵;MARGIN 是数据处理的维度,1 是按行处理,2 是按列处理;FUN 是循环处理的函数。对一个矩阵的行求和使用 apply() 函数更简单,但效率上不如 sapply()

func2 <- function(matrix) {
return(apply(matrix, 1, sum))
}

sapply()

sapply() 函数用于循环处理一维数据,比如向量。参数上更加精简,处理完成的数据返回的结果集为向量,其定义如下:

sapply(X, FUN)

其中 X 是要循环处理的数据,即向量;FUN 是循环处理的函数。在不使用向量运算的前提下计算向量的平方,使用 sapply() 函数可以这样:

> v <- c(1, 2, 3)
> sapply(v, function(item) { return(item ^ 2) })
[1] 1 4 9

使用 parallel 包并行处理

现代 CPU 通常拥有 4 个以上的核心,为了使计算机更努力的“工作”,将任务并行化处理变得很有意义。充分利用多核 CPU,运行速度可能会快四倍,这样我们等待实验的时间更少,并且可以运行更多的实验。在开始将任务并行化之前,首先需要问自己一个问题:任务是否能够并行?要回答这个问题,你需要思考任务是否具有“重复性”,即每个子任务可以保持计算的独立性,只有可重复的任务才能分配到多个 CPU 上运行。回到上文中“对一个矩阵的行求和”这个问题上,“求和”是一个可重复的任务,矩阵的行数决定了“求和”的次数,对矩阵中某一行向量的求和并不会干扰其他行向量的求和,因此该问题可以进行并行处理。或者更简单的说,包含在循环控制块内的代码基本都可以进行并行处理。

在 R 语言中并行计算有 snowparallel 两个包可选,两个包功能上一样,这里使用 parallel,最直接的原因是 R 语言集成了这个包,无需额外安装。并行函数的用法基本等同于 apply() 系列函数,比如:apply() 对应的并行计算函数为 parApply()sapply() 对应的并行计算函数为 parSapply() 等等。

在本机上并行

在本机上处理并行计算的概念很好理解,就是将需要并行处理的任务分配到计算机的多个 CPU 内核中,这也是最常见的场景。继续以“对一个矩阵的行求和”为例,采用并行的方式解决这个问题。首先需要创建一个并行集群:

> library(parallel)
> parallel.cores <- detectCores() # 检测本机的内核数
> cl <- makeCluster(parallel.cores) # 创建集群,从机的数量为内核数

提示

通常创建集群的从机数量不要超过 最大内核数 - 1,最好保留 1~2 个内核供系统调度以及其他任务使用。

如果没有任何错误提示的话,则本机集群创建完成,可以将创建的集群打印出来以查看信息。

> print(cl)
socket cluster with 16 nodes on host 'localhost'

提示

本机集群的创建错误通常和端口占用有关,处理该问题可以查看端口的占用情况并结束程序,或者重启计算机。

紧接着调用 parApply() 进行并行计算,并行计算的 parApply() 系列方法仅仅需要在第一个参数将创建的集群传递进去即可。

func3 <- function(cluster, matrix) {
return(parApply(cluster, matrix, 1, sum))
}

下面来测试一下并行计算的时间开销:

> system.time(func3(cl, m))
用户 系统 流逝
3.43 0.47 4.86

测试的结果似乎与想象的有些不同,时间变得更慢了。这是由于 parallel 创建的是套接字集群,从机之间的通信速度是较慢的,由于求和这个任务本身就很简单,通信的开销远远大于计算的时间消耗,因此导致了计算速度并没有变得更快。这也告诉我们过于“轻松”的任务,并不需要并行执行。

最后在并行计算完成后需要及时关闭集群:

> stopCluster(cl)

由于集群是一个独立的环境,本地环境所引入的包、拥有的变量在集群内是无法访问的。在进行更复杂的并行任务时,需要将包或者变量传递至集群中:

> clusterEvalQ(cl, { library(igraph) })  # 为集群引入包
> clusterExport(cl, c("graph", "subgraph"), envir = environment()) # 为集群引入本地变量

在多台计算机上并行

由于 parallel 创建的是套接字集群,这使得将并行任务分配至多台计算机成为可能。当然这并不意味着计算机越多就能获得更快的计算速度。parallel 分配任务的方式类似均分,如果计算机之间单核的性能差距过大,那么会出现一台计算机分配的任务已经完成而等待其他计算机的现象,这样反而会出现计算速度的下降。并且并行计算的速度还与计算机之间的通信速度有关,从机的变量共享来自于主机,当网络情况不佳时,通信的消耗也是不容忽视的。因此在多台计算机上进行并行任务时需要谨慎考虑。在多台计算机上并行与在本机上并行的区别仅在于集群的创建,因此本小节将只介绍集群创建的不同。

这里使用两台计算机进行模拟实验,主机的操作系统为 Windows 10,从机的操作系统为 Ubuntu 20.04,使用两台安装了不同操作系统的计算机模拟了最复杂的情况,拓扑图如下所示。

提示

计算机之间的通信需要 SSH,Windows 10 请在“可选功能”中添加“OpenSSH 服务器”,Ubuntu Desktop 请运行命令 apt install openssh-server

同时为了避免在创建集群时手动输入 SSH 登录密码,请配置 SSH 密钥登录。

首先创建一个列表,用于配置集群计算机的信息。其中 host 为计算机的地址;user 为 SSH 登录的用户名;rscript 为 Rscript 程序的路径,当主从机的操作系统相同时该字段可以省略;ncore 为分配的 CPU 内核数。

> master <- '192.168.122.100'
> addresses <- list(
+ list(host = master, user = "zhang", rscript = "C:/Program Files/R/R-4.0.5/bin/Rscript", ncore = 4),
+ list(host = "192.168.122.200", user = "zhang", rscript = "/usr/lib/R/bin/Rscript", ncore = 4)
+ )

由于 parallel 是将一个 CPU 内核作为从机,而上面的配置是按照计算机进行的,因此还需要根据 ncore 字段创建分配 CPU 内核数的从机:

> spec <- lapply(addresses, function(machine) {
+ rep(list(list(host = machine$host, user = machine$user, rscript = machine$rscript)), machine$ncore)
+ })
> spec <- unlist(spec, recursive = FALSE)

可以将创建的 spec 变量打印出来,观察是否创建了 8 个从机的信息。

> length(addresses)
[1] 2
> length(spec)
[1] 8

紧接着就可以调用 makeCluster() 创建集群,此过程根据计算机的数量可能需要数分钟。其中 manual 为是否手动激活从机,当创建集群出现问题时,可以将该字段设置为 TRUE,根据提示手动激活从机,以此来观察哪一台计算机出现了问题;outfile 为日志文件的存储地址,当创建集群出现问题时,也可以查看该文件。

cl <- makeCluster(type = "PSOCK", master = master, spec = spec, manual = FALSE, outfile = "log.txt")

此时如果没有提示任何错误,那么一个由多台计算机组成的集群已经创建完成。现在可以使用 parApply() 系列函数将任务并行的在多台计算机上运行。

> print(cl)
socket cluster with 8 nodes on hosts
'192.168.122.100', '192.168.122.200'

提示

多台计算机集群的创建错误通常与 SSH 登录和包的引用有关。SSH 登录的错误根据提示信息进行处理,包引用的错误请确保计算机之间的 R 语言版本、包的版本一致。

️ 练习

1. 使用 for 循环倒序输出 0~100;

2. 定义一个函数,使用 apply() 系列函数,求一个矩阵列向量的平均值。

参考

  1. 掌握R语言中的apply函数族 | 粉丝日志
  2. Running R jobs quickly on many machines – Win Vector LLC

社交网络分析的 R 基础:(四)循环与并行的更多相关文章

  1. 社交网络分析的 R 基础:(一)初探 R 语言

    写在前面 3 年的硕士生涯一转眼就过去了,和社交网络也打了很长时间交道.最近突然想给自己挖个坑,想给这 3 年写个总结,画上一个句号.回想当时学习 R 语言时也是非常戏剧性的,开始科研生活时到处发邮件 ...

  2. 社交网络分析的 R 基础:(二)变量与字符串

    本章会从 R 语言中最基本的数据类型开始介绍,在此之后就可以开始 R 语言实践了.对社交网络分析而言,我们在处理字符串上所花费的时间要远远大于处理数字的时间,因此本章还会介绍常用的字符串处理操作. 变 ...

  3. 社交网络分析的 R 基础:(三)向量、矩阵与列表

    在第二章介绍了 R 语言中的基本数据类型,本章会将其组装起来,构成特殊的数据结构,即向量.矩阵与列表.这些数据结构在社交网络分析中极其重要,本质上对图的分析,就是对邻接矩阵的分析,而矩阵又是由若干个向 ...

  4. 社交网络分析的 R 基础:(五)图的导入与简单分析

    如何将存储在磁盘上的邻接矩阵输入到 R 程序中,是进行社交网络分析的起点.在前面的章节中已经介绍了基本的数据结构以及代码结构,本章将会面对一个实质性问题,学习如何导入一个图以及计算图的一些属性. 图的 ...

  5. 社交网络分析的 R 基础:(六)绘图操作

    R 语言强大的可视化功能在科学研究中非常受欢迎,丰富的类库使得 R 语言可以绘制各种各样的图表.当然这些与本章内容毫无关系,因为笔者对绘制图表了解有限,仅限于能用的程度.接下来的内容无需额外安装任何包 ...

  6. JavaScript 基础(四) 循环

    JavaScript的循环有两种,一种是for 循环,通过初始条件,结束条件和递增条件来循环执行语句块: var x = 0; var i; for(i=1; i <=10000; i++){ ...

  7. Python全栈开发【基础四】

    Python全栈开发[基础四] 本节内容: 匿名函数(lambda) 函数式编程(map,filter,reduce) 文件处理 迭代器 三元表达式 列表解析与生成器表达式 生成器 匿名函数 lamb ...

  8. [转] X-RIME: 基于Hadoop的开源大规模社交网络分析工具

    转自http://www.dataguru.cn/forum.php?mod=viewthread&tid=286174 随着互联网的快速发展,涌现出了一大批以Facebook,Twitter ...

  9. 记一个社交APP的开发过程——基础架构选型(转自一位大哥)

    记一个社交APP的开发过程——基础架构选型 目录[-] 基本产品形态 技术选型 最近两周在忙于开发一个社交App,因为之前做过一点儿社交方面的东西,就被拉去做API后端了,一个人头一次完整的去搭这么一 ...

随机推荐

  1. Windows服务注册(需要指定config文件的情况下)

    最近,遇到一个问题:需要将telegraf在Win平台下注册为windows服务(避免误操作关闭CMD窗口): 尝试了网上的几种注册Windows服务的方法,发现无法将telegraf这种需要在CMD ...

  2. Redis -使用 Bitmap

    redis数据类型 String.Set.Zset.List.hash       Bitmap . 四种统计类型: 二值状态统计: 聚合统计: 排序统计: 基数统计 二值状态统计: 就是集合中的元素 ...

  3. request参数获取,参数校验,参数处理

    需求: 1.post接口,需要在过滤器中进行参数校验,校验通过之后再执行方法 2.原有代码中使用x-www-form-urlencoded传参,新需求要使用json格式 3.原有代码校验过滤器使用Se ...

  4. iOS提交AppStore审核时:提示有其他支付并隐藏功能被拒的处理办法

    背景提示:数字类产品(比如购买会员等不需要配送实物的商品),Apple规定必须使用苹果IAP应用内支付,给Apple分成30%.打包的时候不要勾选微信或支付宝等其他支付方式.如果你提交的包里包含了微信 ...

  5. Zookeeper基础教程(二):Zookeeper安装

    上一篇说了,一个Zookeeper集群一般认为至少需要3个节点,所以我们这里安装需要准备三台虚拟机: # 192.168.209.133 test1 # 192.168.209.134 test2 # ...

  6. 字符串的展开expand

    A. 字符串的展开(expand.cpp) 内存限制:64 MiB 时间限制:1000 ms 标准输入输出 题目类型:传统 评测方式:文本比较 题目描述 在初赛普及组的"阅读程序写结果&qu ...

  7. 安装uiautomator2 + python 自动化环境

    搭建环境: 1.Python版本 37 2.已经搭建到adb (之前试过在python版本几,一直都装不上UIautomator2,报这个错) 安装步骤: 1.到python的安装路径 > 一直 ...

  8. BugKu CTF(杂项篇MISC)-贝斯手

    打开是以下内容 先看一下给了哪些提示 1.介绍 没了?不,拉到最底下还有 2.女神剧照 密码我4不会告诉你的,除非你知道我的女神是哪一年出生的(细品) 大致已经明白了,四位数密码,出生年份 文件是以下 ...

  9. C# 开源一个基于 yarp 的 API 网关 Demo,支持绑定 Kubernetes Service

    关于 Neting 刚开始的时候是打算使用微软官方的 Yarp 库,实现一个 API 网关,后面发现坑比较多,弄起来比较麻烦,就放弃了.目前写完了查看 Kubernetes Service 信息.创建 ...

  10. Keepalived高可用、四层负载均衡

    目录 Keepalived高可用 高可用简介 常用的工具 问题 名称解释 VRRP协议 部署keepalived 下载安装 Keepalived配置 保证nginx配置一样 解决keepalived的 ...