Haciendo subconjuntos de datos
Resumen
Enseñando: 35 min
Ejercicios: 15 minPreguntas
¿Cómo puedo trabajar con subconjuntos de datos en R?
Objectivos
Ser capaz de hacer subconjuntos de vectores, factores, matrices, listas y data frames.
Ser capaz de extraer uno o multiples elementos: por posición, por nombre, o usando operaciones de comparación.
Ser capaz de saltar y quitar elementos de diferentes estructuras de datos.
R dispone de muchas operaciones para generar subconjuntos. Dominarlas te permitirá hacer fácilmente operaciones muy complejas en cualquier dataset.
Existen seis maneras distintas por las cuales se puede hacer un subconjunto de datos de cualquier objeto, y existen tres operadores distintos para hacer subconjuntos para las diferentes estructuras de datos.
Empecemos con el caballito de batalla de R: un vector numérico.
x <- c(5.4, 6.2, 7.1, 4.8, 7.5)
names(x) <- c('a', 'b', 'c', 'd', 'e')
x
a b c d e
5.4 6.2 7.1 4.8 7.5
Vectores atómicos
En R, un vector puede contener palabras, números o valores lógicos. Estos son llamados vectores atómicos ya que no se pueden simplificar más.
Ya que creamos un vector ejemplo juguemos con él, ¿cómo podemos acceder a su contenido?
Accediendo a los elementos del vector usando sus índices
Para extraer elementos o datos de un vector podemos usar su índice correspondiente, empezando por uno:
x[1]
a
5.4
x[4]
d
4.8
No lo parece, pero el operador corchetes es una función. Para los vectores (y las matrices), esto significa “dame el n-ésimo elemento”.
También podemos pedir varios elementos al mismo tiempo:
x[c(1, 3)]
a c
5.4 7.1
O podemos tomar un rango del vector:
x[1:4]
a b c d
5.4 6.2 7.1 4.8
el operador :
crea una sucesión de números del valor a la izquierda hasta el de
la derecha.
1:4
[1] 1 2 3 4
c(1, 2, 3, 4)
[1] 1 2 3 4
También podemos pedir el mismo elemento varias veces:
x[c(1,1,3)]
a a c
5.4 5.4 7.1
Si pedimos por índices mayores a la longitud del vector, R regresará un valor faltante.
x[6]
<NA>
NA
<NA>
NA
Error: <text>:1:1: unexpected '<'
1: <
^
Este es un vector de longitud uno que contiene un NA
, cuyo nombre también es
NA
.
Si pedimos el elemento en el índice 0, obtendremos un vector vacío.
x[0]
named numeric(0)
La numeración de R comienza en 1
En varios lenguajes de programación (C y Python por ejemplo), el primer elemento de un vector tiene índice 0. En R, el primer elemento tiene el índice 1.
Saltando y quitando elementos
Si usamos un valor negativo como índice para un vector, R regresará cada elemento excepto lo que se ha especificado:
x[-2]
a c d e
5.4 7.1 4.8 7.5
También podemos saltar o no mostrar varios elementos:
x[c(-1, -5)] # or x[-c(1,5)]
b c d
6.2 7.1 4.8
Sugerencia: Orden de las operaciones
Un pequeño obstáculo para los novatos ocurre cuando tratan de saltar rangos de elementos de un vector. Es natural tratar de filtrar una sucesión de la siguiente manera:
x[-1:3]
Esto nos devuelve un error algo críptico:
Error in x[-1:3]: only 0's may be mixed with negative subscripts
Pero recuerda el orden de las operaciones.
:
es en realidad una función. Toma como primer elemento -1 y como segundo 3, por lo que se genera la sucesión de números:c(-1, 0, 1, 2, 3)
.La solución correcta sería empaquetar la llamada de la función dentro de paréntesis, de está manera el operador
-
se aplica al resultado:x[-(1:3)]
d e 4.8 7.5
Para quitar los elementos de un vector, será necesario que asignes el resultado de vuelta a la variable:
x <- x[-4]
x
a b c e
5.4 6.2 7.1 7.5
Desafío 1
Dado el siguiente código:
x <- c(5.4, 6.2, 7.1, 4.8, 7.5) names(x) <- c('a', 'b', 'c', 'd', 'e') print(x)
a b c d e 5.4 6.2 7.1 4.8 7.5
Encuentra al menos 3 comandos distintos que produzcan la siguiente salida:
b c d 6.2 7.1 4.8
Después de encontrar los tres comandos distintos, compáralos con los de tu vecino. ¿Tuvieron distintas estrategias?
Solución al desafío 1
x[2:4]
b c d 6.2 7.1 4.8
x[-c(1,5)]
b c d 6.2 7.1 4.8
x[c("b", "c", "d")]
b c d 6.2 7.1 4.8
x[c(2,3,4)]
b c d 6.2 7.1 4.8
Haciendo subconjuntos por nombre
Podemos extraer elementos usando sus nombres, en lugar de extraerlos por índice:
x <- c(a=5.4, b=6.2, c=7.1, d=4.8, e=7.5) # podemos nombrar un vector en la misma línea
x[c("a", "c")]
a c
5.4 7.1
Esta forma es mucho más segura para hacer subconjuntos: las posiciones de muchos elementos pueden cambiar a menudo cuando estamos creando una cadena de subconjuntos, ¡pero los nombres siempre permanecen iguales!
Creando subconjuntos usando operaciones lógicas
También podemos usar un vector con elementos lógicos para hacer subconjuntos:
x[c(FALSE, FALSE, TRUE, FALSE, TRUE)]
c e
7.1 7.5
Dado que los operadores de comparación (e.g. >
, <
, ==
) dan como resultado valores lógicos,
podemos usarlos para crear subconjuntos de manera mas sintética: la siguiente instrucción
tiene el mismo resultado que el anterior.
x[x > 7]
c e
7.1 7.5
Explicando un poco lo que sucedió, la instrucción x>7
, genera un vector
lógico c(FALSE, FALSE, TRUE, FALSE, TRUE)
y después éste selecciona
los elementos de x
correspondientes a los valores TRUE
.
Podemos usar ==
para imitar el método anterior de indexar con nombre
(recordemos que se usa ==
en vez de =
para comparar):
x[names(x) == "a"]
a
5.4
Sugerencia: Combinando condiciones lógicas
Muchas veces queremos combinar varios criterios lógicos. Por ejemplo, tal vez queramos encontrar todos los países en Asia o (en inglés or) Europe y (en inglés and) con esperanza de vida en cierto rango. Existen muchas operaciones para combinar vectores con elementos lógicos en R:
&
, el operador “lógico AND”: regresaTRUE
si tanto la derecha y la izquierda sonTRUE
.|
, el operador “lógico OR”: regresaTRUE
, si la derecha o la izquierda (o ambos) sonTRUE
.A veces encontrarás
&&
ỵ||
en vez de&
y|
. Los operadores de dos caracteres solo comparan los primeros elementos de cada vector e ignoran las demás elementos. En general no debes usar los operadores de dos caracteres en el análisis de datos; déjalos para la programación, i.e. para decir cuando se ejecutara una instrucción.
!
, el operador “lógico NOT”: convierteTRUE
aFALSE
yFALSE
aTRUE
. Puede negar una sola condición lógica (e.g.!TRUE
se vuelveFALSE
), o un vector logical (e.g.!c(TRUE, FALSE)
se vuelvec(FALSE, TRUE)
).Más aún, puedes comparar todos los elementos de un vector entre ellos usando la función
all
(que regresaTRUE
si todos los elementos del vector sonTRUE
) y la funciónany
(que regresaTRUE
si uno o más elementos del vector sonTRUE
).
Desafío 3
Dado el siguiente código:
x <- c(5.4, 6.2, 7.1, 4.8, 7.5) names(x) <- c('a', 'b', 'c', 'd', 'e') print(x)
a b c d e 5.4 6.2 7.1 4.8 7.5
Escribe un comando para crear el subconjunto de valores de
x
que sean mayores a 4 pero menores que 7.Solución al desafío 3
x_subset <- x[x<7 & x>4] print(x_subset)
a b d 5.4 6.2 4.8
Sugerencia: Nombres no únicos
Debes tener en cuenta que es posible que múltiples elementos en un vector tengan el mismo nombre. (Para un data frame, las columnas pueden tener el mismo nombre —aunque R intenta evitarlo— pero los nombres de las filas deben ser únicos). Considera estos ejemplos:
x <- 1:3 x
[1] 1 2 3
names(x) <- c('a', 'a', 'a') x
a a a 1 2 3
x['a'] # solo devuelve el primer valor
a 1
x[names(x) == 'a'] # devuelve todos los tres valores
a a a 1 2 3
Sugerencia: Obteniendo ayuda para los operadores
Recuerda que puedes obtener ayuda para los operadores empaquetándolos entre comillas:
help("%in%")
o?"%in%"
.
Saltarse los elementos nombrados
Saltarse o eliminar elementos con nombre es un poco más difícil. Si tratamos de omitir un elemento con nombre al negar la cadena, R se queja (de una manera un poco oscura) de que no sabe cómo tomar el valor negativo de una cadena:
x <- c(a=5.4, b=6.2, c=7.1, d=4.8, e=7.5) # comenzamos nuevamente nombrando un vector en la misma línea
x[-"a"]
Error in -"a": invalid argument to unary operator
Sin embargo, podemos usar el operador !=
(no igual) para construir un vector
con elementos lógicos, que es lo que nosotros queremos:
x[names(x) != "a"]
b c d e
6.2 7.1 4.8 7.5
Saltar varios índices con nombre es un poco más difícil. Supongamos
que queremos excluir los elementos "a"
y "c"
, entonces intentamos lo siguiente:
x[names(x)!=c("a","c")]
Warning in names(x) != c("a", "c"): longer object length is not a multiple
of shorter object length
b c d e
6.2 7.1 4.8 7.5
R hizo algo, pero también nos lanzó una advertencia que debemos atender
-¡y aparentemente nos dió la respuesta incorrecta!
(el elemento "c"
se encuentra todavía en el vector).
¿Entonces qué hizo el operador !=
en este caso? Esa es una excelente
pregunta.
Reciclando
Tomemos un momento para observar al operador de comparación en este código:
names(x) != c("a", "c")
Warning in names(x) != c("a", "c"): longer object length is not a multiple
of shorter object length
[1] FALSE TRUE TRUE TRUE TRUE
¿Por qué R devuelve TRUE
como el tercer elemento de este vector, cuando
names(x)[3] != "c"
es obviamente falso?. Cuando tú usas !=
, R trata
de comparar cada elemento de la izquierda con el correspondiente elemento
de la derecha. ¿Qué pasa cuando tu comparas dos elementos de diferentes
longitudes?
Cuando uno de los vectores es más corto que el otro, este se recicla:
En este caso R repite c("a", "c")
tantas veces como sea necesario
para emparejar names(x)
, i.e. tenemos c("a","c","a","c","a")
. Ya que
el valor reciclado "a"
no es igual a names(x)
, el valor de !=
es
TRUE
. En este caso, donde el vector de mayor longitud (5) no es múltiplo
del más pequeño (2), R lanza esta advertencia. Si
hubiéramos sido lo suficientemente desafortunados y names(x)
tuviese seis
elementos, R silenciosamente hubiera hecho las cosas incorrectas (i.e.,
no lo que deseábamos hacer). Esta regla de reciclaje puede introducir
bugs difíciles de encontrar.
La manera de hacer que R haga lo que en verdad queremos (emparejar cada uno
de los elementos del argumento de la izquierda con todos los elementos del
argumento de la derecha) es usando el operador %in%
. El operador %in%
toma cada uno de los elementos del argumento de la izquierda, en este caso
los nombres de x
, y pegunta, “¿este elemento ocurre en el segundo argumento?”
Aquí, como queremos excluir los valores, nosotros también necesitamos el
operador !
para cambiar la inclusión por una no inclusión:
x[! names(x) %in% c("a","c") ]
b d e
6.2 4.8 7.5
Desafío 2
Seleccionar elementos de un vector que empareje con cualquier valor de una lista es una tarea muy común en el análisis de datos. Por ejemplo, el data set de gapminder contiene las variables
country
ycontinent
, pero no incluye información de la escala. Supongamos que queremos extraer la información de el sureste de Asia: ¿cómo podemos escribir una operación que resulte en un vector lógico que seaTRUE
para todos los países en el sureste de Asia yFALSE
en otros casos?Supongamos que se tienen los siguientes datos:
seAsia <- c("Myanmar","Thailand","Cambodia","Vietnam","Laos") ## leer los datos de gapminder que bajamos en el episodio 2 gapminder <- read.csv("data/gapminder-FiveYearData.csv", header=TRUE) ## extraer la columna `country` de la **data frame** (veremos esto luego); ## convertir de factor a caracter; ## y quedarse solo con los elementos no repetidos countries <- unique(as.character(gapminder$country))
Existe una manera incorrecta (usando solamente
==
), la cual te dará una advertencia (warning); una manera enredada de hacerlo (usando los operadores lógicos==
y|
) y una manera elegante (usando%in%
). Prueba encontrar esas maneras y explica cómo funcionan (o no).Solución al desafío 2
- La manera incorrecta de hacer este problema es
countries==seAsia
. Esta lanza una advertencia ("In countries == seAsia : longer object length is not a multiple of shorter object length"
) y la respuesta incorrecta (un vector con todos los valoresFALSE
), ya que ninguno de los valores reciclados deseAsia
se emparejaron correctamente para coincidir con los valores decountry
.- La manera enredada (pero técnicamente correcta) de resolver este problema es
(countries=="Myanmar" | countries=="Thailand" | countries=="Cambodia" | countries == "Vietnam" | countries=="Laos")
(o
countries==seAsia[1] | countries==seAsia[2] | ...
). Esto da los valores correctos, pero esperamos que veas lo raro que se ve (¿qué hubiera pasado si hubiéramos querido seleccionar países de una lista mucho más larga?).
- La mejor manera de resolver este problema es
countries %in% seAsia
, la cual es la correcta y la más sencilla de escribir (y leer).
Manejando valores especiales
En algún momento encontraremos funciones en R que no pueden manejar valores faltantes, infinito o datos indefinidos.
Existen algunas funciones especiales que puedes usar para filtrar estos datos:
is.na
regresa todas las posiciones de un vector, matrix o data frame que contenganNA
(oNaN
)- de la misma manera,
is.nan
yis.infinite
hacen lo mismo paraNaN
eInf
. is.finite
regresa todas las posiciones de un vector, matrix o data frame que no contenganNA
,NaN
oInf
.na.omit
filtra todos los valores faltantes de un vector
Haciendo subconjuntos de factores
Habiendo explorado las distintas manera de hacer subconjuntos de vectores, ¿cómo podemos hacer subconjuntos de otras estructuras de datos?
Podemos hacer subconjuntos de factores de la misma manera que con los vectores.
f <- factor(c("a", "a", "b", "c", "c", "d"))
f[f == "a"]
[1] a a
Levels: a b c d
f[f %in% c("b", "c")]
[1] b c c
Levels: a b c d
f[1:3]
[1] a a b
Levels: a b c d
Saltar elementos no quita el nivel, incluso cuando no existan datos en esa categoría del factor:
f[-3]
[1] a a c c d
Levels: a b c d
Haciendo subconjuntos de matrices
También podemos hacer subconjuntos de matrices usando la función [
En este
caso toma dos argumentos: el primero se aplica a las filas y el segundo
a las columnas:
set.seed(1)
m <- matrix(rnorm(6*4), ncol=4, nrow=6)
m[3:4, c(3,1)]
[,1] [,2]
[1,] 1.12493092 -0.8356286
[2,] -0.04493361 1.5952808
Siempre puedes dejar el primer o segundo argumento vacío para obtener todas las filas o columnas respectivamente:
m[, c(3,4)]
[,1] [,2]
[1,] -0.62124058 0.82122120
[2,] -2.21469989 0.59390132
[3,] 1.12493092 0.91897737
[4,] -0.04493361 0.78213630
[5,] -0.01619026 0.07456498
[6,] 0.94383621 -1.98935170
Si quisieramos acceder a solo una fila o una columna, R automáticamente convertirá el resultado a un vector:
m[3,]
[1] -0.8356286 0.5757814 1.1249309 0.9189774
Si quieres mantener la salida como una matriz, necesitas especificar un tercer
argumento; drop = FALSE
:
m[3, , drop=FALSE]
[,1] [,2] [,3] [,4]
[1,] -0.8356286 0.5757814 1.124931 0.9189774
A diferencia de los vectores, si tratamos de acceder a una fila o columna fuera de la matriz, R arrojará un error:
m[, c(3,6)]
Error in m[, c(3, 6)]: subscript out of bounds
Sugerencia: Arrays de más dimensiones
Cuando estamos lidiando con arrays multi-dimensionales, cada uno de los argumentos de
[
corresponden a una dimensión. Por ejemplo,en un array 3D, los primeros tres argumentos corresponden a las filas, columnas y profundidad.
Como las matrices son vectores, podemos también hacer subconjuntos usando solo un argumento:
m[5]
[1] 0.3295078
Normalmente esto no es tan útil y muchas veces difícil de leer. Sin embargo es útil notar que las matrices están acomodadas en un formato column-major por defecto. Esto significa que los elementos del vector están acomodados por columnas:
matrix(1:6, nrow=2, ncol=3)
[,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6
Si quisieramos llenar una matriz por filas, usamos byrow=TRUE
:
matrix(1:6, nrow=2, ncol=3, byrow=TRUE)
[,1] [,2] [,3]
[1,] 1 2 3
[2,] 4 5 6
Podemos también hacer subconjuntos de las matrices usando los nombres de sus filas y de sus columnas en vez de usar sus índices.
Desafío 4
Dado el siguiente código:
m <- matrix(1:18, nrow=3, ncol=6) print(m)
[,1] [,2] [,3] [,4] [,5] [,6] [1,] 1 4 7 10 13 16 [2,] 2 5 8 11 14 17 [3,] 3 6 9 12 15 18
- ¿Cuál de los siguientes comandos extraerá los valores 11 y 14?
A.
m[2,4,2,5]
B.
m[2:5]
C.
m[4:5,2]
D.
m[2,c(4,5)]
Solución al desafío 4
D
Haciendo subconjuntos de listas
Ahora introduciremos un nuevo operador para hacer subconjuntos. Existen tres
funciones para hacer subconjuntos de listas. Ya las hemos visto cuando
aprendimos los vectores atómicos y las matrices: [
, [[
y $
.
Usando [
siempre se obtiene una lista. Si quieres un subconjunto de una
lista, pero no quieres extraer un elemento, entonces probablemente usarás
[
.
xlist <- list(a = "Software Carpentry", b = 1:10, data = head(iris))
xlist[1]
$a
[1] "Software Carpentry"
Esto regresa una lista de un elemento.
Podemos hacer subconjuntos de elementos de la lista de la misma manera que
con los vectores atómicos usando [
. Las operaciones de comparación sin
embargo no funcionan, ya que no son recursivas, estas probarán la
condición en la estructura de datos de los elementos de la lista,
y no en los elementos individuales de dichas estructuras de datos.
xlist[1:2]
$a
[1] "Software Carpentry"
$b
[1] 1 2 3 4 5 6 7 8 9 10
Para extraer elementos individuales de la lista, tendrás que hacer uso de
la función doble corchete: [[
.
xlist[[1]]
[1] "Software Carpentry"
Nota que ahora el resultados es un vector, no una lista.
No puedes extraer más de un elemento al mismo tiempo:
xlist[[1:2]]
Error in xlist[[1:2]]: subscript out of bounds
Tampoco puedes usarlo para saltar elementos:
xlist[[-1]]
Error in xlist[[-1]]: attempt to select more than one element in get1index <real>
Pero tú puedes usar los nombres para hacer subconjuntos y extraer elementos:
xlist[["a"]]
[1] "Software Carpentry"
La función $
es una manera abreviada para extraer elementos por nombre:
xlist$data
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5.0 3.6 1.4 0.2 setosa
6 5.4 3.9 1.7 0.4 setosa
Desafío 5
Dada la siguiente lista:
xlist <- list(a = "Software Carpentry", b = 1:10, data = head(iris))
Usando tu conocimiento para hacer subconjuntos de listas y vectores, extrae el número 2 de
xlist
. Pista: el número 2 está contenido en el elemento “b” de la lista.Solución al desafío 5
xlist$b[2]
[1] 2
xlist[[2]][2]
[1] 2
xlist[["b"]][2]
[1] 2
Desafío 6
Dado un modelo lineal:
mod <- aov(pop ~ lifeExp, data=gapminder)
Extrae los grados de libertad residuales (pista:
attributes()
te puede ayudar)Solución del desafío 6
attributes(mod) ## `df.residual` es uno de los nombres de `mod`
mod$df.residual
Data frames
Recordemos que las data frames son listas, por lo que aplican reglas similares. Sin embargo estos también son objetos de dos dimensiones:
[
con un argumento funcionará de la misma manera que para las listas,
donde cada elemento de la lista corresponde a una columna. El objeto devuelto
será una data frame:
head(gapminder[3])
pop
1 8425333
2 9240934
3 10267083
4 11537966
5 13079460
6 14880372
Similarmente, [[
extraerá una sola columna:
head(gapminder[["lifeExp"]])
[1] 28.801 30.332 31.997 34.020 36.088 38.438
Con dos argumentos, [
se comporta de la misma manera que para las matrices:
gapminder[1:3,]
country year pop continent lifeExp gdpPercap
1 Afghanistan 1952 8425333 Asia 28.801 779.4453
2 Afghanistan 1957 9240934 Asia 30.332 820.8530
3 Afghanistan 1962 10267083 Asia 31.997 853.1007
Si nuestro subconjunto es una sola fila, el resultado será una data frame (porque los elementos son de distintos tipos):
gapminder[3,]
country year pop continent lifeExp gdpPercap
3 Afghanistan 1962 10267083 Asia 31.997 853.1007
Pero para una sola columna el resultado será un vector (esto puede cambiarse
con el tercer argumento, drop = FALSE
).
Desafío 7
Corrige cada uno de los siguientes errores para hacer subconjuntos de data frames:
Extraer observaciones colectadas en el año 1957
gapminder[gapminder$year = 1957,]
Extraer todas las columnas excepto de la 1 a la 4
gapminder[,-1:4]
Extraer las filas donde la esperanza de vida es mayor a 80 años
gapminder[gapminder$lifeExp > 80]
Extraer la primer fila, y la cuarta y quinta columna (
lifeExp
ygdpPercap
).gapminder[1, 4, 5]
Avanzado: extraer las filas que contienen información para los años 2002 y 2007
gapminder[gapminder$year == 2002 | 2007,]
Solución del desafío 7
Corrige cada uno de los siguientes errores para hacer subconjuntos de data frames:
Extraer observaciones colectadas en el año 1957
# gapminder[gapminder$year = 1957,] gapminder[gapminder$year == 1957,]
Extraer todas las columnas excepto de la 1 a la 4
# gapminder[,-1:4] gapminder[,-c(1:4)]
Extraer las filas donde la esperanza de vida es mayor a 80 años
# gapminder[gapminder$lifeExp > 80] gapminder[gapminder$lifeExp > 80,]
Extraer la primer fila, y la cuarta y quinta columna (
lifeExp
ygdpPercap
).# gapminder[1, 4, 5] gapminder[1, c(4, 5)]
Avanzado: extraer las filas que contienen información para los años 2002 y 2007
# gapminder[gapminder$year == 2002 | 2007,] gapminder[gapminder$year == 2002 | gapminder$year == 2007,] gapminder[gapminder$year %in% c(2002, 2007),]
Desafío 8
¿Por qué
gapminder[1:20]
regresa un error? ¿En qué difiere degapminder[1:20, ]
?Crea un
data.frame
llamadogapminder_small
que solo contenga las filas del 1 al 9 y del 19 al 23. Puedes hacerlo en uno o dos pasos.Solución al desafío 8
gapminder
es undata.frame
por lo que para hacer un subconjunto necesita dos dimensiones.gapminder[1:20, ]
genera un subconjunto de los datos de las primeras 20 filas y todas las columnas.2.
gapminder_small <- gapminder[c(1:9, 19:23),]
Puntos Clave
Los índices en R comienzan con 1, no con 0.
Acceso a un elemento por posición usando
[]
.Acceso a un rango de datos usando
[min:max]
.Acceso a subconjuntos arbitrarios usando
[c(...)]
.Usar operaciones lógicas y vectores lógicos para acceder a subconjuntos de datos