Estructura de datos

Resumen

Enseñando: 40 min
Ejercicios: 15 min
Preguntas
  • ¿Cómo puedo leer datos en R?

  • ¿Cuáles son los tipos de datos básicos en R?

  • ¿Cómo puedo representar información categórica en R?

Objectivos
  • Conocer los distintos tipos de datos.

  • Iniciar a explorar data frames, y entender como están relacionados con los vectores, factores y listas.

  • Ser capaz de hacerle preguntas a R sobre el tipo, la clase y la estructura de un objeto.

Uno de las características más poderosas de R es su capacidad de manejar datos tabulados - como los que tal vez ya tengas en una hoja de cálculo o un archivo CSV. Empecemos por descargar y leer un archivo nordic-data.csv. Guardaremos estos datos como un objeto llamado nordic:

nordic <- read.csv("data/nordic-data.csv")

La función read.table se usa para leer datos tabulados guardados en un archivo de texto donde las columnas de los datos están separadas por caracteres de puntuación como sucede en los archivos CSV (csv = valores separados por comas). Tabuladores y comas son los caracteres más usados para separar o delimitar datos en archivos csv. Por comodidad R provee dos otras versiones de read.table. Esas son: read.csv para archivos donde los datos están separados por comas y read.delim para archivos donde los datos están separados por tabuladores. De esas tres funciones read.csv es la más usada. Si es necesario, es posible cambiar las el carácter por defecto delimitando las marcas de puntuación para ambos read.csv y read.delim.

Podemos empezar explorando nuestro conjunto de datos de inmediato, extrayendo columnas especificándolas usando el operador $:

nordic$country
[1] "Denmark" "Sweden"  "Norway" 
nordic$lifeExp
[1] 77.2 80.0 79.0

Podemos hacer otras operaciones en las columnas. Por ejemplo, si descubrimos que la esperanza de vida es dos años más alta:

nordic$lifeExp + 2
[1] 79.2 82.0 81.0

Pero qué pasa:

nordic$lifeExp + nordic$country
Error in nordic$lifeExp + nordic$country: non-numeric argument to binary operator

Entender lo que sucedió aquí es clave para analizar con éxito los datos en R.

Tipo de datos

Si pensaste que el último comando resultaría en un error porque 77.2 más "Denmark" no tiene sentido, entonces acertaste - y ya tienes una intuición para un concepto importante en programación llamado clases de datos. Podemos preguntar qué clase de datos es algo:

class(nordic$lifeExp)
[1] "numeric"

Hay 6 tipos de datos fundamentales: numeric, integer, complex, logical, character, y factor.

class(3.14)
[1] "numeric"
class(1L) # El sufijo L fuerza que el número sea un entero ya que R los guarda como punto flotante por defecto
[1] "integer"
class(1+1i)
[1] "complex"
class(TRUE)
[1] "logical"
class('banana')
[1] "character"
class(factor('banana'))
[1] "factor"

No importa cuan complicado sea nuestro análisis, todos los datos en R se convierten a una clase de datos específica. Este rigor tiene algunas consecuencias realmente importantes.

Un usuario ha añadido nuevos detalles a la esperanza de vida. Dicha información está en el archivo data/nordic-data-2.csv.

Carga los datos nórdicos nuevos como nordic_2, y comprueba que clase de datos encontramos en la columna lifeExp:

nordic_2 <- read.csv("data/nordic-data-2.csv")
class(nordic_2$lifeExp)
[1] "character"

¡Oh no, nuestra esperanza de vida lifeExp ya no es de tipo numérico! Si intentamos hacer los mismos cálculos matemáticos que hicimos con ellos antes, nos metemos en problemas:

nordic_2$lifeExp + 2
Error in nordic_2$lifeExp + 2: non-numeric argument to binary operator

