【问题标题】:Progressively find most frequent item in list in R逐步找到R中列表中最常见的项目
【发布时间】:2015-04-22 21:01:16
【问题描述】:

我想浏览一个列表,并检查该项目是否是该列表中出现频率最高的项目。与 Python 相比,我目前拥有的解决方案非常慢。有没有有效的加速方法?

   dat<-data.table(sample(1:50,10000,replace=T))
   k<-1
   correct <- 0  # total correct predictions
   for (i in 2:(nrow(dat)-1)) {
      if (dat[i,V1] %in% dat[1:(i-1),.N,by=V1][order(-N),head(.SD,k)][,V1]) {
         correct <- correct + 1
      }
   }

更一般地说,我最终想看看一个项目是否是 k most 之一 直到某个点之前的频繁项,或者如果它具有直到某个点为止的 k 个最高值之一。

作为比较,这里有一个非常快速的 Python 实现:

dat=[random.randint(1,50) for i in range(10000)]
correct=0
k=1
list={}

for i in dat:
    toplist=heapq.nlargest(k,list.iteritems(),key=operator.itemgetter(1))
    toplist=[j[0] for j in toplist]
    if i in toplist:
        correct+=1
    if list.has_key(i):
        list[i]=list[i]+1
    else:
        list[i]=1

【问题讨论】:

  • list 在哪里?
  • @Khashaa 是一维数据表dat
  • 介意分享您的 Python 解决方案吗?
  • 您能否展示(并合理化)dat &lt;- data.table(c(1,2,2,1,1)) 的预期输出?
  • 在 OP 的解决方案中,如果条件几乎没有损坏。设置dat[i,V1] %in% dat[1:(i-1),.N,by=V1][order(-N),head(.SD,k)]$V1 会修复它

标签: r for-loop optimization time-series data.table


【解决方案1】:

[新解决方案]

k=1 有一个闪电般快速且非常简单的dplyr 解决方案。下面的fC1 平等对待平局,即没有平局。您将看到您可以对其施加任何打破平局的规则。而且,它真的很快。

library(dplyr)
fC1 <- function(dat){
   dat1 <- tbl_df(dat) %>%
       group_by(V1) %>% 
       mutate(count=row_number()-1)  %>%  ungroup() %>% slice(2:n()-1) %>% 
       filter(count!=0) %>%
       mutate(z=cummax(count)) %>% 
       filter(count==z)  
   z <- dat1$z
   length(z)
}

set.seed(1234)
dat<-data.table(sample(1:5000, 100000, replace=T))
system.time(a1 <- fC1(dat))[3] #returns 120
elapsed 
   0.04 
system.time(a3m <- f3m(dat, 1))[3] #returns 29, same to the Python result which runs about 60s
elapsed 
  89.72 
system.time(a3 <- f3(dat, 1))[3] #returns 31. 
elapsed 
  95.07 

您可以随意对fC1的结果施加一些平局规则,以得出不同的解决方案。例如,为了得到f3mf3 的解决方案,我们限制选择一些行如下

fC1_ <- function(dat){
    b <- tbl_df(dat) %>% 
        group_by(V1) %>% 
        mutate(count=row_number()-1) %>%
        ungroup()  %>% 
        mutate(L=cummax(count+1))# %>% 
    b1 <- b %>% slice(2:(n()-1)) %>% 
        group_by(L) %>% 
        slice(1) %>% 
        filter(count+1>=L& count>0)
    b2 <- b %>% group_by(L) %>% 
        slice(1)  %>%
        ungroup() %>%  
        select(-L)  %>%  
        mutate(L=count)
    semi_join(b1, b2, by=c("V1", "L")) %>% nrow
}


set.seed(1234)
dat <- data.table(sample(1:50,10000,replace=T))
fC1_(dat)
#[1] 218
f3m(dat, 1)
#[1] 217
f3(dat, 1)
#[1] 218

前面的例子

set.seed(1234)
dat<-data.table(sample(1:5000, 100000, replace=T))
system.time(fC1_(dat))[3];fC1_(dat)
#elapsed 
#   0.05 
#[1] 29

不知何故,我无法为一般k&gt;1 扩展解决方案,所以我求助于 Rcpp。

