使用dplyr快速生成频率和百分比表格

4

我已经使用了一个小的tab函数一段时间,它可以显示一个向量的频率、百分比和累积百分比。输出的样式如下:

          Freq    Percent        cum
ARSON      462 0.01988893 0.01988893
BURGLARY 22767 0.98011107 1.00000000
         23229 1.00000000         NA

优秀的dplyr包激励我更新了这个函数。现在我想知道如何让更新后的版本更快。以下是旧函数:

tab = function(x,useNA =FALSE) {
  k=length(unique(x[!is.na(x)]))+1
  if (useNA) k=k+1
  tab=array(NA,c(k,3))
  colnames(tab)=c("freq.","prob.","cum.")
  useNA=ifelse(useNA,"always","no")
  rownames(tab)=names(c(table(x,useNA=useNA),""))

  tab[-nrow(tab),1]=table(x,useNA=useNA)
  tab[-nrow(tab),2]=prop.table(table(x,useNA=useNA))
  tab[,3] = cumsum(tab[,2])
  if(k>2)  tab[nrow(tab),-3]=colSums(tab[-nrow(tab),-3])
  if(k==2) tab[nrow(tab),-3]=tab[-nrow(tab),-3]

  tab
}

基于dplyr的新方法

tab2 = function(x, useNA =FALSE) {
    if(!useNA) if(any(is.na(x))) x = na.omit(x)
    n = length(x)
    out = data.frame(x,1) %.%
        group_by(x) %.%
        dplyr::summarise(
            Freq    = length(X1),
            Percent = Freq/n
        ) %.%
        dplyr::arrange(x)
    ids = as.character(out$x)
    ids[is.na(ids)] = '<NA>'
    out = select(out, Freq, Percent)
    out$cum = cumsum(out$Percent)
    class(out)="data.frame"
    out = rbind(out,c(n,1,NA))
    rownames(out) = c(ids,'')
    out
}

最后,一些性能基准测试:

x1 = c(rep('ARSON',462),rep('BURGLARY',22767))
x2 = c(rep('ARSON',462),rep('BURGLARY',22767),rep(NA,100))
x3 = c(c(1:10),c(1:10),1,4)
x4 = c(rep(c(1:100),500),rep(c(1:50),20),1,4)

library('rbenchmark')

benchmark(tab(x1), tab2(x1), replications=100)[,c('test','elapsed','relative')]
#       test elapsed relative
# 1  tab(x1)   1.412    2.307
# 2 tab2(x1)   0.612    1.000

benchmark(tab(x2),tab2(x2), replications=100)[,c('test','elapsed','relative')]
#       test elapsed relative
# 1  tab(x2)   1.351    1.475
# 2 tab2(x2)   0.916    1.000

benchmark(tab(x2,useNA=TRUE), tab2(x2,useNA=TRUE), replications=100)[,c('test','elapsed','relative')]
#                     test elapsed relative
# 1  tab(x2, useNA = TRUE)   1.883    2.282
# 2 tab2(x2, useNA = TRUE)   0.825    1.000

benchmark(tab(x3), tab2(x3), replications=1000)[,c('test','elapsed','relative')]
#       test elapsed relative
# 1  tab(x3)   0.997    1.000
# 2 tab2(x3)   2.194    2.201

benchmark(tab(x4), tab2(x4), table(x4), replications=100)[,c('test','elapsed','relative')]
#        test elapsed relative
# 1   tab(x4)  19.481   18.714
# 2  tab2(x4)   1.041    1.000
# 3 table(x4)   6.515    6.258
tab2 除了对于非常短的向量之外,速度更快。性能增益在较大的向量中变得明显(请参见具有 51002 obs 的 x4)。它甚至比 table 更快,尽管该函数执行了更多操作。

现在我的问题是:如何进一步提高性能?创建频率和百分比表是一个相当标准的应用程序,当您处理大型数据集时,快速实现非常好。

编辑:这里还有一个包含 data.table 解决方案的 2e6 向量的附加测试用例。

x5 = sample(c(1:100),2e6, replace=TRUE)
benchmark(tab(x5), tab2(x5), table(x5), tabdt(x5), replications=100)[,c('test','elapsed','relative')]
#        test elapsed relative
# 1   tab(x5) 350.878   19.444
# 2  tab2(x5)  52.917    2.932
# 4 tabdt(x5)  18.046    1.000
# 3 table(x5)  98.429    5.454

1
这些都是微小的向量,使用基础库运行需要很短的时间 - 这真的是你所说的大型数据集吗(或者你是在循环中运行此操作)? - eddi
不,我的实际数据在1到5百万行之间。这些只是测试用例,在x4中已经显而易见了,它大约有51000个观测值。 - user2503795
1
好的,我建议在真实大小的数据上进行基准测试,因为不同的选项在从50k到5M的规模上可能会有很大的差异。 - eddi
正在处理此事,并将更新一个新案例。 - user2503795
1个回答

8

作为 library(data.table) 的忠实粉丝,我写了一个类似的函数:

tabdt <- function(x){
    n <- length(which(!is.na(x)))
    dt <- data.table(x)
    out <- dt[, list(Freq = .N, Percent = .N / n), by = x]
    out[!is.na(x), CumSum := cumsum(Percent)]
    out
}

> benchmark(tabdt(x1), tab2(x1), replications=1000)[,c('test','elapsed','relative')]
       test elapsed relative
2  tab2(x1)    5.60    1.879
1 tabdt(x1)    2.98    1.000
> benchmark(tabdt(x2), tab2(x2), replications=1000)[,c('test','elapsed','relative')]
       test elapsed relative
2  tab2(x2)    6.34    1.686
1 tabdt(x2)    3.76    1.000
> benchmark(tabdt(x3), tab2(x3), replications=1000)[,c('test','elapsed','relative')]
       test elapsed relative
2  tab2(x3)    1.65    1.000
1 tabdt(x3)    2.34    1.418
> benchmark(tabdt(x4), tab2(x4), replications=1000)[,c('test','elapsed','relative')]
       test elapsed relative
2  tab2(x4)   14.35    1.000
1 tabdt(x4)   22.04    1.536

于是,对于x1和x2,data.table方法更快,而对于x3和x4,dplyr更快。实际上,我没有看到使用这些方法有任何改进的空间。
附言:你能在这个问题中添加data.table关键字吗?我相信人们会喜欢看到dplyrdata.table性能比较(例如,请参见data.table vs dplyr: can one do something well the other can't or does poorly? )。

您介意更新您的答案并提供实际的基准测试数据吗?不幸的是,我在安装dplyr时遇到了很大的问题,所以无法同时运行它们(并确认它们实际上产生相同的输出)。 - BrodieG
3
@BrodieG,你说你在安装dplyr时遇到了很多问题。 当你运行install.packages("dplyr")时会发生什么?请简述。 - Romain Francois
2
@RomainFrancois,出于某种原因(我敢肯定我在某个地方读到过),我认为这只是一个github发布版,到目前为止一直遇到建议依赖项的问题。正常安装可以正常工作(需要自杀表情符号)。 - BrodieG
不错!我已经添加了关键字。尽管 tab2 在计算较长向量时更快,但我认为它表现得更好。即使 x4 不是特别长,其他的也非常短,因此运行速度会非常快。 - user2503795
1
你的解决方案在性能方面可以稍微改进一下:tabdt2 <- function(x){ NnotNA <- sum(!is.na(x)); setnames(setDT(list(x)),"x")[,list(Freq = .N, Percent = .N / NnotNA), by = x][!is.na(x), CumSum := cumsum(Percent)] } - jangorecki
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接