Curso
@drsimonj te trae cinco trucos sencillos que siempre comparto con otros usuarios de R para mejorar su código.
1. Más divertido secuenciar desde 1
La próxima vez que uses el operador de dos puntos para crear una secuencia desde 1 como 1:n, prueba con seq().
# Sequence a vector
x <- runif(10)
seq(x)
#> [1] 1 2 3 4 5 6 7 8 9 10
# Sequence an integer
seq(nrow(mtcars))
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#> [24] 24 25 26 27 28 29 30 31 32
El operador de dos puntos puede dar resultados inesperados que te creen todo tipo de problemas sin que te des cuenta. Fíjate en lo que ocurre cuando quieres secuenciar la longitud de un vector vacío:
# Empty vector
x <- c()
1:length(x)
#> [1] 1 0
seq(x)
#> integer(0)
Además, esto te ahorra tener que usar funciones como length(). Cuando se aplica a un objeto con cierta longitud, seq() creará automáticamente una secuencia de 1 hasta la longitud del objeto.
2. vector() para lo que sueles c()
La próxima vez que crees un vector vacío con c(), intenta sustituirlo por vector("type", length).
# A numeric vector with 5 elements
vector("numeric", 5)
#> [1] 0 0 0 0 0
# A character vector with 3 elements
vector("character", 3)
#> [1] "" "" ""
Hacerlo mejora el uso de memoria y aumenta la velocidad. A menudo sabes de antemano qué tipo de valores irá en un vector y qué longitud tendrá. Usar c() obliga a R a calcular ambas cosas poco a poco. Dale un empujón con vector().
Un buen ejemplo es en un bucle for. Mucha gente escribe bucles declarando un vector vacío y haciéndolo crecer con c() así:
x <- c()
for (i in seq(5)) {
x <- c(x, i)
}
#> x at step 1 : 1
#> x at step 2 : 1, 2
#> x at step 3 : 1, 2, 3
#> x at step 4 : 1, 2, 3, 4
#> x at step 5 : 1, 2, 3, 4, 5
En su lugar, predefine el tipo y la longitud con vector(), y asigna por índice, así:
n <- 5
x <- vector("integer", n)
for (i in seq(n)) {
x[i] <- i
}
#> x at step 1 : 1, 0, 0, 0, 0
#> x at step 2 : 1, 2, 0, 0, 0
#> x at step 3 : 1, 2, 3, 0, 0
#> x at step 4 : 1, 2, 3, 4, 0
#> x at step 5 : 1, 2, 3, 4, 5
Aquí tienes una comparación rápida de velocidad:
n <- 1e5
x_empty <- c()
system.time(for(i in seq(n)) x_empty <- c(x_empty, i))
#> user system elapsed
#> 15.238 2.327 17.650
x_zeros <- vector("integer", n)
system.time(for(i in seq(n)) x_zeros[i] <- i)
#> user system elapsed
#> 0.007 0.000 0.007
Más que convincente, ¿no?
Domina tus habilidades de datos con DataCamp
Más de 10 millones de personas aprenden Python, R, SQL y otras habilidades tecnológicas con nuestros cursos prácticos elaborados por expertos del sector.