¿Qué ha sucedido? Cuando R lee un archivo csv en una de esas tablas, R insiste que todo en una columna sea de la misma clase. Si R no puede entender todo en una columna como numérico, entonces a nada en la columna se le asigna el tipo numérico. La tabla en la que R cargó nuestros datos nórdicos es algo conocido como marco de datos (dataframe), y es nuestro primer ejemplo de lo que se conoce como una estructura de datos - es decir, una estructura que R sabe como construir partiendo de tipo de datos básicos.

Podemos ver que us un marco de datos (dataframe) llamándo la función class() en ella:

class(nordic)
[1] "data.frame"

Para usar con éxito nuestros datos en R, necesitamos entender cuales son las estructuras de datos fundamentales y como se comportan.

Vectores y Tipo de Coerción

Para mejor entender este comportamiento, conozcamos otra de las estructuras de datos: el vector.

my_vector <- vector(length = 3)
my_vector
[1] FALSE FALSE FALSE

Un vector en R es esencialmente un lista de cosas ordenadas, con la condición especial de que todo en el vector tiene que ser del mismo tipo de dato. Si no eliges el tipo de dato, por defecto será lógico; o, puedes declarar un vector vacío del tipo que quieras.

another_vector <- vector(mode = 'character', length = 3)
another_vector
[1] "" "" ""

Puedes revisar si algo es un vector:

str(another_vector)
 chr [1:3] "" "" ""

El resultado un poco críptico de este comando indica el tipo básico de dato encontrado en este vector - en este caso chr, carácter; una indicación del numero de cosas en el vector - en realidad, los índices del vector, en este caso [1:3]; y unos ejemplos de lo que está realmente en el vector - en este caso cadenas de caracteres vacíos. Si lo hacemos de manera similar

str(nordic$lifeExp)
 num [1:3] 77.2 80 79

vemos que nordic$lifeExp también es un vector - las columnas de datos que cargamos en los data frames en R, son todas vectores, y es por eso que R obliga que todo en una columna para ser del mismo tipo de dato básico.

Discusión 1

¿Por qué R es tan dogmático con los datos que ponemos en nuestras columnas? ¿Cómo esto nos ayuda?

Discusión 1

Manteniendo todo en una columna igual , nos permitimos hacer simples supuestos sobre nuestros datos; si puedes interpretar una entrada en una columna como un  número, entonces puedes interpretar todas ellas como números, así no tenemos que verificar todo el tiempo. Esta consistencia es a lo que se refieren las personas cuando hablan de datos limpios; a la larga, la estricta consistencia contribuye considerablemente a hacer nuestras vidas más fáciles en R.

También puedes crear vectores con contenido explícito usando la función combine:

combine_vector <- c(2, 6, 3)
combine_vector
[1] 2 6 3

Dado lo que aprendimos hasta ahora, ¿qué crees que va a resultar de lo siguente?

quiz_vector <- c(2, 6, '3')

Esto es algo llamado tipo coerción, y es la fuente de muchas sorpresas y la razón por la que debemos conocer los tipos de dato básicos y cómo R los interpreta. Cuando R consigue una mezcla de tipos (aquí numéricos y de caracteres) para combinarlos en un sólo vector, los obligará a todos ser del mismo tipo. Considera:

coercion_vector <- c('a', TRUE)
coercion_vector
[1] "a"    "TRUE"
another_coercion_vector <- c(0, TRUE)
another_coercion_vector
[1] 0 1

Las reglas de coerción son: logical -> integer -> numeric -> complex -> character, donde -> puede ser leído como se transfoman en. Puedes intentar forzar la coerción contra este flujo usando las funciones as.:

character_vector_example <- c('0', '2', '4')
character_vector_example
[1] "0" "2" "4"
character_coerced_to_numeric <- as.numeric(character_vector_example)
character_coerced_to_numeric
[1] 0 2 4
numeric_coerced_to_logical <- as.logical(character_coerced_to_numeric)
numeric_coerced_to_logical
[1] FALSE  TRUE  TRUE

