BIO 4022 - CLASES 3 y 4

Programa:
Doctorado en Ciencias Biológicas, Pontificia Universidad Católica de Chile

Autores:
Dr. Sebastián Abades
Departamento de Ecología, P. U. Católica de Chile
&
Instituto de Ecología y Biodiversidad (IEB)


Dr. Ariel Farías
Departamento de Ecología, P. U. Católica de Chile
&
Centro de Ecología Aplicada y Sustentabilidad (CAPES)


Año: 2015

Tópicos a abordar: Importar, agregar y presentar datos (tablas con estadísticas descriptivas y gráficos)

1. Importar y exportar tablas de datos

La estrategia más conveniente para importar datos tabulares en R producidos por otros programas (p.e., Excel) es guardarlos originalmente en formato de texto, con separador de campos simple (p.e. comas, espacio, tabulador, etc). De esta manera, la tabla de datos pesará menos (al no guardar formatos idiosincráticos), y podrá ser importada por funciones de base en R cuyo uso es muy simple. La más sencilla es read.table. Para ejemplificar usaremos las tablas contenidas en el archivo Tablas_clase2.zip disponible en la página web del curso. Son tres tablas creadas con distintos separadores de columnas.

data1 <- read.table("tabla1.txt", sep="\t", dec=",", h=T)
data1

Examinemos los argumentos usados en la función (y noten que hay muchos otros argumentos con valores por defecto llamando a ?read.table). + el primer argumento declara la ubicación y nombre COMPLETO de la tabla. En este ejemplo hemos supuesto que las tablas están en el mismo directorio de trabajo actual. De no ser el caso, es necesario reemplazar este argumento por “c:/path/al/directorio/de/datos/tabla1.txt”. Noten que las comillas son necesarias, pues se trata de un texto. + el argumento sep permite declarar el separados de columnas. En esta caso * indica tabulador. Para declarar separado de espacio usaríamos “”. Si el separador es una coma podríamos usar sep=“,”** o sep=“;”. + el argumento dec declara el separado decimal DE ORIGEN, en este caso ,. R usa por defecto el punto como separador decimal, por lo que este argumento controla la correcta conversión desde el formato de origen. + el argumento h es la versión corta de header, es decir, si los datos poseen encabezados de columna. Se trata de un argumento lógico, que por defecto es FALSE (o F). En este caso indicamos que sí existen encabezados de columna usando TRUE o T.

NOTA: lo anterior ilustra claramente que debemos tener pleno control del diseño de nuestras tablas ANTES de producirlas para ser enviadas a R. Buenas prácticas de manejo de datos implican, por lo bajo, mantener coherencia en el tipo de datos usados en cada columna. El uso de Excel u otras planillas de cálculo está muy difundido entre nosotros, pero la gran libertad para mezclar tipos de datos en celdas, ubicar múltiples tablas por hoja, incluir comentarios, etc, implica MUCHAS horas de trabajo organizando nuevas tablas que sean susceptibles de ser importadas por R (o cualquier otro software especializado). SEAN ORDENADOS Y PLANIFIQUEN CON ANTELACIÓN COMO USARAN SUS DATOS PARA PRODUCIR BUENAS TABLAS DESDE ORIGEN

EJERCICIO

Cargar todas las tablas provistas en Tablas_clase2.zip.

Revisen las siguientes funciones y comenten las similitudes:

help(read.csv)
help(read.delim)

Las opciones para exportar tablas desde R son muy similares a las de importación.

help(write.table)

Usemos una de las tablas ya cargadas (p.e. data1) para ilustrar

write.table(data1, "respaldo.txt", sep="\t", dec=".", col.names=TRUE)

Discutiremos en clases las implicancias de estos argumentos y otros ofrecidos por la función.

Más adelante en el curso veremos que es posible trabajar de formas muy sofisticadas conectando directamente R con bases de datos relacionales tales como Access, MySQL, PostgreSQL, etc.


2. Agregar datos

Usemos nuevamente la tabla de datos iris que ya viene instalada en la librería de base de R.

data(iris)
head(iris)
##   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

Nuestra función más elemental para agregar datos es aggregate. Discutiremos en clases las siguientes operaciones:

aggregate(Sepal.Length ~ Species, FUN=sum, data=iris)
aggregate(Sepal.Length ~ Species, FUN=mean, data=iris)
aggregate(Sepal.Length ~ Species, FUN=sd, data=iris)

Nota: el símbolo ~ es muy importante en R cuando se declaran expresiones o fórmulas. Significa es función de. Por ejemplo, y ~ x es interpretado por R como “la variable y es función de la variable x”. La expresión y ~ x + z indica que y es función (lineal) de las variables x y z. Esta forma de declarar una función será requerida la función básica de agregación llamada aggregate, pero también será imprescindible al usar funciones de regresión, al graficar, etc.

Hay librerias especializadas como reshape que permite transformar las tablas a formatos más apropiados para ejecutar algunos análisis, como anova, regresión lineal, etc. Ilustraremos un ejemplo, pero dejaremos que ustedes revisen con más profundidad las opciones disponibles en librerías especializadas (recuerden que el énfasis pedagógico del curso consiste en promover la escritura de funciones usando elementos muy básicos de programación y evitando al comienzo el uso de librerías muy especializadas)

library(reshape)

Posiblemente tengamos un error, pues aun no hemos instalado esta librería en nuestro sistema. Buen momento para introducirnos en el proceso de instalación.

# install.packages("reshape", dep=TRUE)

Ahora podemos llamar a la librería

require(reshape)  ### noten que "library" y "requiere" tienen el mismo efecto.
## Loading required package: reshape

Vamos a crear una pequeña tabla para ilustrar:

ID <- rep(1:2, times=2)
tiempo <- rep(1:2, each=2)
set.seed(1234)   # para que el ejercicio sea reproducible
x1 <- rnorm(4)
x2 <- runif(4, 3, 10)

mis.datos <- cbind(ID, tiempo, x1,x2)

Revisemos lo recién creado

mis.datos
##      ID tiempo         x1       x2
## [1,]  1      1 -1.2070657 7.662586
## [2,]  2      1  0.2774292 6.599758
## [3,]  1      2  1.0844412 7.855139
## [4,]  2      2 -2.3456977 6.814824

Un detalle más

typeof(mis.datos)
## [1] "double"

Así que aprovechemos de cambiar la estructura de datos desde matriz a data frame

mis.datos<-as.data.frame(mis.datos)

Check:

typeof(mis.datos)
## [1] "list"
as.data.frame(mis.datos)
##   ID tiempo         x1       x2
## 1  1      1 -1.2070657 7.662586
## 2  2      1  0.2774292 6.599758
## 3  1      2  1.0844412 7.855139
## 4  2      2 -2.3456977 6.814824

Las dos funciones contenidas en la librería reshape que usaremos son melt y cast. La función melt permite que cada fila tenga una combianción única de variables.

mis.datos2 <- melt(mis.datos, id=c("ID","tiempo"))

La función cast permite moldear los datos usando una “función” y una “fórmula” según este modelo: cast(data, formula, function). Por ejemplo:

proms.por.id <- cast(mis.datos2, ID~variable, mean)
proms.por.id
##   ID          x1       x2
## 1  1 -0.06131229 7.758863
## 2  2 -1.03413423 6.707291

o bien

proms.por.tiempo <- cast(mis.datos2, tiempo~variable, mean) 
proms.por.tiempo
##   tiempo         x1       x2
## 1      1 -0.4648183 7.131172
## 2      2 -0.6306283 7.334981


3. Estadísticas descriptivas

Tenemos una infinidad de formas para obtener estadísticas descripticas a partir de nuestras tablas de datos. El contexto de análisis dictará cuál resulta más conveniente, pero en cualquier caso no éxiste una única solución. La versión más rápida de que disponemos es:

summary(iris[,-ncol(iris)]) 
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500

Discutir:¿por qué se incluyó -ncol(iris)?

Ciertamente podemos usar aggregate para producir tablas resumenes a medida. Por ejemplo:

