Creación de objetos

R es un lenguaje orientado a objetos: cada comando crea un objeto que debe ser “nombrado” para que permanezca en la memoria utilizando una <- y su nombre debe ser “invocado” para que aparezca en pantalla. A pesar de ser un lenguaje de programación, R es comparativamente simple e intuitivo.

Objetos comunes en R

objeto descripción
data.frame Objeto que contiene datos. Habitualmente se crea al importar datos externos, aunque pueden ser creados dentro del mismo R. Consiste en una serie de variables (vectors) de igual longitud y que pueden ser del mismo tipo o no.
vectors Colección de datos del mismo tipo (números, letras, etc.) que puede ser creado dentro de R o importado como una columna de un data.frame. Existen muchos tipos de vectores, entre ellos: numeric consiste de números reales; integer consiste de números enteros; character contiene letras, nombres, etc. (notar que cada elemento se encuentra entre comillas, por ej. “A1”); factor sirve para representar variables categóricas, porta información sobre los niveles del factor (levels) y sobre si estos niveles siguen un orden o no.
matrix Matriz formada por la unión de vectores de un mismo tipo y largo, por un solo vector que es partido en columnas y filas o (más habitualmente) producto de ciertas funciones, por ejemplo cor, que construye matrices de correlación.
list Objeto que compuesto de objetos de distinto tipo y largo.
tibble Objeto propio de los paquetes de tidyverse, similar en sus funciones a un data.frame pero con menores capacidades (por ejemplo, no admite nombres de filas), destinadas a simplificar su uso y evitar errores comunes.Más información en https://tibble.tidyverse.org/articles/tibble.html

Algunas ayudas básicas

Cuatro símbolos básicos: # <- ? c

# El símbolo # desactiva el espacio a su derecha.
# Es útil para poner aclaraciones en nuestras rutinas.

# El comando más básico: Asignar crea objetos.
# Puede usarse = en su reemplazo o -> para asignar el nombre al final.
# Para unificar el estilo de todas las rutinas sólo usaré <-
n <- 15  
n   # escribir el nombre de un objeto es "invocarlo"

# al usar de nuevo el mismo nombre el objeto anterior se pierde ("pisarlo")
n <- 46 + 12                 
n

# los caracteres categóricos necesitan ser ingresados con comillas
di <- "A"
di

# Obtener ayudas, el símbolo ?
# si tenemos dudas sobre el funcionamiento de una función
? lm

# Si desconocemos el nombre de una función para realizar determinada
# tarea, puede realizarse una búsqueda con ?? (sólo en los paquetes intalados)
?? "linear models"

# crear vectores. La función concatenar c
vector <- c(1, 2, 3, 4)
vector <- c("a", "b", "c", "d")

Funciones básicas: creación de vectores, secuencias y matrices

Las funciones de esta sección son las más sencillas de R. Como regla general las funciones siempre deben escribirse acompañadas de paréntesis, que contienen los elementos sobre los cuales la función actuará. Un ejemplo de función es c, que simplemente concatena objetos. funciones más complejas apelan a diferentes argumentos a los cuales hay que asignarles un valor y se encuentran separados por comas.

# vectores de repetición. 
# esta función tiene dos argumentos, separados por una coma
# rep(lo_que_queremos_repetir, cuantas veces)
xx <- rep("A", 50) 
xx

# secuencias 
seq1 <- c(1:50)  # el símbolo : indica desde:hasta
seq1

seq2 <- seq(from = 0, to = 0.99, by = 0.01) # argumentos explícitos
seq2

# combinando vectores de a pares: cbind y rbind
az <- cbind(seq1, xx)
za <- rbind(c(1:25), rnorm(25)) 

# comparar
az
za

# construyendo una matriz: matrix()
m <- c(1:20)
matriz1 <- matrix(m, 4, 5) # (vector a usar, filas, columnas) 
matriz1

Preparación e ingreso de datos: las funciones read.table y read.csv.

Los datos pueden prepararse con cualquier software para planillas de datos como Excel o LibreOffice Calc. Se recomienda:
- Que los nombres de las columnas no tengan espacios ni comiencen con números.
- Evitar los símbolos extraños como %, &, ^, ~, ñ, etc.
- Utilizar nombres cortos.
- Los datos faltantes no deben dejarse en blanco, sino señalarse con NA.
- Utilizar un formato tidy es decir donde *cada columna es una variable y cada fila es una observación.

Una vez lista la planilla se recomienda guardarla en formato .txt o .csv (si bien R admite otros formatos). Pueden guardarse en cualquier carpeta del equipo. Si utilizamos R Studio y guardamos los sets de datos en la misma carpeta que la rutina, el programa determinará esa carpeta como el directorio de trabajo de forma automática, por lo que no es necesario escribir la ruta completa al archivo. También puede evitarse escribir la ruta si al abrir R seleccionamos la carpeta que donde los archivos están guardados con la opción Session - Set Working Directory, o usando la función setwd (para examinar cual es la carpeta en uso, utilizar getwd). Esta opción es muy útil si vamos a abrir varios archivos de datos guardados en el mismo directorio. Finalmente, puede abrirse una ventana de búsqueda para seleccionar el archivo con la opción file.choose().