Como puedes ver, ¡algunas cosas sorprendentes pueden ocurrir cuando R fuerza un tipo de datos básico en otro! Dejando de lado la cuestión de la coerción de tipo, el punto es: si tus datos no se parecen a lo que pensabas que iban a ser , la coerción de tipo bien puede ser la culpable; asegúrate que todo sea del mismo tipo en tus vectores y en tus columnas del data frame , o ¡tendrás sorpresas desagradables!

Desafío 1

Dado lo que sabe ahora sobre la conversión de tipos, observe la clase de datos en nordic_2$lifeExp y compáralo con nordic$lifeExp. ¿Por qué estas columnas son de diferentes clases?

Solución

str(nordic_2$lifeExp)
 chr [1:3] "77.2" "80" "79.0 or 83"
str(nordic$lifeExp)
 num [1:3] 77.2 80 79

Los datos en nordic_2$lifeExp son almacenados como factor en lugar de numérico. Esto es por el caracter tipo texto (string) “or” en la tercer punto de datos. “Factor” es el término especial de R para los datos categóricos. Trabajaremos más con datos de factores más adelante en este taller.

La función de combinación, c(), también agregará cosas a un vector existente:

ab_vector <- c('a', 'b')
ab_vector
[1] "a" "b"
combine_example <- c(ab_vector, 'DC')
combine_example
[1] "a"  "b"  "DC"

También puedes hacer series de números:

my_series <- 1:10
my_series
 [1]  1  2  3  4  5  6  7  8  9 10
seq(10)
 [1]  1  2  3  4  5  6  7  8  9 10
seq(1,10, by = 0.1)
 [1]  1.0  1.1  1.2  1.3  1.4  1.5  1.6  1.7  1.8  1.9  2.0  2.1  2.2  2.3  2.4
[16]  2.5  2.6  2.7  2.8  2.9  3.0  3.1  3.2  3.3  3.4  3.5  3.6  3.7  3.8  3.9
[31]  4.0  4.1  4.2  4.3  4.4  4.5  4.6  4.7  4.8  4.9  5.0  5.1  5.2  5.3  5.4
[46]  5.5  5.6  5.7  5.8  5.9  6.0  6.1  6.2  6.3  6.4  6.5  6.6  6.7  6.8  6.9
[61]  7.0  7.1  7.2  7.3  7.4  7.5  7.6  7.7  7.8  7.9  8.0  8.1  8.2  8.3  8.4
[76]  8.5  8.6  8.7  8.8  8.9  9.0  9.1  9.2  9.3  9.4  9.5  9.6  9.7  9.8  9.9
[91] 10.0

Puedes hacer algunas preguntas sobre vectores:

sequence_example <- seq(10)
head(sequence_example,n = 2)
[1] 1 2
tail(sequence_example, n = 4)
[1]  7  8  9 10
length(sequence_example)
[1] 10
class(sequence_example)
[1] "integer"

Finalmente, puedes poner nombres a los elementos en tu vector:

my_example <- 5:8
names(my_example) <- c("a", "b", "c", "d")
my_example
a b c d 
5 6 7 8 
names(my_example)
[1] "a" "b" "c" "d"

Desafío 2

Empieza por hacer un vector con los números 1 a 26. Multiplica el vector por 2, y da al vector resultante los nombres A a Z (sugerencia: hay un vector incorporado llamado LETTERS)

##Solución al Desafío 2

<- 1:26
x <- x * 2
names(x) <- LETTERS

Factores

Dijimos que las columnas en los dataframes eran vectores:

str(nordic$lifeExp)
 num [1:3] 77.2 80 79
str(nordic$year)
int [1:3] 2002 2002 2002

Esto tiene sentido. Pero qué pasa con

str(nordic$country)
chr [1:3] "Denmark" "Sweden" "Norway"