data.proms <- aggregate(Petal.Length ~ Species, FUN=mean, data=iris)
data.sd <- aggregate(Petal.Length ~ Species, FUN=sd, data=iris)
data.min <- aggregate(Petal.Length ~ Species, FUN=min, data=iris)
data.max <- aggregate(Petal.Length ~ Species, FUN=max, data=iris)
head(data.max)
##      Species Petal.Length
## 1     setosa          1.9
## 2 versicolor          5.1
## 3  virginica          6.9

y consolidamos:

tabla.resumen <- cbind(data.proms, data.sd[,2], data.min[,2], data.max[,2])
tabla.resumen
##      Species Petal.Length data.sd[, 2] data.min[, 2] data.max[, 2]
## 1     setosa        1.462    0.1736640           1.0           1.9
## 2 versicolor        4.260    0.4699110           3.0           5.1
## 3  virginica        5.552    0.5518947           4.5           6.9

¿y los nombres de cada columna?

colnames(tabla.resumen) <- c("Especie", "Promedio", "Des.Est", "Min", "Max")
tabla.resumen
##      Especie Promedio   Des.Est Min Max
## 1     setosa    1.462 0.1736640 1.0 1.9
## 2 versicolor    4.260 0.4699110 3.0 5.1
## 3  virginica    5.552 0.5518947 4.5 6.9

¿y por qué no dejar un comentario que nos ayude en el futuro a recordar lo que acabamos de hacer? Asignamos un comentario usando

comment(tabla.resumen) <- "Tabla resumen para la variable Largo de Petalos a partir de la tabla de datos iris"

Para visualizar el comentario simplemente usamos

comment(tabla.resumen)
## [1] "Tabla resumen para la variable Largo de Petalos a partir de la tabla de datos iris"



4. Gráficos

R nos da MUCHA flexibilidad al momento de preparar gráficas. La combinaciones de parámetros disponibles es ilimitada, y nos permite elaborar gráficos de calidad de impresión. Sin embargo, las opciones habilitadas por defecto son muy simples, lo que tiende a desmotivar al usuario primerizo de R. La duración de este curso no permite expandirnos tanto que deseamos en este tópico, por lo que elaboraremos sobre lo escencial. Existen muchos y buenos libros, manuales y tutoriales que pueden ser consultados, comenzando por los tutoriales provistos por los desarrolladores de R (disponibles para descargar desde www.r-project.org).

Usemos nuevamente la tabla de datos iris que ya viene instalada en la librería de base de R.

data(iris)
head(iris)
##   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

4.1. Scatterplots

La forma más sencilla para representar la relación entre dos variables continuas es un gráfico de dispersión o scatterplot.

plot(iris$Sepal.Length, iris$Sepal.Width)

Bastante feo. Alternativamente podemos declarar de esta forma

plot(Sepal.Width ~ Sepal.Length, data=iris)

Hagamos más informativo el gráfico

plot(iris$Sepal.Length, iris$Sepal.Width, xlab=" Largo (cm)", ylab="Ancho (cm)", main="Relacion morfometrica de sepalos")

Mejor, pero hay una mezcla de datos de tres especies distintas.

plot(iris$Sepal.Length, iris$Sepal.Width, xlab=" Largo (cm)", ylab="Ancho (cm)", main="Relacion morfometrica de sepalos", col=iris$Species, pch=19)

Ejercicio dirigido en clases: + cambiar valores de pch + escala de los ejes + asignar colores específicos a los puntos + cambiar tamaño de puntos + cambiar tamaño de letras + incluir legenda


Existen varias librerías que permiten elaborar gráficos condicionales, insertos en paneles, etc. Dos de los más utilizados son lattice y ggplot2. Esta última es en realidad una implementación en R de un lenguaje independiente basado en aesthetics (i.e. estética), en la que es posible ir modificando el gráfico por capas, similar a la aproximación usada en photoshop.


4.2. Boxplots ——————————- Los boxplots nos dan una de las visualizaciones más útiles para diagnosticar diferencias entre promedios, posible ausencia de homocedasticidad, presencia de valores extremos (i.e. outliers). Evidentemente es útil para relacionar una variable de respuesta continua y una variable independiente categórica. Nuevamente haremos uso de iris para ilustrar. Lo mínimo necesario sería:

boxplot(Sepal.Length ~ Species, data=iris)

Revisemos las opciones que nos brinda la función (?boxplot) y ejercitemos algunos cambios estéticos:

boxplot(Sepal.Length ~ Species, data=iris, width=c(.1,.8,.3))

boxplot(Sepal.Length ~ Species, data=iris, width=c(.1,.8,.3), col="red")

boxplot(Sepal.Length ~ Species, data=iris, width=c(.1,.8,.3), col=c("red","lightblue","blue"))

EJERCICIO: alterar otros parámetros gráficos a placer.


4.3. Histogramas

Es muy sencillo resumir un vector de datos en un histograma de frecuencias. Simulemos 5000 datos tomados de una distribución Norma con media 10 y desvío estándar 2.

da <- rnorm(5000, 10, 2)
hist(da)

Mejoremos la estética:

hist(da, breaks=10, xlab="", ylab="Frecuencia", main="", col="green2")

EJERCICIO: + cambia el valor asignado al argumento breaks + crea una secuencia de datos y úsala en reemplazo del número empleado en breaks

4.4. Graficos de barras

Hasta ahora hemos generado gráficas usando datos crudos, sin necesidad de agregar esta información de ninguna manera, pues las funciones de graficación usadas se han encargado de resumir la información por nosotros. Curiosamente, el gráfico de barras a pesar de ser una de las visualizaciones más básicas, no obliga a hacer varias operaciones:

+Si las barras son frecuencias ++Producir una tabla resumen con conteos para cada categoría. Estos valores definirán el alto de las barras.

+Si las barras son promedios ++ Producir una tabla resumen, típicamente con valores promedios por categoría. ++ Producir una tabla resumen con errores estándar, para ser usados en la confección de las líneas de error.

Para el primer caso:

data.barras <- aggregate(Petal.Width ~ Species, FUN=length ,data=iris)
barplot(height=data.barras[,2], names.arg=data.barras[,1])

No es muy lindo. Examina las opciones disponibles con ?barplot y haz modificaciones que: + cambien el ancho de las barras + los colores de las barras + Agregar la etiqueta del eje y


Para el segundo caso, simplifiquemos las cosas incluyendo una librería que posee algunas funciones útiles:

# install.packages("gplots", dep=T)
require(gplots)
## Loading required package: gplots
## 
## Attaching package: 'gplots'
## 
## The following object is masked from 'package:stats':
## 
##     lowess

Ahora disponemos de la función barplot2

promedios <- aggregate(Petal.Width ~ Species, FUN=mean ,data=iris)  ## promedios por especia
desv.est <- aggregate(Petal.Width ~ Species, FUN=sd ,data=iris)  ## desvío estándar
N <- aggregate(Petal.Width ~ Species, FUN=length ,data=iris)  ## tamaño muestra
ee <- desv.est[,2]/N[,2]   ## error estándar
dos.ee <- 1.86*ee  ## ¿por qué se muliplica por 1.86? ¿por qué no llamarlo 2.ee?

#### cálculo de límite superior
inferior <- promedios[,2]+dos.ee

#### cálculo de límite inferior
superior <- promedios[,2]-dos.ee

barplot2(height=promedios[,2], names.arg=promedios[,1], plot.ci=TRUE, ci.l=inferior,ci.u=superior)

Hay poca dispersión. Magnifiquemos arificialmente las barras de error SÓLO para dejar en claro que las barras de error están ahí.

inferior2 <- inferior+0.1
superior2 <- superior-0.1

barplot2(height=promedios[,2], names.arg=promedios[,1], plot.ci=TRUE, ci.l=inferior2,ci.u=superior2)

y mejoremos un poco la presentación de la “T” de las líneas de error

barplot2(height=promedios[,2], names.arg=promedios[,1], plot.ci=TRUE, ci.l=inferior2,ci.u=superior2, ci.width=.1, space=.8, ylim=c(0,2.5))

box()

EJERCICIO: explora las opciones que ofrece barplot2.


4.5. Modificación de parámetros gráficos generales