3. Di adiós a which()
La próxima vez que uses which(), mejor evítalo. A menudo se usa which() para obtener índices a partir de una condición booleana y luego seleccionar los valores en esos índices. No hace falta.
Obtener los elementos del vector mayores que 5:
x <- 3:7
# Usando which (innecesario)
x[which(x > 5)]
#> [1] 6 7
# Sin which
x[x > 5]
#> [1] 6 7
O contar cuántos valores son mayores que 5:
# Usando which
length(which(x > 5))
#> [1] 2
# Sin which
sum(x > 5)
#> [1] 2
¿Por qué deberías prescindir de which()? Muchas veces es innecesario y con vectores booleanos tienes todo lo que necesitas.
Por ejemplo, R te permite seleccionar elementos marcados como TRUE en un vector booleano:
condition <- x > 5
condition
#> [1] FALSE FALSE FALSE TRUE TRUE
x[condition]
#> [1] 6 7
Además, combinados con sum() o mean(), los vectores booleanos sirven para obtener el recuento o la proporción de valores que cumplen una condición:
sum(condition)
#> [1] 2
mean(condition)
#> [1] 0.4
which() te devuelve los índices de los valores TRUE:
which(condition)
#> [1] 4 5
Y aunque el resultado no es incorrecto, simplemente no es necesario. Por ejemplo, a menudo veo a gente combinar which() y length() para comprobar si alguno o todos los valores son TRUE. En su lugar, usa any() o all():
x <- c(1, 2, 12)
# Usando `which()` y `length()` para comprobar si algún valor es mayor que 10
if (length(which(x > 10)) > 0)
print("At least one value is greater than 10")
#> [1] "At least one value is greater than 10"
# Envolviendo un vector booleano con `any()`
if (any(x > 10))
print("At least one value is greater than 10")
#> [1] "At least one value is greater than 10"
# Usando `which()` y `length()` para comprobar si todos los valores son positivos
if (length(which(x > 0)) == length(x))
print("All values are positive")
#> [1] "All values are positive"
# Envolviendo un vector booleano con `all()`
if (all(x > 0))
print("All values are positive")
#> [1] "All values are positive"
Ah, y también te ahorra un poco de tiempo...
x <- runif(1e8)
system.time(x[which(x > .5)])
#> user system elapsed
#> 1.156 0.522 1.686
system.time(x[x > .5])
#> user system elapsed
#> 1.071 0.442 1.662
4. ¡factor a ese factor!
¿Alguna vez has eliminado valores de un factor y te has quedado con niveles antiguos que ya no existen? He visto todo tipo de formas creativas de lidiar con esto. La solución más simple suele ser volver a envolverlo con factor().
Este ejemplo crea un factor con cuatro niveles ("a", "b", "c" y "d"):
# A factor with four levels
x <- factor(c("a", "b", "c", "d"))
x
#> [1] a b c d
#> Levels: a b c d
plot(x)

Si eliminas todos los casos de un nivel ("d"), el nivel sigue registrado en el factor:
# Drop all values for one level
x <- x[x != "d"]
# ¡Pero aún tenemos este nivel!
x
#> [1] a b c
#> Levels: a b c d
plot(x)

Un método supersencillo para quitarlo es usar factor() de nuevo:
x <- factor(x)
x
#> [1] a b c
#> Levels: a b c
plot(x)
Esto suele ser una buena solución a un problema que saca de quicio a mucha gente. Evítate el dolor de cabeza y haz factor a ese factor.
5. Primero va el $, luego el poder
La próxima vez que quieras extraer valores de una columna de un data.frame donde las filas cumplan una condición, especifica primero la columna con $ y después las filas con [.
Imagina que quieres la potencia (hp) de los coches con 4 cilindros (cyl) usando el conjunto de datos mtcars. Puedes escribir cualquiera de estas dos:
# filas primero, columna después - no ideal
mtcars[mtcars$cyl == 4, ]$hp
#> [1] 93 62 95 66 52 65 97 66 91 113 109
# columna primero, filas después - mucho mejor
mtcars$hp[mtcars$cyl == 4]
#> [1] 93 62 95 66 52 65 97 66 91 113 109
El consejo es usar el segundo enfoque.
¿Y por qué?
Primer motivo: olvídate de la dichosa coma. Cuando especificas filas antes que columna, tienes que recordar la coma: mtcars[mtcars$cyl == 4,]$hp. Si especificas primero la columna, ahora te refieres a un vector y no necesitas la coma.
Segundo motivo: la velocidad. Probémoslo en un data frame más grande:
# Simulate a data frame...
n <- 1e7
d <- data.frame(
a = seq(n),
b = runif(n)
)
# filas primero, columna después - no ideal
system.time(d[d$b > .5, ]$a)
#> user system elapsed
#> 0.497 0.126 0.629
# columna primero, filas después - mucho mejor
system.time(d$a[d$b > .5])
#> user system elapsed
#> 0.089 0.017 0.107
Merece la pena, ¿verdad?
Aun así, si quieres pulir tus habilidades como ninja de data frames en R, te recomiendo aprender dplyr. Puedes ver una buena introducción en la web de dplyr o aprender a fondo con cursos online como Data Manipulation in R with dplyr de DataCamp.
Despedida
Gracias por leerme; espero que te haya sido útil.
Para estar al día de las últimas entradas del blog, sigue a @drsimonj en Twitter o escríbeme a drsimonjackson@gmail.com.
Si quieres el código con el que se ha generado este blog, echa un vistazo al repositorio blogR en GitHub.
Consulta nuestro primeros pasos con el Tidyverse: tutorial.