Otra estructura de datos importante se llama factor. Los factores se parecen a los datos de tipo carácter, pero son usados para representar información categórica. Por ejemplo, hagamos un vector de tipo string (cadena de caracteres) etiquetando los países nórdicos para todos los países de nuestro estudio.

nordic_countries <- c('Norway', 'Finland', 'Denmark', 'Iceland', 'Sweden')
nordic_countries
[1] "Norway"  "Finland" "Denmark" "Iceland" "Sweden" 
str(nordic_countries)
 chr [1:5] "Norway" "Finland" "Denmark" "Iceland" "Sweden"

Podemos convertir un vector en un factor así:

categories <- factor(nordic_countries)
class(categories)
[1] "factor"
str(categories)
 Factor w/ 5 levels "Denmark","Finland",..: 4 2 1 3 5

Ahora R identificó que hay 5 categorías posibles en nuestros datos - pero también realizó algo sorprendente; en vez de imprimir la secuencia de caracteres que le dimos, tenemos un montón de números en su lugar. R remplazó nuestras categorías legibles para humanos con índices numerados por detrás, esto es necesario ya que muchos cálculos estadísticos utilizan esta representación numérica para datos categóricos.

class(nordic_countries)
[1] "character"
class(categories)
[1] "factor"

Desafío

¿Puedes adivinar por qué estos números son utilizados para representar a estos países?

Solución

Están ordenados alfabéticamente.

Desafío 3

¿Hay un factor en nuestro dataframe nordic? ¿Cual es su nombre? Trata de usar ?read.csv para deducir como mantener las columnas de texto como vectores de caracteres en vez de factores; luego escribe un comando o dos para mostrar que el factor en nordic es en realidad un vector de caracteres cuando está cargado de esta manera .

Solución al Desafío 3

Una solución es usar el argumento stringAsFactors:

nordic <- read.csv(file = "data/nordic-data.csv", stringsAsFactors = FALSE)
str(nordic$country)

Otra solución es usar el argumento colClasses que permite un control más fino.

nordic <- read.csv(file="data/nordic-data.csv", colClasses=c(NA, NA, "character"))
str(nordic$country)

Nota: nuevos estudiantes encuentran difícil de entender los archivos de ayuda ; asegúrate de hacerles saber que es común, y de alentarlos a hacer su mejor conjetura sobre el significado semántico, incluso si no están seguros.

Al hacer modelos estadísticos, es importante conocer cuales son los levels (categorías) de base. Se supone que este es el primer factor, pero por defecto los factores se etiquetan alfabéticamente. Puedes cambiar esto al especificar los levels (categorías):

mydata <- c("case", "control", "control", "case")
factor_ordering_example <- factor(mydata, levels = c("control", "case"))
str(factor_ordering_example)
 Factor w/ 2 levels "control","case": 2 1 1 2

En este caso, le dijimos a R que “control” debería ser representado con 1, y “case” con 2. ¡Esta designación puede ser muy importante para interpretar los resultados de modelos estadísticos!

Listas

Otra estructura de datos que querrás tener en tu bolsa de trucos es la list. Una lista es en de algún modo más simple que otros tipos, porque puedes poner lo quieras dentro:

list_example <- list(1, "a", TRUE, c(2, 6, 7))
list_example
[[1]]
[1] 1

[[2]]
[1] "a"

[[3]]
[1] TRUE

[[4]]
[1] 2 6 7
another_list <- list(title = "Numbers", numbers = 1:10, data = TRUE )
another_list
$title
[1] "Numbers"

$numbers
 [1]  1  2  3  4  5  6  7  8  9 10

$data
[1] TRUE

Ahora podemos entender algo un poco sorprendente en nuestro dataframe; que ocurre si comparamos str(nordic) y str(another_list):

str(nordic)
'data.frame':\t3 obs. of  3 variables:
 $ country: chr  "Denmark" "Sweden" "Norway"
 $ year   : int  2002 2002 2002
 $ lifeExp: num  77.2 80 79