Existem múltiples parámetros que modifican el dispositivo sobre el cual se produce el despliegue gráfico. La función par nos da mucha flexibilidad sobre los atributos generales del lienzo. Nota: Una cosa es el lienzo donde se desplegará el gráfico, y otra cosa es el gráfico mismo. Las opciones del lienzo modifican atributos como el tamaño de márgenes, tipo y tamaño de letra, ángulos de letras, número de tickmarks, etc. Las opciones de un gráfico específico no necesariamente estarán listadas en par, pues son parte de un código específico creado por un programador con el fin de producir el gráfico deseado. En ocasiones es posible invocar opciones de par desde la función gráfica, pero no siempre es el caso.

Cambiemos los márgenes del lienzo. La opción mar realiza esta acción a través de un vector con cuatro valores que indican en número de líneas (usadas por un texto). El vector tendría la forma c(abajo, izquierda, arriba, derecha) :

par(mar=c(6,6,1,0))
plot(1:10, runif(10))

Para ilustrar otra opción:

par(mar=c(2,1,7,4))
plot(1:10, runif(10))

Otra opción interesante de par es el argumento mfrow, la que permite dividir el lienzo en un número predefinido de cuadros. El argumento necesita dos valores para crear la grilla que dividirá el lienzo c(n°filas, n°columnas). Una vez realizado esto, se pueden invocar los gráficos secuencialmente para ocupar los espacios recién creados

par(mfrow=c(2,3))
hist(runif(100), main="")
plot(1:10)
plot(3:7,3:7)
hist(iris$Sepal.Length, main="")
hist(iris$Sepal.Width, main="")
boxplot(iris$Petal.Width ~ iris$Species)

No quedan muy atractivos, pues no hicimos un buen control de los márgenes. Incluyamos algunos arreglos

par(mfrow=c(2,3), mar=c(3,3,1,1))
hist(runif(100), main="")
plot(1:10)
plot(3:7,3:7)
hist(iris$Sepal.Length, main="")
hist(iris$Sepal.Width, main="")
boxplot(iris$Petal.Width ~ iris$Species)

Obviamente, en la medida que incorporemos más detalles nos veremos obligados a cambiar algunos atributos para calzar los formatos, tales como tamaño de letras, orientación, tamaño de símbolos, tamaño del lienzo, etc.
Podemos usar otros argumentos de par para generar gráficos compuestos con posiciones irregulaes. Para esto utilizaremos fig, argumento que consiste en cuatro coordenadas c(x1, x2, y1, y2) expresadas en escala relativa (i.e., entre 0 y 1)

par(fig=c(0.2, 0.5, 0, 0.5))
hist(rnorm(1000), main="")

par(fig=c(0.55, 1, 0.5, 0.9), new=T)   #### Nótese el argumento new=TRUE
hist(runif(1000), col="blue", main="")

EJERCICIOS: + Explorar otras de las funciones disponibles en par y probar su efecto sobre un gráfico a elección. + Elaborar un panel compuesto por cuatro gráficas de tamaños irregulares.


4.6. Exportar gráficas

R soporta varios formatos populares, tales como jpg, png, tiff, pdf. En todos los casos, el procedimiento para exportar la gráfica consiste en tres pasos:
+ abrir el dispositivo
+ generar el gráfico
+ cerrar el dispositivo

Ilustremos usando jpg

jpeg(filename="test.jpg")
hist(rnorm(500),breaks=30, col=gray(0.3))
dev.off()
## quartz_off_screen 
##                 2

Cambios en el tamaño de la figura pueden controlase con los argumentos width y height, mientras que cambios en resolución se obtienen por medio de una combinación de valores para res, pointsize y quality. Figuras de resolución muy fina tendrán un peso muy elevado, por lo que es necesario mantener un cierto trade-off y no exagerar en resolución.

Las funciones png, tiff y otras emparentadas funcionan de similar forma. Para el caso de pdf es necesario contar con una instalación previa del driver correspondiente (esto no es un problema de R, sino del sistema operativo utilizado). En ocasiones es necesario también contar con una instalación del programa Ghosview. En OSX y Linux por los general este no es un problema, pero Windows puede en ocasiones requerir un poco de trabajo administrativo.