Como vetorizar r strsplit?
-
27-09-2019 - |
Pergunta
Ao criar funções que usam strsplit
, entradas vetoriais não se comportam como desejado, e sapply
precisa ser usado. Isso se deve à saída da lista que strsplit
produz. Existe uma maneira de vetorizar o processo - ou seja, a função produz o elemento correto na lista para cada um dos elementos da entrada?
Por exemplo, para contar os comprimentos das palavras em um vetor de personagem:
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
Idealmente, algo como length(strsplit(words,"")[[.]])
Onde .
é interpretado como a parte relevante do vetor de entrada.
Solução
Em geral, você deve tentar usar uma função vetorizada para começar. Usando strsplit
Freqüentemente exigirá algum tipo de iteração posteriormente (que será mais lenta), então tente evitá -lo, se possível. No seu exemplo, você deve usar nchar
em vez de:
> nchar(words)
[1] 1 5 5 3
De maneira mais geral, aproveite o fato de que strsplit
Retorna uma lista e use lapply
:
> as.numeric(lapply(strsplit(words,""), length))
[1] 1 5 5 3
Ou então use um l*ply
função familiar de plyr
. Por exemplo:
> laply(strsplit(words,""), length)
[1] 1 5 5 3
Editar:
Em honra de Bloomsday, Decidi testar o desempenho dessas abordagens usando o Ulisses de Joyce:
joyce <- readLines("http://www.gutenberg.org/files/4300/4300-8.txt")
joyce <- unlist(strsplit(joyce, " "))
Agora que tenho todas as palavras, podemos fazer nossas contagens:
> # 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
A função vetorizada e lapply
são consideravelmente mais rápidos que o original sapply
versão. Todas as soluções retornam a mesma resposta (como visto pela saída de resumo).
Aparentemente a versão mais recente de plyr
é mais rápido (isso está usando uma versão ligeiramente mais antiga).