挑战:重新编码data.frame() - 让它更快

时间:2011-05-27 02:06:42

标签: r dataframe

重新编码是调查数据的常见做法,但最明显的路线需要的时间比应有的多。

通过我的计算机上system.time()提供的示例数据完成相同任务的最快代码获胜。

## Sample data
dat <- cbind(rep(1:5,50000),rep(5:1,50000),rep(c(1,2,4,5,3),50000))
dat <- cbind(dat,dat,dat,dat,dat,dat,dat,dat,dat,dat,dat,dat)
dat <- as.data.frame(dat)
re.codes <- c("This","That","And","The","Other")

优化代码。

for(x in 1:ncol(dat)) { 
    dat[,x] <- factor(dat[,x], labels=re.codes)
    }

当前system.time()

   user  system elapsed 
   4.40    0.10    4.49 

提示:dat <- lapply(1:ncol(dat), function(x) dat[,x] <- factor(dat[,x],labels=rc)))速度不快。

6 个答案:

答案 0 :(得分:10)

我的计算机显然要慢得多,但结构是一种非常快速的方法:

> system.time({
+ dat1 <- dat
+ for(x in 1:ncol(dat)) {
+   dat1[,x] <- factor(dat1[,x], labels=re.codes)
+   }
+ })
   user  system elapsed 
 11.965   3.172  15.164 
> 
> system.time({
+ m <- as.matrix(dat)
+ dat2 <- data.frame( matrix( re.codes[m], nrow = nrow(m)))
+ })
   user  system elapsed 
  2.100   0.516   2.621 
> 
> system.time(dat3 <- data.frame(lapply(dat, structure, class='factor', levels=re.codes)))
   user  system elapsed 
  0.484   0.332   0.820 

# this isn't because the levels get re-ordered
> all.equal(dat1, dat2)

> all.equal(dat1, dat3)
[1] TRUE

答案 1 :(得分:10)

结合@DWin's answerMost efficient list to data.frame method?的回答:

system.time({
  dat3 <- list()
  # define attributes once outside of loop
  attrib <- list(class="factor", levels=re.codes)
  for (i in names(dat)) {              # loop over each column in 'dat'
    dat3[[i]] <- as.integer(dat[[i]])  # convert column to integer
    attributes(dat3[[i]]) <- attrib    # assign factor attributes
  }
  # convert 'dat3' into a data.frame. We can do it like this because:
  # 1) we know 'dat' and 'dat3' have the same number of rows and columns
  # 2) we want 'dat3' to have the same colnames as 'dat'
  # 3) we don't care if 'dat3' has different rownames than 'dat'
  attributes(dat3) <- list(row.names=c(NA_integer_,nrow(dat)),
    class="data.frame", names=names(dat))
})
identical(dat2, dat3)  # 'dat2' is from @Dwin's answer

答案 2 :(得分:8)

试试这个:

m <- as.matrix(dat)

dat <- data.frame( matrix( re.codes[m], nrow = nrow(m)))

答案 3 :(得分:7)

data.table答案供您考虑。我们只使用了setattr()data.framedata.frame列。无需转换为data.table

再次测试数据:

dat <- cbind(rep(1:5,50000),rep(5:1,50000),rep(c(1L,2L,4L,5L,3L),50000)) 
dat <- cbind(dat,dat,dat,dat,dat,dat,dat,dat,dat,dat,dat,dat) 
dat <- as.data.frame(dat) 
re.codes <- c("This","That","And","The","Other") 

现在更改类并直接设置每列的级别,参考:

require(data.table)
system.time(for (i in 1:ncol(dat)) {
  setattr(dat[[i]],"levels",re.codes)
  setattr(dat[[i]],"class","factor")
}
# user  system elapsed 
#   0       0       0 

identical(dat, <result in question>)
# [1] TRUE

0.00赢了吗?随着您增加数据的大小,此方法保持为0.00

好的,我承认,我将所有列的输入数据略微更改为integer(问题在第三列中有double个输入数据)。这些double列必须转换为integer,因为factor仅对integer个向量有效。正如其他答案所述。

因此,严格按照问题中的输入数据进行操作,并包括doubleinteger转换:

dat <- cbind(rep(1:5,50000),rep(5:1,50000),rep(c(1,2,4,5,3),50000))             
dat <- cbind(dat,dat,dat,dat,dat,dat,dat,dat,dat,dat,dat,dat)               
dat <- as.data.frame(dat)               
re.codes <- c("This","That","And","The","Other")           

system.time(for (i in 1:ncol(dat)) {
  if (!is.integer(dat[[i]]))
      set(dat,j=i,value=as.integer(dat[[i]]))
  setattr(dat[[i]],"levels",re.codes)
  setattr(dat[[i]],"class","factor")
})
#  user  system elapsed
#  0.06    0.01    0.08      # on my slow netbook

identical(dat, <result in question>)
# [1] TRUE

请注意,set也适用于data.frame。您无需转换为data.table即可使用它。

很明显,这些时间非常短暂。因为它只是一个小的输入数据集:

dim(dat)
# [1] 250000     36 
object.size(dat)
# 68.7 Mb

从中扩大应该会发现更大的差异。但即便如此,我认为应该(几乎)可测量的速度最快。但是,在这个规模上,没有人会想到这一点。

setattr函数也在bit包中,顺便说一下。因此,0.00方法可以使用data.tablebit来完成。要通过引用进行类型转换(如果需要),需要set:=(均在data.table中),afaik。

答案 4 :(得分:6)

class()的帮助页面说该类&lt; - 已弃用并用作。方法。我还没弄清楚为什么当数据明显在对象中时,之前的努力报告了0个观察结果,但是这个方法产生了一个完整的对象:

    system.time({ dat2 <- vector(mode="list", length(dat))
      for (i in 1:length(dat) ){ dat2[[i]] <- dat[[i]]
        storage.mode(dat2[[i]]) <- "integer"
               attributes(dat2[[i]]) <- list(class="factor", levels=re.codes)}
  names(dat2) <- names(dat)
  dat2 <- as.data.frame(dat2)})
#--------------------------  
  user  system elapsed 
  0.266   0.290   0.560 
> str(dat2)
'data.frame':   250000 obs. of  36 variables:
 $ V1 : Factor w/ 5 levels "This","That",..: 1 2 3 4 5 1 2 3 4 5 ...
 $ V2 : Factor w/ 5 levels "This","That",..: 5 4 3 2 1 5 4 3 2 1 ...
 $ V3 : Factor w/ 5 levels "This","That",..: 1 2 4 5 3 1 2 4 5 3 ...
 $ V4 : Factor w/ 5 levels "This","That",..: 1 2 3 4 5 1 2 3 4 5 ...
 $ V5 : Factor w/ 5 levels "This","That",..: 5 4 3 2 1 5 4 3 2 1 ...
 $ V6 : Factor w/ 5 levels "This","That",..: 1 2 4 5 3 1 2 4 5 3 ...
 $ V7 : Factor w/ 5 levels "This","That",..: 1 2 3 4 5 1 2 3 4 5 ...
 $ V8 : Factor w/ 5 levels "This","That",..: 5 4 3 2 1 5 4 3 2 1 ...
 snipped

所有36列都在那里。

答案 5 :(得分:3)

制造因素很昂贵;只执行一次与使用structure的命令相当,而且在我看来,最好不要依赖于如何构建因子。

rc <- factor(re.codes, levels=re.codes)
dat5 <- as.data.frame(lapply(dat, function(d) rc[d]))

编辑2:有趣的是,这似乎是lapply确实加速的情况。这个for循环要慢得多。

for(i in seq_along(dat)) {
  dat[[i]] <- rc[dat[[i]]]
}

编辑1:您还可以通过更精确地使用类型来加快速度。尝试使用任何解决方案(尤其是原始解决方案)将数据创建为整数,如下所示。有关详细信息,请参阅我之前的答案here

dat <- cbind(rep(1:5,50000),rep(5:1,50000),rep(c(1L,2L,4L,5L,3L),50000))

这也是一个好主意,因为从浮点转换为整数,就像在这里所有更快的解决方案中所做的那样,可能会产生意外行为,请参阅this question