Desde R 4.0.0 las variables categóricas ya no son reconocidas como factores al importar un set de datos en R. Así que deben ser convertidas una por una con as.factor() cuando sea necesario o con la opción stringsAsFactors = TRUE dentro de read.table o de read.csv.

# la función read.table
# opción 1 con ruta completa (notar orientación de las barras /)
# en este caso asumimos que se encuentra en el directorio RD
datos <- read.table("C:/RD/datos/peces1.txt", header=TRUE)

# opción 2 seleccionando el directorio en uso previamente desde "Archivo"
# o con setwd
setwd("C:/RD/") ## "C:/RD/" es un ejemplo...
datos <- read.table("peces1.txt", header=TRUE)

# opción 3 Abre una ventana de búsqueda 
# desventaja: requiere que el humano trabaje!
datos <- read.table(file.choose(), header=TRUE)

# opción 4 la función read.csv
datos <- read.csv("C:/RD/peces1.csv", header=TRUE)

# los datos ya están guardados, pero si queremos verlos...
datos    # no siempre es buena idea, sobre todo si son muchos

# una forma práctica de ver solo las primeras filas es con head 
head(datos)

# o echar un vistazo a la estructura de los datos
# str informa sobre la estructura de cualquier objeto
str(datos)

# Información básica del set de datos
nrow(datos)     # número de filas
ncol(datos)     # número de columnas
names(datos)    # nombre de las columnas

Indexación

Estas operaciones se realizan para extraer de un objeto la parte que nos interesa, por ejemplo una columna con una variable de un marco de datos.
Sugiero no usar attach(), esta función aumenta las probabilidades de confundirse e introducir errores.

# Para seleccionar una columna del marco de datos utilizamos $
datos$grupo

# los corchetes indican el contenido de un conjunto de datos, 
# matriz o vector. La coma separa filas de columnas

#columnas
datos[, 1]                           
datos[, "grupo"]

# grupos de columnas
datos[, 1:3]                         
datos[, c("grupo", "largo.a")]

# filas y datos individuales
datos[2, ]                           
datos[2, 3]
datos[-2, 2]

# indexando por una condición
datos[datos$largo.a > 15, ]
datos[datos$largo.a > 15 & datos$grupo == "A", ]

# Indexación de vectores
vec <- datos$largo.a                 
vec[1]
vec[vec > 15]

Subdivisión de conjuntos de datos.

Para esta sección es clave conocer los símbolos lógicos: - == (igual)
- != (distinto)
- > (mayor)
- < (menor)
- >= (mayor o igual)
- <= (menor o igual)
- & y
- | o
- %in% en c(lista de posibles valores…)

# Subdivisión de un conjunto de datos
dat1 <- subset(datos, datos$grupo == "A")
dat1

dat2 <- subset(datos, datos$grupo == "A" & datos$trat == 2)
dat2

# Crear una lista de bases de datos
ldat <- split(datos, f = datos$grupo) 
ldat
ldat[["B"]] # indexacion d listas

Modificación.

La indexación y la subdivisión permite seleccionar una parte de un objeto (por ejemplo una columna). Esto es útil para aplicar transformaciones y también para resumir información de partes de un objeto.

# reemplazar (al usar el mismo nombre) una variable numérica por un factor
datos$trat
datos$trat <- as.factor(datos$trat) 
datos$trat    # notar la lista de niveles del factor

# crear una nueva columna en una base de datos ya existentes
# (utilizo un nombre nuevo)
datos$pob <- c(rep("pob1", 15), rep("pob2", 15))

datos$log.largo.a <- log(datos$largo.a)

head(datos)

# crear una nueva columna uniendo clasificadores
datos$clave <- paste(datos$pob, datos$trat, sep = ".")
head(datos)

# Usando factor para arreglos más complicados
datos$pais <- factor(datos$pob, levels = c("pob2", "pob1"), 
                     labels = c("Bolivia", "Chile"))
head(datos)

Funciones estadísticas básicas y de resumen.

En general si cualquiera de ellas se aplica sobre datos que contienen NA (dato ausente) el resultado será NA también, por lo cual hay indicarle a la función que los elimine. Una alternativa es eliminar previamente todas las filas con datos faltantes usando la función na.omit.

# media
M <- mean(datos$largo.a, na.rm = TRUE)
M

# desvío estándar
S <- sd(datos$largo.a, na.rm = TRUE)
S

# varianza
V <- var(datos$largo.a, na.rm = TRUE)
V