str(another_list)
List of 3
 $ title  : chr "Numbers"
 $ numbers: int [1:10] 1 2 3 4 5 6 7 8 9 10
 $ data   : logi TRUE

Vemos que los resultados para estos dos objetos se ven parecidos. Esto es porque los dataframes son listas ‘tras bambalinas’. Los dataframes son un caso especial de lista donde cada elemento (las columnas del dataframe) tiene el mismo largo.

En nuestro ejemplo nordic, tenemos una variable integer, una variable double y una variable logical. Como ya vimos, cada columna del dataframe es un vector.

nordic$country
[1] "Denmark" "Sweden"  "Norway" 
nordic[, 1]
[1] "Denmark" "Sweden"  "Norway" 
class(nordic[, 1])
[1] "character"
str(nordic[, 1])
chr [1:3] "Denmark" "Sweden" "Norway"

Cada fila es una observación de diferentes variables, en sí misma un dataframe, y entonces puede estar compuesto de elementos de diferentes tipos.

nordic[1, ]
  country year lifeExp
1 Denmark 2002    77.2
class(nordic[1, ])
[1] "data.frame"
str(nordic[1, ])
'data.frame':\t1 obs. of  3 variables:
 $ country: chr "Denmark"
 $ year   : int 2002
 $ lifeExp: num 77.2

Desafío 4

Hay varias maneras sutilmente diferentes de llamar variables, observaciones y elementos de los dataframes:

  • nordic[1]
  • nordic[[1]]
  • nordic$country
  • nordic["country"]
  • nordic[1, 1]
  • nordic[, 1]
  • nordic[1, ]

Prueba estos ejemplos y explica que se regresa en cada uno.

Sugerencia: Usa la función class() para examinar que se devuelve en cada caso.

Solución al Desafío 4

nordic[1]
  country
1 Denmark
2  Sweden
3  Norway

Podemos pensar en un dataframe como una lista de vectores. El corchete simple [1] devuelve la primer parte de la lista, como otra lista. En este caso es la primer columna del dataframe.

nordic[[1]]
[1] "Denmark" "Sweden"  "Norway" 

El corchete doble [[1]] regresa el contenido del elemento de la lista. En este caso es el contenido de la primer columna, un_vector_del tipo factor.

nordic$country
[1] "Denmark" "Sweden"  "Norway" 

Este ejemplo use el carácter $ para dirigirse a elementos por su nombre. coat es la primer columna del dataframe, de nuevo un vector de tipo factor. X

nordic["country"]
  country
1 Denmark
2  Sweden
3  Norway

Aquí usamos un corchete sencillo ["country"] remplazando el índice numérico con el nombre de la columna. Como en el ejemplo 1, el objeto devuelto es una lista.

nordic[1, 1]
[1] "Denmark"

Este ejemplo usa un corchete sencillo, pero esta vez brindamos las coordenadas de fila y columna. El objeto devuelto es el valor en la fila 1, columna 1. El objeto un integer pero debido a que es parte de un vector de tipo factor, R muestra la etiqueta “Denmark” asociada con el valor entero (integer).

nordic[, 1]
[1] "Denmark" "Sweden"  "Norway" 

Como los ejemplos previos, usamos el corchete sencillos y brindamos las coordenadas de fila y columna. La coordenada de fila no está especificada, R interpreta este valor faltante como todos los elementos en esta columna vector

nordic[1, ]
  country year lifeExp
1 Denmark 2002    77.2

Otra vez usamos el corchete sencillo con las coordenadas de fila y columna. La coordenada de columna no está especificada. El valor devuelto es una lista que contiene todos los valores en la primer fila.

Puntos Clave

  • Usar read.csv para leer datos tabulados desde R.

  • Los tipos de datos fundamentales en R son dobles, enteros, complejos, lógicos y caracteres.

  • Usar factores para representar categorías en R.