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.

 

R-Subsetting

 

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 [.

  1. 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"

  1. 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"

  1. 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  225

head(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

 

Comentarios

0 Comments

Submit a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

This site uses Akismet to reduce spam. Learn how your comment data is processed.