如何向量化R中的strsplit函数?

15

在创建使用strsplit的函数时,向量输入不会按预期工作,并且需要使用sapply。这是由于strsplit生成的列表输出。是否有一种矢量化的方法 - 即函数为输入的每个元素产生正确的列表元素?

例如,计算字符向量中单词长度:

words <- c("a","quick","brown","fox")

> length(strsplit(words,""))
[1] 4 # The number of words (length of the list)

> length(strsplit(words,"")[[1]])
[1] 1 # The length of the first word only

> sapply(words,function (x) length(strsplit(x,"")[[1]]))
a quick brown   fox 
1     5     5     3 
# Success, but potentially very slow
理想情况下,可以使用类似于length(strsplit(words,"")[[.]])的方式,其中.被解释为输入向量中相关部分。
1个回答

21

通常情况下,你应该尝试使用向量化函数来开始处理。如果可能的话,尽量避免使用strsplit,因为它通常需要之后进行某种形式的迭代(这会更慢)。在你的例子中,你应该使用nchar代替:

> nchar(words)
[1] 1 5 5 3

更一般地,利用strsplit返回列表的事实,并使用lapply

> as.numeric(lapply(strsplit(words,""), length))
[1] 1 5 5 3

或者使用来自plyrl*ply家族函数。例如:

> laply(strsplit(words,""), length)
[1] 1 5 5 3

编辑:

为了纪念布卢姆之日,我决定使用乔伊斯的《尤利西斯》来测试这些方法的性能:

joyce <- readLines("http://www.gutenberg.org/files/4300/4300-8.txt")
joyce <- unlist(strsplit(joyce, " "))

现在我已经有了所有的单词,我们可以进行计数:

> # original version
> system.time(print(summary(sapply(joyce, function (x) length(strsplit(x,"")[[1]])))))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   3.000   4.000   4.666   6.000  69.000 
   user  system elapsed 
   2.65    0.03    2.73 
> # vectorized function
> system.time(print(summary(nchar(joyce))))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   3.000   4.000   4.666   6.000  69.000 
   user  system elapsed 
   0.05    0.00    0.04 
> # with lapply
> system.time(print(summary(as.numeric(lapply(strsplit(joyce,""), length)))))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   3.000   4.000   4.666   6.000  69.000 
   user  system elapsed 
    0.8     0.0     0.8 
> # with laply (from plyr)
> system.time(print(summary(laply(strsplit(joyce,""), length))))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   3.000   4.000   4.666   6.000  69.000 
   user  system elapsed 
  17.20    0.05   17.30
> # with ldply (from plyr)
> system.time(print(summary(ldply(strsplit(joyce,""), length))))
       V1        
 Min.   : 0.000  
 1st Qu.: 3.000  
 Median : 4.000  
 Mean   : 4.666  
 3rd Qu.: 6.000  
 Max.   :69.000  
   user  system elapsed 
   7.97    0.00    8.03 

矢量化函数和lapply比原始的sapply版本要快得多。所有解决方案都返回相同的答案(如摘要输出所示)。

显然,最新版本的plyr更快(这是使用稍旧版本的情况)。


谢谢Shane,但是我并没有得到我所做的相同结果。这是Verhoeff检查位方案的一种实现方式。我已经修改了我的函数以与上述实现兼容,但对于一个100,000长向量的输入,我从第一个得到了一个8个元素的列表,而从第二个得到了一个8个元素的向量(8是向量元素最可能的长度)。 - James
顺便提一下,我刚在R 2.11.1中安装了plyr版本0.1.9,并且与上述情况的时间相似。 - Shane
@Shane:是的,我在调用列表时错误地对其进行了索引。现在它可以工作了,但是lapply的时间并没有比sapply好多少。算法需要按顺序处理拆分的数字,所以这可能是问题的原因。 - James
@Shane:那不完全正确。在某些情况下,lapply和sapply实际上可以进行优化。apply()通常很快。如果您像这里使用lapply一样使用sapply,您可以使性能更接近。我为此计时了一个'for'循环,它与此处使用的sapply接近,但是如果您像当前的lapply函数一样重写sapply函数,则sapply比'for'快两倍。(即使是plyr例程也比for循环慢得难以理解) - John
1
plyr的慢速问题已在devel版本中得到修复 - 但是在处理时间个别应用程序占主导地位的更复杂问题时,plyr通常更有用。 - hadley
显示剩余3条评论

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