En el post anterior hablamos sobre las estructuras de datos en R. Una vez que estemos familiarizados con estas, toca comenzar a ver cómo podemos extraer subconjuntos de datos (subsetting) y cómo pintar nuevos valores (assignment). Estos dos conceptos es fundamental que los dominemos para continuar con temas más avanzados.
En este post comenzaremos a viendo los operadores de subsetting, y luego como aplicarlos a cada tipo de estructura. Por último, veremos algunas aplicaciones prácticas.
Tabla de Contenidos
Operadores
Hay tres operadores que pueden ser usados para extraer subconjuntos de objetos en R:
- El operador [: siempre devuelve un objeto de la misma clase que el original, y puede ser usado para seleccionar múltiples elementos de un objeto.
- El operador [[: es usado para extraer elementos de una lista o de un data frame, y devuelve elementos simplificados.
- El operador $: es usado para extraer elementos de una lista o un data frame indicando su nombre.
Para comenzar a ver cómo utilizar estos operadores en la práctica vayamos viendo caso por caso.
Vectores
Los vectores son las estructuras más sencillas para comenzar.
x <- c("a.1", "s.2", "d.3", "f.4")
x "a.1" "s.2" "d.3" "f.4"
Hay tres formas de extraer subconjuntos de un vector utilizando el operadora [.
- Indicando con un vector la posición de cada uno de los elementos que deseamos extraer. Recordemos que en R el primer elemento de un vector lleva el índice 1.
x[c(3, 1)] # Extraemos el tercer y primer elemento
"d.3" "a.1"
x[c(1, 1)] # Extraemos dos veces el primer elemento
"a.1" "a.1"
x[c(3.5, 2.9)] # En caso de indica la posición con un número
decimal, este es truncado.
"d.3" "s.2"
- Podemos indicar un vector con todos los elementos que no queremos extraer anteponiendo un menos al vector de posiciones.
x[-c(3, 1)]
"s.2" "f.4"
- Con un vector booleano podemos indicar para cada posición si queremos recuperar el elemento (TRUE) o no (FALSE).
x[c(TRUE, TRUE, FALSE, FALSE)]
"a.1" "s.2"
Esta forma de recuperar elementos de un vector nos permite hacer cosas muy útiles como extraer solo aquellos elementos que cumplan alguna condición.
a <- 1:10
a
1 2 3 4 5 6 7 8 9 10
a > 3
FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
a[a > 3]
4 5 6 7 8 9 10
En caso de que alguno de los elementos del vector sea NA, entonces el elemento en tal posición resultará también NA.
x[c(TRUE, TRUE, NA, TRUE)]
"a.1" "s.2" NA "f.4"
Y en caso de indicar un vector con menor cantidad de posiciones, este se reciclará, es decir, se repetirá una y otra vez hasta alcanzar el tamaño del vector del cual buscamos extraer elementos.
x[c(TRUE, FALSE)]
"a.1" "d.3"
x[c(TRUE, FALSE, TRUE, FALSE)] # Equivalente a la expresión anterior
"a.1" "d.3"
Por otra parte, si no indicamos un vector de posiciones dentro de las llaves, se recuperará el mismo vector.
x[]
"a.1" "s.2" "d.3" "f.4"
En caso de indicar la posición 0 del vector, no se recuperarán elementos.
x[0]
character(0)
Si intentamos recuperar un elemento que no existe se nos devolverá un NA.
x[c(1,10, 15)]
"a.1" NA NA
En caso de los vectores nombrados, es decir, aquellos vectores en los que sus elementos tengan asignado un nombre, podemos recuperar tales elementos mediante sus nombres. Lo que haremos es indicar en el vector de posiciones el nombre de los elementos que queremos recuperar.
y <- setNames(x, c("primero", "segundo", "tercero", "cuarto"))
y
primero segundo tercero cuarto
"a.1" "s.2" "d.3" "f.4"
y[c("segundo", "tercero")]
segundo tercero
"s.2" "d.3"
Listas
En cuanto a las listas, se pueden extraer subconjuntos usando los tres operadores que mencionamos, cada uno con un propósito diferente.
x <- list(foo = 1:5, bar = 3.14)
x
$foo
[1] 1 2 3 4 5
$bar
[1] 3.14
x[[1]]
[1] 1 2 3 4 5
x[["bar"]]
[1] 3.14
x$bar
[1] 3.14
Una diferencia importante entre usar el [[ y el $ es que en este último debemos indicar literalmente el nombre del elemento a extraer, mientras que en el caso del [[ el nombre puede estar contenido en una variable.
x <- list(foo = 1:5, bar = 3.14, baz = "hello")
name <- "foo"
x[[name]]
1 2 3 4 5
x$name
NULL
x$foo
1 2 3 4 5
Matrices y Arrays
La forma más simple de extraer un subconjunto de una matriz o un array es generalizando lo que hicimos previamente con los vectores, es decir, indicando los indices con la forma (i, j). Lo más simple es que veamos esto con un ejemplo.
# Subsetting en Matrices
a <- matrix(1:9, nrow = 3)
colnames(a) <- c("A", "B", "C")
a
A B C
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
a[1:2, ]
A B C
[1,] 1 4 7
[2,] 2 5 8
a[c(T, F, T), c("B", "A")]
B A
[1,] 4 1
[2,] 6 3
a[0, -2]
[1] A C
# Subsetting en Arrays
x <- matrix(1:6, 2, 3)
x
[,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6
x[1, 2]
[1] 3
x[2, 1]
[1] 2
En caso de que deseemos recuperar una fila o una columna de una matriz podemos dejar el índice asociado a estos vació.
x[1, ] # Recuperamos la primer fila de la matriz
[1] 1 3 5
x[, 2] # Recuperamos la segunda columna de la matriz
[1] 3 4
Data frames
Los data frames poseen características tanto de listas como de matrices. Esto nos permite extraer subconjuntos de forma similar a como veníamos haciendo. Veamos esto con ejemplos:
head(mtcars)
mpg cyl disp hp drat wt qsec vs am gear carb Mazda_RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4 Mazda_RX4_Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4 Datsun_710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1 Hornet_4_Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 Hornet_Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2 Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1 mtcars$cyl [1] 6 6 4 6 8 6 8 4 4 6 6 8 8 8 8 8 8 4 4 4 4 8 8 8 8 4 4 4 8 6 8 4 mtcars$hp [1] 110 110 93 110 175 105 245 62 95 123 123 180 180 180 205 215 230 ...head(mtcars[mtcars$cyl==4,]) # Por fila
mpg cyl disp hp drat wt qsec vs am gear carb Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1 Merc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2 Merc 230 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2 Fiat 128 32.4 4 78.7 66 4.08 2.200 19.47 1 1 4 1 Honda Civic 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2 Toyota Corolla 33.9 4 71.1 65 4.22 1.835 19.90 1 1 4 1
head(mtcars[c(1, 3)]) # Por columna, por índice, como lista
mpg disp Mazda RX4 21.0 160 Mazda RX4 Wag 21.0 160 Datsun 710 22.8 108 Hornet 4 Drive 21.4 258 Hornet Sportabout 18.7 360 Valiant 18.1 225head(mtcars[,c("mpg", "disp", "cyl")]) # Por columna, por nombre, como matriz
mpg disp cyl Mazda RX4 21.0 160 6 Mazda RX4 Wag 21.0 160 6 Datsun 710 22.8 108 4 Hornet 4 Drive 21.4 258 6 Hornet Sportabout 18.7 360 8 Valiant 18.1 225 6
En caso de seleccionar una única columna, hay una diferencia importante entre seleccionar como lista o como matriz. En el primer caso, la columna que seleccionamos la obtenemos como un data frame, mientras que en el segundo caso como un vector.
str(mtcars["cyl"]) # Como lista -> obtenemos data frame
'data.frame': 32 obs. of 1 variable:
$ cyl: num 6 6 4 6 8 6 8 4 4 6 ...
str(mtcars[, "cyl"]) # Como matriz -> obtenemos vector
num [1:32] 6 6 4 6 8 6 8 4 4 6 ...
Una consideración a tener en cuenta es que mediante el operador $ no se puede acceder a una columna mediante una variable. Si tenemos almacenado en una variable el nombre la columna que deseamos recuperar debemos utilizar el [[.
col_name <- "mpg" mtcars$col_name NULL mtcars[[col_name]] [1] 21.0 21.0 22.8 21.4 18.7 18.1 14.3 24.4 22.8 ...
Otra diferencia importante entre el $ y el [[, es que el primero acepta coincidencias parciales (partial matching), mientras que el segundo no.
mtcars$m # La única columna cuyo nombre empieza por 'm' es
'mpg'. Entonces esta es devuelva
[1] 21.0 21.0 22.8 21.4 18.7 18.1 14.3 24.4 22.8 ...
Modificación de estructuras
Los operadores que hemos visto hasta aquí pueden realizar modificaciones en la estructura de los datos devueltos. Es decir, estos operadores los aplicamos sobre cierta estructura de datos, pero no necesariamente la estructura devuelta es la misma que la original. A este concepto en ingles se lo denomina simplifying & preserving subsetting.
Cuando un operador simplifica un resultado, la estructura de este es la más simple posible que pueda representar al resultado. En cambio, los operadores que conservan la estructura de datos, devuelve datos con la misma estructura que el objeto original. Este último caso es el más aconsejable a utilizar cuando se realicen scripts dado que nos aseguramos que la salida de una operación será siempre del mismo tipo.
No hay operadores que conserven las estructuras, y otros que las simplifiquen, sino que esto depende del tipo de objeto sobre el que se aplican. En la siguiente tabla se resumen los comportamientos para los casos vistos.
Simplifica | Conserva | |
---|---|---|
vector | x[[1]] | x[1] |
lista | x[[1]] | x[1] |
factor | x[1:4, drop = T] | x[1:4] |
array | x[1, ] or x[, 1] | x[1, , drop = F] or x[, 1, drop = F] |
data frame | x[, 1] or x[[1]] | x[, 1, drop = F] or x[1] |
Como dijimos, en los casos que se conserva la estructura de datos, esta es la misma para la salida que para la entrada. En el caso que se simplifique, esta varia. Veamos unos ejemplos para visualizar esto.
# Vectores x <- c(a = 1, b = 2) x[1] a 1 x[[1]] [1] 1 # Listas y <- list(a = 1, b = 2) str(y[1]) List of 1 $ a: num 1 str(y[[1]]) num 1 # Factor z <- factor(c("a", "b")) z[1] [1] a Levels: a b z[1, drop = TRUE] [1] a Levels: a # Matrix, array a <- matrix(1:4, nrow = 2) a[1, , drop = FALSE] [,1] [,2] [1,] 1 3 a[1, ] [1] 1 3 # Data frame df <- data.frame(a = 1:2, b = 1:2) str(df[1]) 'data.frame': 2 obs. of 1 variable: $ a: int 1 2 str(df[[1]]) int [1:2] 1 2 str(df[, "a", drop = FALSE]) 'data.frame': 2 obs. of 1 variable: $ a: int 1 2 str(df[, "a"]) int [1:2] 1 2
Subsetting y asignación de valores
Todas las operaciones que hemos visto hasta el momento nos permiten extraer valores de los objetos que tenemos en el espacio de trabajo en R. Si en cambio, lo que queremos es modificar estos datos, simplemente debemos utilizar los mismos operadores, pero invirtiendo el orden de asignación. Veamos unos ejemplos para que quede claro.
# Para el caso de vectores x <- 1:5 x[c(1, 2)] <- 8:9 x [1] 8 9 3 4 5 x[-1] <- 4:1 # La longitud de ambos ambos miembros de la asignación debe ser igual x [1] 8 4 3 2 1 x[c(1, 1)] <- 2:3 # En caso de pintar más de una vez en la misma posición, no se generará una error x [1] 3 4 3 2 1 x[c(1, NA)] <- c(1, 2) # Si intentamos pintar indicando la posición con un NA, nos saldrá un error Error in x[c(1, NA)] <- c(1, 2) : NAs are not allowed in subscripted assignments x[c(T, F, NA)] <- 1 # Pero sí podemos combinar un índice lógico con un NA, en tal caso, se omitirá reemplazar el valor en la posición x [1] 1 4 3 1 1
Casos de aplicación
En esta sección vamos a ver algunas aplicaciones practicas de los métodos de subsetting.
Muestras aleatorias
En muchas situaciones es preferible trabajar con muestras de un conjunto, que con el conjunto entero. Casos de estos iremos viendo en próximos post. De momento, quedémonos con la idea de que tomar muestras de un objeto es algo que realizaremos con frecuencia.
Entonces, existen funciones que se ocupan de simplificar esta tarea, como por ejemplo sample_n del paquete dplyr. No obstante, siempre es bueno saber realizar este procedimiento utilizando los operadores de subsetting. Vamos a ver como hacer esto para el caso de los data frames, que es lo más habitual.
# Primero, como muestrear un vector datos <- 1:15 datos [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # La función 'sample' toma un vector y extraer una muestra aletoria, de tamaño 'size'. Con el argumento 'replace' se elije si se tomar elementos repetido. Para más información mirar la documentación, '?sample' sample(x = datos, size = 5, replace = F) [1] 2 9 8 1 7 sample(x = datos, size = 5, replace = F) [1] 11 3 14 15 13 sample(x = datos, size = 5, replace = F) [1] 9 2 1 3 13 # Y ahora como muestrear un data frame df <- data.frame(x = rep(1:3, each = 2), y = 6:1, z = letters[1:6]) df x y z 1 1 6 a 2 1 5 b 3 2 4 c 4 2 3 d 5 3 2 e 6 3 1 f nrow(df) # La función 'nrow' devuelve el número de filas de un data frame [1] 6 set.seed(88) # Configuramos la semilla para que las muestra aleatoria sea reproducible df[sample(nrow(df)), ] # Reordenamos el data frame. La función 'sample' por defecto tiene el argumento 'replace' a FALSE. x y z 3 2 4 c 1 1 6 a 6 3 1 f 2 1 5 b 4 2 3 d 5 3 2 e df[sample(nrow(df), 3), ] # Tomamos tres aleatorias filas, sin repetición, del data frame. x y z 1 1 6 a 4 2 3 d 3 2 4 c
Lookup tables
Una lookup table es una estructura, típicamente un vector, que contiene datos precalculados de operaciones. De este modo, permite ahorra tiempo de procesamiento al ya tener guardado los resultados de ciertas operaciones. De momento, con esta idea nos alcanza. Si quieren leer más sobre esto pueden ir acá. Veamos un ejemplo:
x <- c("h", "m", "x", "m", "m", "h", "h") # Valores a hallar (índices)
lookup <- c(h = "Hombre", m = "Mujer", x = NA) # Vector con los valores precalculados
unname(lookup[x]) # Resultado de la búsqueda
[1] "Hombre" "Mujer" NA "Mujer" "Mujer" "Hombre" "Hombre"
Ordenando vectores
Para ordenar vectores, nos podemos ayudar mediante la función order de la siguiente forma.
x <- c("mauricio", "carlos", "marcos") order(x) [1] 2 3 1 x[order(x)] [1] "carlos" "marcos" "mauricio"
En caso de que deseemos invertir el orden, podemos indicar el argumento decreasing = TRUE de la función order.
Eliminar columnas de un data frame
En caso de que deseemos eliminar una columnas de un data frame, hay dos caminos que podemos seguir.
El primer es mediante el operador $ de la siguiente forma: df$variable <- NULL.
df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3]) df x y z 1 1 3 a 2 2 2 b 3 3 1 c df$z <- NULL # Eliminamos la columna z del data frame df x y 1 1 3 2 2 2 3 3 1
Y la otra alternativa es con el operador [.
df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3]) df x y z 1 1 3 a 2 2 2 b 3 3 1 c df[c("x", "y")] # Seleccionamos las columnas que queremos conservar x y 1 1 3 2 2 2 3 3 1 df[setdiff(names(df), "z")] # o seleccionamos las que queremos quitar x y 1 1 3 2 2 2 3 3 1
Selección de filas en función de condiciones (logical subsetting)
Continuando con la idea anterior, podemos fácilmente combinar condiciones de múltiples columnas para quedarnos con las filas (o registros) que cumplan las condiciones buscadas. Para quienes estén familiarizados con SQL, esto sería similar a poner condiciones en el where para quedarnos con los registros que deseamos. Entonces, veamos con unos ejemplos como hacer esto.
mtcars[mtcars$mpg > 25 & mtcars$disp > 75, ] mpg cyl disp hp drat wt qsec vs am gear carb Fiat 128 32.4 4 78.7 66 4.08 2.200 19.47 1 1 4 1 Honda Civic 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2 Fiat X1-9 27.3 4 79.0 66 4.08 1.935 18.90 1 1 4 1 Porsche 914-2 26.0 4 120.3 91 4.43 2.140 16.70 0 1 5 2 Lotus Europa 30.4 4 95.1 113 3.77 1.513 16.90 1 1 5 2 mtcars[mtcars$mpg > 25 & mtcars$disp > 80, ] mpg cyl disp hp drat wt qsec vs am gear carb Porsche 914-2 26.0 4 120.3 91 4.43 2.140 16.7 0 1 5 2 Lotus Europa 30.4 4 95.1 113 3.77 1.513 16.9 1 1 5 2
Una alternativa que podemos utilizar y mejora la legibilidad del código es la función subset.
subset(mtcars, mpg > 25) mpg cyl disp hp drat wt qsec vs am gear carb Fiat 128 32.4 4 78.7 66 4.08 2.200 19.47 1 1 4 1 Honda Civic 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2 Toyota Corolla 33.9 4 71.1 65 4.22 1.835 19.90 1 1 4 1 Fiat X1-9 27.3 4 79.0 66 4.08 1.935 18.90 1 1 4 1 Porsche 914-2 26.0 4 120.3 91 4.43 2.140 16.70 0 1 5 2 Lotus Europa 30.4 4 95.1 113 3.77 1.513 16.90 1 1 5 2 subset(mtcars, mpg > 25 & disp > 80) mpg cyl disp hp drat wt qsec vs am gear carb Porsche 914-2 26.0 4 120.3 91 4.43 2.140 16.7 0 1 5 2 Lotus Europa 30.4 4 95.1 113 3.77 1.513 16.9 1 1 5 2
Excelente guia me ayudo mucho
Wonderful site you have here but I was wondering if you knew of any discussion boards that
cover the same topics talked about in this article?
I’d really like to be a part of online community where I can get advice from other experienced individuals that share the same interest.
If you have any suggestions, please let me know. Thanks a
lot!