#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
std::vector<int> countrank(std::vector<int> y, int k) {
    std::vector<int> v(y.begin(), y.begin() + k);
    std::make_heap(v.begin(), v.end());
    std::vector<int> count(y.size());
    for(int i=0; i < y.size(); i++){
        if(y[i]==0){count[i]=0;}
        else{
            v.push_back(y[i]); std::push_heap(v.begin(), v.end()); 
            std::pop_heap(v.begin(), v.end()); v.pop_back();
            std::vector<int>::iterator it = std::find (v.begin(), v.end(), y[i]);
            if (it != v.end()) {count[i]=1;};
        }
    }
    return count;
}

对于k=1,值得注意的是fC1至少和下面的Rcpp版本fCpp一样快。

 fCpp <- function(dat, k) {
    dat1 <- tbl_df(dat) %>% 
        group_by(V1) %>% 
        mutate(count=row_number())
    x <- dat1$V1
    y <- dat1$count-1
    z <- countrank(-y, k)
    sum(z[2:(nrow(dat)-1)])
}

同样,您可以毫不费力地实施任何打破平局的规则。


[f3, f3m函数]

f3 来自@Marat Talipov,f3m 是对它的一些修改(不过似乎是多余的)。

f3m <- function(dat, k){
    n <- nrow(dat)
    dat1 <- tbl_df(dat) %>% 
        group_by(V1) %>% 
        mutate(count=row_number())
    x <- dat1$V1
    y <- dat1$count
    rank <- rep(NA, n)
    tablex <- numeric(max(x))
    for(i in 2:(n-1)){
        if(y[i]==1){rank[i]=NA} #this condition was originally missing
        else{
            tablex[x[i-1]] = y[i-1]
            rank[i]=match(x[i], order(tablex, decreasing = T))
        }
    }
    rank <- rank[2:(n-1)] 
    sum(rank<=k, na.rm=T)
 } 

请参阅编辑历史以获得早期解决方案。

