对于属性较多的数据,我们一般都会去计算两两属性之间的相关性,用以探讨属性之间的关系.
相对于皮尔森相关系数,我们更喜欢使用spearman和kendall相关系数,因为作为非参数相关系数,稳定性更好.
一般来说,计算kendall相关系数可以用以下语句:
cor(x, y, method = "kendall")
在本文一开始提到的场景下,这十分耗时.我们可以从以下3个方面减少计算时间:
1.优化算法,pcaPP包提供的cor.fk函数具有更好的效率,大规模计算kendall系数的话,多使用这个函数.
pcaPP::cor.fk(x, y)
2.将任务拆分成几个并行的子任务分别在不同的计算机上运行,如果能够得到这些计算机的权限,这个方法可以归于第3个方法,这里不多讲.
3.将任务拆分成几个并行的子任务分别在某台计算机的多个cpu上进行计算,这是本文的重点.
R语言提供简单并行计算的包有parallel包.下面以实例说明如何进行并行计算.
##### 生成测试数据 ##### library(parallel) set.seed(0) n <- 1000 # 设定需要计算两两相关系数的属性个数 test <- matrix(runif(100 * n), 100, n) # 样本容量为100 pair <- t(combn(1:n, 2)) # 生成两两配对数据 ##### 并行计算集群初始化及任务拆分 ##### nt <- detectCores() # detectCores函数可以获得本地计算机cpu个数,本地cpu为4 cl <- makeCluster(type = "SOCK", rep("localhost", nt - 1)) # 初始化并行计算集群,cpu个数为总个数减1 ichunk <- split(as.data.frame(pair), 2:nt) # 将配对数据拆分为nt - 1份 ##### f0为非并行计算kendall函数 ##### f0 <- function(x) { nc <- ncol(x) res <- NULL for (i in 1:(nc - 1)) { tmp <- (i + 1):nc res <- c(res, cor(x[, i], x[, tmp], method = "kendall")) } cbind(pair, res) } ##### f1为并行计算kendall函数子函数,也可作为主函数使用 ##### f1 <- function(id, x) { nr <- nrow(id) res <- matrix(0, nr, 1) k <- 1 for (i in 1:nr) { res[k, ] <- pcaPP::cor.fk(x[, id[i, 1]], x[, id[i, 2]]) k <- k + 1 } cbind(id, res) } ##### 计算两两相关系数 ##### system.time(res0 <- f0(test)) system.time(res1 <- f1(pair, test)) system.time(res1p <- clusterApply(cl, ichunk, f1, test)) stopCluster(cl) # 关闭并行计算集群
设n为100,实际运行结果如下:
> system.time(res0 <- f0(test)) 用户 系统 流逝 1.584 0.000 1.587 > system.time(res1 <- f1(pair, test)) 用户 系统 流逝 0.56 0.00 0.56 > system.time(res1p <- clusterApply(cl, ichunk, f1, test)) 用户 系统 流逝 0.008 0.000 0.526
设n为1000,实际运行结果如下:
> system.time(res0 <- f0(test)) user system elapsed 156.84 1.86 162.17 > system.time(res1 <- f1(pair, test)) 用户 系统 流逝 57.056 0.020 57.186 > system.time(res1p <- clusterApply(cl, ichunk, f1, test)) 用户 系统 流逝 0.176 0.124 46.035
可见:
1.即使f0的循环次数比f1的循环次数少得多,但是运行时间还是慢很多,这主要是cor.fk的性能比cor好很多的原因.
2.使用了3个cpu,但是加速的效率却不是3倍的,这主要的是并行计算时需要传递数据等其他运算.