Cómo vectorizar R strsplit?
-
27-09-2019 - |
Pregunta
Al crear funciones que el uso strsplit
, entradas vector no se comportan como se desee, y las necesidades sapply
a utilizar. Esto se debe a la salida de la lista que strsplit
produce. ¿Hay una manera de vectorizar el proceso - es decir, la función produce el elemento correcto en la lista para cada uno de los elementos de la entrada
Por ejemplo, para contar las longitudes de las palabras en un vector de caracteres:
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 así como length(strsplit(words,"")[[.]])
donde .
se interpreta como el ser la parte relevante de vector de entrada.
Solución
En general, usted debe tratar de utilizar una función vectorizado para empezar. Usando strsplit
con frecuencia requerirá algún tipo de iteración después (que será más lento), lo que debería evitar si es posible. En su ejemplo, se debe utilizar en lugar nchar
:
> nchar(words)
[1] 1 5 5 3
De forma más general, se aprovechan del hecho de que strsplit
devuelve una lista y el uso lapply
:
> as.numeric(lapply(strsplit(words,""), length))
[1] 1 5 5 3
O más use una función de la familia l*ply
de plyr
. Por ejemplo:
> laply(strsplit(words,""), length)
[1] 1 5 5 3
Editar:
En honor a Bloomsday , decidí probar el rendimiento de estos enfoques utilizando el Ulises de Joyce:
joyce <- readLines("http://www.gutenberg.org/files/4300/4300-8.txt")
joyce <- unlist(strsplit(joyce, " "))
Ahora que tengo todas las palabras, podemos hacer nuestros sentidos:
> # 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
La función vectorizado y lapply
son considerablemente más rápido que la versión original sapply
. Todas las soluciones de devolver la misma respuesta (como se ve por el resumen de la salida).
Al parecer, la última versión del plyr
es más rápido (esto es utilizando una versión ligeramente mayores).