【讨论】:

    【解决方案2】:

    在观察到 k+1 个值之前,条件自动为真:

    startrow <- dat[,list(.I,.GRP),by=V1][.GRP==k+1]$.I[1]
    correct  <- rep(0L,length(v))
    correct[1:(startrow-1)] <- 1L
    

    您可以预先计算值 V1 到目前为止的出现次数:

    ct   <- dat[,ct:=1:.N,by=V1]$ct
    

    在循环过程中,我们可以检查第k个最频繁的值是否被当前值淘汰。

    1. 获取前 k 个值及其计数,直到 startrowtopk &lt;- sort(tapply(ct[1:(startrow-1)],v[1:(startrow-1)],max))
    2. 注意第一项是加入top-k俱乐部的门槛:thresh &lt;- unname(topk[1])
    3. startrow 循环到length(v),只要满足阈值就更新correct(这里是一个向量,而不是一个运行总和);如果满足阈值并且该值尚未在俱乐部中,则更新前 k 俱乐部。


    就是这样;剩下的只是细节。这是我的功能:

    ff <- function(dat){
        vf   <- factor(dat$V1)
        v    <- as.integer(vf) 
        ct   <- dat[,ct:=1:.N,by=V1]$ct
        n    <- length(v)
    
        ct <- setNames(ct,v)
    
        startrow <- dat[,list(.I,.GRP),by=V1][.GRP==k+1]$.I[1]
        topk     <- sort(tapply(ct[1:(startrow-1)],v[1:(startrow-1)],max))
        thresh   <- unname(topk[1])
    
        correct  <- rep(0L,n)
        correct[1:(startrow-1)] <- 1L
        for (i in startrow:n) {
            cti = ct[i]
            if ( cti >= thresh ){
                correct[i] <- 1L
                if ( cti > thresh & !( names(cti) %in% names(topk) ) ){
                    topk    <- sort(c(cti,topk))[-1]
                    thresh  <- unname(topk[1])
                }
            }
        }
        sum(correct)
    }
    

    它非常快,但与@MaratTalipov 和 OP 的结果不同:

    set.seed(1)
    dat <- data.table(sample(1:50,10000,replace=T))
    k   <- 5
    
    f1(dat) # 1012
    f3(dat) # 1015
    ff(dat) # 1719
    

    这是我的基准(不包括 f1() 中封装的 OP 方法,因为我很不耐烦):

    > benchmark(f3(dat),ff(dat),replications=10)[,1:5]
         test replications elapsed relative user.self
    1 f3(dat)           10    2.68    2.602      2.67
    2 ff(dat)           10    1.03    1.000      1.03
    

    我的函数比@Marat 和 OP 提供更多匹配项,因为它允许阈值处的平局计为“正确”,而他们的函数最多只计算由 R 的 order 函数使用的任何算法选择的 k 个值的匹配项。

    【讨论】:

    • +1 因为我认为这是正确的想法,但是你的实现有问题,我得到Error in 1:(startrow - 1) : argument of length 0
    • @eddi:嗯,这很奇怪;我猜startrow 应该总是大于 1,只要dat$V1 有多个值。我注意到我的解决方案还有其他问题——它没有像应有的那样经常更新阈值。我想如果我清楚对领带的期望处理是什么,我会重新审视它;无论如何都要保留它,以便其他回答者可以借用有用的部分(topkct 的代码)来加快他们的解决方案。
    【解决方案3】:

    这个解决方案怎么样:

    # unique values
    unq_vals <- sort(dat[, unique(V1)])
    
    # cumulative count for each unique value by row
    cum_count <- as.data.table(lapply(unq_vals, function(x) cumsum(dat$V1==x)))
    
    # running ranking for each unique value by row
    cum_ranks <- t(apply(-cum_count, 1, rank, ties.method='max'))
    

    现在(例如)第 8 个观察值的第 2 个唯一值的排名存储在:

    cum_ranks[8, 2]
    

    您可以像这样逐行获取每个项目的排名(并将其呈现在可读的表格中)。如果 rank V1 的第 i 个项目是观察 i 的第 k 个最频繁的项目。

    dat[, .(V1, rank=sapply(1:length(V1), function(x) cum_ranks[x, V1[x]]))]
    

    第一个代码块在我的机器上只需要 0.6883929 秒(根据粗略的now &lt;- Sys.time(); [code block in here]; Sys.time() - now 计时),dat &lt;- data.table(sample(1:50, 10000, replace=T))

    【讨论】:

    • dat&lt;-data.table(1:10000)的情况如何?
    • 对,但我的示例有 10000 个不同元素
    • 哦,我明白了。使用您的 dat(10,000 行和 10,000 个不同的元素),它在 41.41968 秒内运行。
    • 在我的笔记本电脑上,大约需要 50 秒。我的解决方案在大约 10 秒内运行,python 解决方案大约需要 8 秒。
    • 此外,使用 data.table 的 frank 代替基础 rank 可以显着改进此解决方案。谢谢@MaratTalipov 和@arvi1000,我早上再看看这些
    【解决方案4】:

    这是我到目前为止所得到的(我的解决方案是 f3):

    set.seed(10)
    dat<-data.table(sample(1:3,100,replace=T))
    k<-1
    
    f3 <- function(dat) {
        correct <- 0  # total correct predictions
        vf <- factor(dat$V1)
        v <- as.integer(vf) 
        tabs <- integer(max(v))
        for (i in 2:(nrow(dat)-1)) {
            tabs[v[i-1]] <- tabs[v[i-1]] + 1
            #print(tabs)
            #print(v[1:i])
            if (match(v[i],order(tabs,decreasing = T))<=k) {
                correct <- correct + 1
            }
            #print(correct)
            #print('')
    
        }
        correct
    }
    
    f1 <- function(dat) {
        correct <- 0  # total correct predictions
        for (i in 2:(nrow(dat)-1)) {
    
            if (dat[i,V1] %in% dat[1:(i-1),.N,by=V1][order(-N),head(.SD,k)]) {
                correct <- correct + 1
            }
    
        }
        correct
    }
    
    library(rbenchmark)
    
    print(f1(dat)==f3(dat))
    
    library(rbenchmark)
    benchmark(f1(dat),f3(dat),replications=10)
    

    基准测试结果:

         test replications elapsed relative user.self sys.self user.child sys.child
    1 f1(dat)           10   2.939  163.278     2.931    0.008          0         0
    2 f3(dat)           10   0.018    1.000     0.018    0.000          0         0
    

    令人鼓舞,但f3 有两个问题:

    1. 它并不总是提供与 OP 算法相同的答案,因为对关系的处理方式不同,

    2. 还有很大的改进空间,因为tabs每次都重新排序。

    【讨论】:

    • print(f1(dat)==f3(dat))FALSE?
    • @Khashaa,是的,是FALSE
    猜你喜欢
    • 2013-09-20
    • 2015-02-18
    • 2017-04-25
    • 2021-05-21
    • 2018-11-06
    • 1970-01-01
    • 1970-01-01
    • 2021-09-22
    • 1970-01-01
    相关资源
    最近更新 更多