新的dataframe列作为另一个的函数(摘要)对我不起作用

时间:2015-09-08 18:45:54

标签: r data.table digest

我想创建一个新的计算列(另一列文本的摘要)。为了重现,我创建了一个df作为可重现的例子:

df <- data.frame(name = replicate(1000, paste(sample(LETTERS, 20, replace=TRUE), collapse="")),stringsAsFactors=FALSE)

> head(df,3)
              name
1 ZKBOZVFKNJBRSDWTUEYR
2 RQPHUECABPQZLKZPTFLG
3 FTBVBEQTRLLUGUVHDKAY

现在我想要一个第二列,其中包含&#39; name&#39;的摘要。每一行的col 这非常有效,但速度很慢(每个md5都不同,它是名称列的相应摘要):

> df$md5 <- sapply(df$name, digest)   
> head(df, 3)
              name                              md5
1 ZKBOZVFKNJBRSDWTUEYR b8d93a9fe6cefb7a856e79f54bac01f2
2 RQPHUECABPQZLKZPTFLG 52f6acbd939df27e92232904ce094053
3 FTBVBEQTRLLUGUVHDKAY a401a8bc18f0cb367435b77afd353078

但是这个(使用dplyr)不起作用,我不明白为什么:md5对于每一行都是相同的!实际上它是完整的df $名称的摘要,包括所有行。请有人向我解释一下吗?

> df <- mutate(df, md5=digest(name))
> head(df, 3)
                  name                              md5
1 ZKBOZVFKNJBRSDWTUEYR 10aa31791d0b9288e819763d9a41efd8
2 RQPHUECABPQZLKZPTFLG 10aa31791d0b9288e819763d9a41efd8
3 FTBVBEQTRLLUGUVHDKAY 10aa31791d0b9288e819763d9a41efd8

再次,如果我采用数据表的方式,似乎使用新变量的标准方法不起作用:

> dt <- data.table(df)
> dt[, md5:=digest(name)]  
> head(dt,3)
                   name                              md5
1: ZKBOZVFKNJBRSDWTUEYR 10aa31791d0b9288e819763d9a41efd8
2: RQPHUECABPQZLKZPTFLG 10aa31791d0b9288e819763d9a41efd8
3: FTBVBEQTRLLUGUVHDKAY 10aa31791d0b9288e819763d9a41efd8

如果我强迫分组,那么它再次起作用(但很慢):

> dt[,md5:=digest(name), by=name]   
> head(dt, 3)
                   name                              md5
1: ZKBOZVFKNJBRSDWTUEYR b8d93a9fe6cefb7a856e79f54bac01f2
2: RQPHUECABPQZLKZPTFLG 52f6acbd939df27e92232904ce094053
3: FTBVBEQTRLLUGUVHDKAY a401a8bc18f0cb367435b77afd353078

我也测试了tapply并且工作(创建一个因素,但我的真实数据为数百万行,而且非常慢)。

然后,首先,有人可以向我解释为什么dplyr mutate没有采用每行的值来计算摘要以及为什么数据表符号会发生同样的想法(除非我分组)?

第二,有没有更快的方法来计算所有行的摘要?

2 个答案:

答案 0 :(得分:3)

考虑到你有一个非常大的数据集,最好在较大的数据集上测试不同的方法(对于这个例子,我使用100000行,更大的数据集在我的系统上需要很长时间):

df <- data.frame(name = replicate(1e5, paste(sample(LETTERS, 20, replace=TRUE), collapse="")), stringsAsFactors=FALSE)

首先,让我们考虑几种方法:

# base R
df$md5 <- sapply(df$name, digest)

# data.table (grouping by name, based on the assumption that all names are unique)
dt[, md5:=digest(name), name]

# data.table with a unique identifier for each row
dt[,indx:=.I][, md5:=digest(name), indx]

# dplyr (grouping by name, based on the assumption that all names are unique)
df %>% group_by(name) %>% mutate(md5=digest(name))

# dplyr with rowwise (from the other answer)
df %>% rowwise() %>% mutate(md5=digest(name))

其次,测试哪个appraoch最快:

library(rbenchmark)
benchmark(replications = 10, order = "elapsed", columns = c("test", "elapsed", "relative"),
          baseR = df$md5 <- sapply(df$name, digest),
          dtbl1 = dt[, md5:=digest(name), name],
          dtbl2 = dt[,indx:=.I][, md5:=digest(name), indx],
          dplyr = df %>% group_by(name) %>% mutate(md5=digest(name)),
          rowwi = df %>% rowwise() %>% mutate(md5=digest(name)))

给出:

   test elapsed relative
2 dtbl1  77.878    1.000
3 dtbl2  78.343    1.006
1 baseR  81.399    1.045
5 rowwi 118.799    1.525
4 dplyr 129.748    1.666

因此,坚持基础R解决方案根本不是一个糟糕的选择。我怀疑它真正的数据集速度慢的原因可能是digest函数,而不是某个包/函数的错误行为。

答案 1 :(得分:2)

获得相同md5值的原因是digest函数不是矢量化函数。要解决此问题,请在mutate之前放置rowwise,如下所示:

df <- data.frame(name = replicate(1000, paste(sample(LETTERS, 20, replace=TRUE), collapse="")),stringsAsFactors=FALSE)
ptm <- proc.time()
df %>% rowwise() %>% mutate(md5=digest(name)) %>% print(n=3)

1  SSYNAIPPMBNICTXCTZMH cf06eaeab2a4b1b3f0fb964e91867702
2  XAFNBFYOXSDIFSSCGKKX 28cb7f90ac14f4a2ee5743a1dce91ac7
3  TMWBHOHWVDSRUPBGKYGS a248a7eb31657555b2bf8b463b7e3ce3
..                  ...                              ...

proc.time() - ptm
user  system elapsed 
0.09    0.00    0.09 

至于速度,你可以看到它只占我桌面的1/10秒。