# sobre un conjunto de datos, se obtiene una matriz de varianza-covarianza
VC <- var(datos[,c(3,4)], na.rm = TRUE)
VC

# de forma parecida, puede obtenerse una matriz de correlación
CO <- cor(datos[, c(3, 4)], use="complete.obs")  #complete.obs elimina NA
CO

# en cambio, para realizar un test de correlación
cor.test(datos$largo.a, datos$largo.b, method = "pearson")

# resumen de datos
summary(datos)

# una función útil para aplicar una función a un subconjunto de los datos 
# es aggregate, con una estructura ligeramente más complicada

MG<-aggregate(datos$largo.a, by=list(datos$grupo), FUN=mean, na.rm=TRUE)
MG

Indexación, subdivisión y modificación en tidyverse.

Es cada vez más frecuente encontrar operaciones básicas re R realizadas a través del paquete dplyr. Si bien mi objetivo es enseñar a utilizar r-base incorporo esta sección y la siguiente para mostrar compartivamente estos diferentes dialectos de un mismo lenguaje.

library(dplyr)
# Subdivisión o *filtrado*
dat1 <- filter(datos, grupo == "A")
dat2 <- filter(datos, grupo == "A", largo.b > 100)

# Selección por colunmas
dat3 <- select(datos, grupo, largo.a)

# select tiene funciones de ayuda. Por ejemplo, seleccionar todas las 
# columnas que comiencen con "larg". Ver ?select para más ejemplos.
dat4 <- select(datos, starts_with("larg"))

# Modificación de columnas
# puede crearse un nuevo set de datos como aquí o modificarse el original
dat5 <- mutate(datos,
  prop = largo.b / largo.a, # columna nueva
  log.largo = log(largo.a)) # columna nueva
  

Haciendo más eficiente la programación: esto es una pipa


En 2014 Stefan Bache introdujo el operador pipa %>% en el paquete magrittr que “canaliza” un objeto hacia una función (pipe puede también traducirse como un tubo o cañería). Su uso principal es expresar una secuencia de múltiples operaciones, evitando la creación de objetos intermedios.
A partir de R.4.1.0, el lengujae básico incorpora una pipa nativa (native pipe) |> con funciones similares pero una sintaxis ligeramente diferente a la de la pipa del paquete magrittr.
Veamos un ejemplo sencillo donde:

library(magrittr)
library(dplyr)

# Usando R base, con creación de objetos intermedios
datos <- read.table("peces1.txt", header=TRUE)
largo.a_A <- subset(datos$largo.a, datos$grupo == "A")
M <- mean(largo.a_A)
M

# Usando R base, evitando los objetos intermedios
# Notar cómo se debe leer de "adentro" hacia "afuera".
M <- mean(subset(datos$largo.a, datos$grupo == "A"))
M

# Usando la pipa de magrittr %>%
# Notar como sigue más de cerca la lógica de 
# un "lenguaje natural"
datos$largo.a %>%
subset(datos$grupo == "A") %>%
mean() -> M  # en magrittr los paréntesis que acompañan mean son opcionales
M

# Usando 'pipas nativas' |>
datos$largo.a |>
subset(datos$grupo == "A") |>
mean() -> M
M

# Usando dplyr y pipa de magrittr
# (mismo resultado usando |>)
filter(datos, grupo == "A") %>%
select(largo.a) %>%
summarize(mean(largo.a)) -> M
M

Una nota sobre tidyverse

Los paquetes de tidyverse forman una colección de paquetes especialmente dedicados a la ciencia de datos, introducida por Hadley Wickham. Con la excepción de ggplot2, en este curso no profundiaremos en estos paquetes, que tienen una popularidad creciente. En mi experiencia son muy eficientes para la ciencia de datos, pero el objetivo de este curso es solamente aplicar R al análisis estadístico en ciencias biológicas. Creo que es más útil enseñar tidyverse en cursos de R de nivel intermedio y no introductorio, o con otras orientaciones. R base ofrece pocas herramientas que deben combinarse para resolver un problema. Por el contrario, las funciones de tidyverse se cuentan por cientos, son sumamente eficaces (y en muchos casos resuelven problemas que requieren bastante programación en R), pero limitan la creatividad, uno de los puntos que quiero enfatizar. Finalmente, tidyverse está sujeto a rápidos cambios, mientras que R Base es altamente conservado, permitiendo la ejecución de rutinas incluso varios años después de publicadas. Por ese motivo, lo encuentro más util para asegurar una ivnestigación reproducible.

Un excelente lugar para introducirce a tidyverse es R para ciencia de datos. También, para comparar tres tipos de sintaxis en R (con signo $, el modo fórmula y tidy) se puede consultar esta cheatshet.

Ejercicios

  1. Construya (o utilice si ya tiene disponible) una base de datos donde se encuentren variables categóricas, variables continuas y valores faltantes.