require(CursoR) require(learnr) require(tidyverse) require(curl) require(gradethis) require(kableExtra) require(palmerpenguins) library(gapminder) require(tibble) require(timevis) require(ggplot2) require(patchwork) require(fontawesome) #PARA COMENTAR AL FINALIZAR require(jpeg) require(grid) require(gridGraphics) require(showtext) font_add_google('Gochi Hand', 'gochi') #PARA COMENTAR AL FINALIZAR tutorial_options(exercise.timelimit = 60, exercise.checker = gradethis::grade_learnr) knitr::opts_chunk$set(echo = F, warning = F, error = F, message = F)
knitr::include_graphics("images/imagen_curso.png")
Este curso pretende ser una introducción básica del R
. Su objetivo consiste en enseñar habilidades en la limpieza, manipulación y visualización de datos mediante el uso del R
. Tenga en cuenta que este curso no pretende ser un sustituto de un curso completo de programación o de estadística.
knitr::include_graphics("images/1ra_imagen.png")
A lo largo del curso podrá encontrar fragmentos de código para realizar ejemplos y ejercicios de codificación. Abajo encontrara como podrán ser usados:
:::example
Los ejemplos contienen código previamente escrito para que pueda explorarlo y ejecutarlo presionando el botón amarillo "Run Code" (ejecutar) en la esquina superior derecha. Puede actualizar el código en cualquiera de los ejemplos presionando "Start Over" (iniciar de nuevo) en la esquina superior izquierda y el código se restablecerá automáticamente al valor predeterminado. :::
:::exercise
Aquí se indican ejercicios prácticos de codificación donde se le pedirá que escriba el código manualmente. Si no da con la solución, puede buscar ayuda dando clic en los botones "Solution" (solución) o "Hints" (sugerencias). En la mayoría de los casos el código podrá ser evaluado con el botón "Submit Answer" (Enviar Respuesta), indicándole cuando la respuesta sea acertada. :::
#Ejercicio_código
También se encontrara con el siguiente bloque informativo:
:::caution
En ocasiones se dara alguna recomendación sobre un tema. Este bloque se usará para llevar a cabo esto. :::
No dude en comunicarse con nosotros por correo electrónico si tiene preguntas acerca del curso y su contenido.
Tenga en cuenta que este curso está bajo la licencia Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. Por tanto el contenido del curso debe acreditarse a sus autores.
R
?R
es un lenguaje de programación creado por Robert Gentleman y Ross Ihaka en el año de 1992. Ambos creadores le dieron el nombre de R
al lenguaje implementado por las iniciales de sus nombres (a modo de broma).
knitr::include_graphics("images/Creadores.jpg")
Se trata de un lenguaje interpretado o de script, con tipado dinámico, multiplataforma y orientado a objetos:
Lenguaje interpretado o de script: se ejecuta utilizando un programa intermedio llamado intérprete (IDE).
Orientado a objetos: R
realiza acciones sobre objetos (un número, un conjunto de datos, una palabra o un resumen estadístico como una media o varianza).
:::example
:::
a <- 12 b <- c(4, 6, 8, 12) c <- matrix(data = 1:4, nrow = 2, ncol = 2)
:::caution
Para crear nuevos objetos en R
, se debe hacer la asignación del objeto. La asignación de objetos es la forma de almacenar información. Para hacer una asignación, se suele emplear el simbolo <-. Por ejemplo la notación a <- ..., asigna ... (lo que se quiere almacenar) al objeto a.
:::
Multiplataforma: el intérprete de R
está disponible en muchas plataformas (Linux, Windows, UNIX, Mac OS, etc), por lo que podrá ejecutarse en muchos computadores.
Tipado dinámico: se refiere a que no es necesario
declarar el tipo de dato. A su vez, R
es fuertemente tipado.
:::example
:::
a <- 'Hola' typeof(x = a) b <- 1L typeof(x = b) c <- 12.3 typeof(x = c) d <- 1 + 2i typeof(x = d) e <- c(FALSE, TRUE) typeof(x = e)
R
?El lenguaje de programación R
se puede obtener y distribuir de forma gratuita, debido a que se encuentra bajo Licencia Pública General del proyecto GNU. Por lo tanto es un programa de código abierto y gratis.
Finalmente, el uso del R
garantiza que otro investigador pueda repetir el experimento, comprobar los resultados obtenidos y estar en la condición de ampliar o refutar las interpretaciones del estudio realizado.
knitr::include_graphics("images/al_principio_pero_ahora.jpg")
¡No se desanime! Los lenguajes de programación como el R
no son sencillos de aprender, pero con trabajo y motivación vera como en poco tiempo se lograrán grandes avances.
R
base y RStudioR
base es el software básico que contiene el lenguaje de programación R
. RStudio es un software que facilita la programación en R
.
R
base:La instalación de R
base tanto en Windows como en Linux se realiza a través de la CRAN (Comprehensive R Archive Network).
Ejemplo de consola del
R
knitr::include_graphics("images/consola_R_base.jpg")
knitr::include_graphics("images/source_RStudio.jpg")
:::caution
Un texto insertado en el código el cual es omitido en la ejecución, se denomina como comentario. En R
, un comentario es un texto que comienza con el símbolo # y se extiende hasta el final de la línea. Este permite que R
no intente interpretarlo como parte del código.
:::
R
knitr::include_graphics("images/console.jpg")
Ejemplo de ambiente del
R
knitr::include_graphics("images/environment.jpg")
Ejemplo de historial del
R
knitr::include_graphics("images/history.jpg")
Ejemplo de archivos del
R
knitr::include_graphics("images/files.jpg")
Ejemplo de gráficos del
R
knitr::include_graphics("images/plots.jpg")
Ejemplo de ayuda del
R
knitr::include_graphics("images/help.jpg")
Ejemplo de paqutes del
R
knitr::include_graphics("images/packages.jpg")
R
install.packages('tidyr', 'dplyr', 'ggplot2')
2) Mediante el uso del paquete devtools
, el cual permite instalar paquetes alojados en distintos servidores:
install_bioc()
desde Bioconductor.install_cran()
desde CRAN.install_git()
desde un repositorio git.install_github()
desde GitHub.:::example
:::
install.packages('devtools') devtools::install_github('tidyverse/dplyr')
Una vez haya instalado un paquete, estará en la computadora. Si se desea usar una función o un conjunto de datos del paquete instalado, debe cargar el paquete en la sesión de R
. Para esto, existen dos formas:
1) Con la notación nombre_paquete::nombre_función()
para hacer un uso temporal de la función o conjunto de datos.
:::example
:::
dplyr::starwars
2) Cargándolo en la memoria del computador mediante el uso de la función library()
.
:::example
:::
library(dplyr) Datos_starwars <- starwars
Para acceder a la descripción de un paquete desde R
se puede emplear las funciones packageDescription()
y help()
.
:::example
:::
packageDescription('dplyr') #help(package = 'dplyr')
Otras funciones que permiten la gestión de los paquetes instalados en el computador:
installed.packages() # Para ver que paquetes se tienen instalados. remove.packages('dplyr') # Si se desease eliminar un paquete (en este caso el paquete dplyr). old.packages() # Para comprobar que paquetes necesitan ser actualizados. update.packages() # Para actualizar todo los paquetes instalados.
Un directorio de trabajo es el lugar en la computadora en el que se encuentran los archivos con los que se esta trabajando. Es el lugar donde el programa R
buscara los archivos para importarlos y al que serán exportados.
knitr::include_graphics("images/59va_imagen.png")
Con la función getwd()
se puede encontrar el directorio en el que se esta trabajando.
:::example
:::
getwd() # Da como resultado la ruta "/home/leo/Escritorio/github/Un_curso_amigable_sobre_R".
Para cambiar el directorio de trabajo se puede emplear la función setwd()
, dando como argumento la ruta donde se encuentra el nuevo directorio de trabajo.
:::example
:::
setwd(dir = "/home/leo/Escritorio/Curso_estadística") # Se cambia el directorio de trabajo a la carpeta Curso_estadistica.
Sin embargo como señala Jenny Bryan, con la función setwd()
es practicamente imposible para cualquier otra persona que no sea el autor original del código R
, en su computadora, hacer que las rutas de archivo funcionen. La solución consistiría en trabajar con proyectos.
La tabla a continuación presenta algunas funciones útiles para administrar el directorio de trabajo:
knitr::include_graphics("images/60va_imagen.png")
Además de usar proyectos, también es una buena práctica estructurar el directorio de una manera que ayude a cualquier persona con la que se esta colaborando, o una versión futura de usted intente reproducir algunos análisis.
knitr::include_graphics("images/2da_imagen.png")
knitr::include_graphics("images/38va_imagen.png")
Los escalares son el tipo de objeto más simple en R
. Un objeto escalar es un objeto de un único valor, sin embargo estos se pueden clasificar en cinco clases (clases atómicas):
character
(caracteres)integer
(números enteros)double
(números reales)complex
(números complejos)logical
(lógicos o boleanos):::example
:::
a <- 'Hola' typeof(x =a) b <- 1L typeof(x = b) b_2 <- 1 typeof(x = b_2) c <- 12.3 typeof(x = c) d <- 1 + 2i typeof(x = d) e <- c(FALSE, TRUE) typeof(x = e)
:::caution
En R
existen las llamadas palabras clave o palabras reservadas. Estas no se deben usar como nombres ni para crear objetos, ni para crear funciones.
:::
Un vector consiste en una colección de escalares de la misma clase atómica, ya sean estos números, caracteres o lógicos, pero nunca podrán ser de dos o más clases diferentes.
Hay muchas formas de crear vectores en R
. A continuación podrá encontrar una tabla con ejemplos de estas formas:
knitr::include_graphics("images/39va_imagen.png")
:::example
:::
num <- c(1, 2, 3, 4, 5) num let <- c('a', 'b', 'c', 'd', 'e', 'f') let num_2 <- 1:10 num_2 num_3 <- 10:1 num_3 num_4 <- seq(from = 0, to = 100, by = 10) num_4 num_5 <- rep(x = 4, times = 10) num_5 num_6 <- rep(x = c(1, 2), each = 2) num_6
:::caution
Una función en R
es un procedimiento que generalmente toma un objeto como argumento, hace algo con ese objeto y luego devuelve un nuevo objeto.
:::
knitr::include_graphics("images/40va_imagen.png")
Lista de funciones útiles
knitr::include_graphics("images/57va_imagen.png")
:::caution Los argumentos de una función son una serie de valores (objetos o incluso el resultado de otra función) que se pasan dentro de la función para que esta realice un proceso. :::
:::example
:::
args(round) #?round #help(round)
Las operaciones aritméticas más comunes están definidas para vectores. A continuación podrá encontrar una tabla con los operadores básicos:
knitr::include_graphics("images/41va_imagen.png")
:::example
:::
vec_1 <- c(1:8) vec_2 <- 2 sum_vec_1_vec_2 <- vec_1 + vec_2 sum_vec_1_vec_2
:::example
:::
vec_1 <- seq(from = 1, to = 4, by = 1) vec_2 <- rep(x = 2, times = 4) div_vec_1_vec_2 <- vec_1 / vec_2 div_vec_1_vec_2
Debido a que el R
es un lenguaje creado para estadísticas, el mismo contiene muchas funciones básicas que permiten calcular estadísticas descriptivas a partir de un vector de datos. A continuación podrá encontrar una tabla de estas funciones:
knitr::include_graphics("images/42va_imagen.png")
:::example
:::
Pesos_rn <- rnorm(n = 12, mean = 3768, sd = 572) Pesos_rn min(x = Pesos_rn) mean(x = Pesos_rn) sd(x = Pesos_rn) quantile(x = Pesos_rn) summary(object = Pesos_rn)
:::caution
En el R
los datos faltantes se identifican como NA
(not available). La mayoría de las funciones estadísticas en R
en presencia de valores NA
pueden generar resultados inconsistentes.
:::
:::example
:::
val_NA <- c(1, 5, NA, 2, 10) mean(val_NA) #mean(val_NA, na.rm = TRUE)
Dentro de los múltiples objetos en R
, como los vectores en este caso, se debe tener claro como manejar o acceder a la información contenida en los mismos. Para estos casos se utiliza la indexación.
La indexación consiste en la selección de subconjuntos de datos por medio de operadores de selección, que en el caso de los vectores corresponde al operador [ ]
. Existen dos tipos de indexación:
:::caution
La posición de los valores de un vector se pueden identificar mediante su índice. En el caso del lenguaje de programación R
, el tipo de indexación es en base-uno ([1]).
:::
:::example
:::
seq(from = 1, to = 100, by = 1)
La indexación numérica permite acceder a los valores de un vector de acuerdo a su posición especifica dentro del vector. Para esto, se emplea la forma a[index], donde a es el nombre del vector e index es es el índice del vector.
:::example
:::
indice <- seq(from = 1, to = 100, by = 1) indice[44] indice[2:10] indice[c(4, 4, 4)] seleccion <- 2:8 indice[seleccion]
:::example
:::
fechas <- c('Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic') fechas[c(1, 6, 11)] fechas[-c(2:5, 7:10, 12)]
:::caution
En el lenguaje de programación R
existen los denominados operadores lógicos. Estos pueden ser usados para hacer comparaciones u operaciones lógicas cuyo resultado del mismo será un vector lógico (TRUE
y FALSE
).
:::
knitr::include_graphics("images/44va_imagen.png")
:::example
:::
numeros <- seq(from = 1, to = 40, by = 1) numeros >= 34
La segunda forma de indexar vectores es mediante el uso de vectores lógicos (vector que solo contiene valores TRUE
y FALSE
como el ejemplo anterior).
:::example
:::
knitr::include_graphics("images/43va_imagen.png")
ind_logica <- c(4, 8, -1, 9, -3) ind_logica[ind_logica > 0] ind_logica[!(ind_logica < 0)]
:::example
:::
meses <- c('Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic') meses[meses == 'Nov'] meses[meses != 'Nov']
:::example
:::
numeros <- seq(from = -10, to = 10, by = 1) numeros[numeros >= 0 & numeros != 4]
knitr::include_graphics("images/46va_imagen.png")
Las matrices y los conjuntos de datos son dos tipos de objetos que representan estructuras de datos grandes. Si bien las matrices y los conjuntos de datos se ven muy similares, dada su estructura tabular, no son exactamente lo mismo: las matrices pueden contener solo una clase de datos (ya sea numérico o carácter), mientras que los conjuntos de datos pueden contener columnas tanto numéricas como de caracteres.
Hay muchas formas de crear matrices y conjuntos de datos en R
. A continuación podrá encontrar una tabla de estas formas:
knitr::include_graphics("images/45va_imagen.png")
:::example
:::
x <- 1:5 y <- 6:10 z <- 11:15 x_y_z <- cbind(x, y, z) x_y_z x_y_z <- rbind(x, y, z) x_y_z
:::example
:::
x <- c(1, 2, 3, 4, 5) y <- c('a', 'b', 'c', 'd', 'e') x_y <- cbind(x, y) x_y
:::caution
Recuerden, las matrices pueden contener números o caracteres, ¡no ambos!. Si intenta crear una matriz con números y caracteres, el R
convertirá todos los números en caracteres (coerción implicita).
:::
knitr::include_graphics("images/58va_imagen.png")
:::example
:::
matrices_1 <- matrix( data = 1:15, nrow = 5, ncol = 3 ) matrices_1 matrices_2 <- matrix( data = 1:15, nrow = 5, ncol = 3, byrow = TRUE ) matrices_2
:::example
:::
con_dtos <- data.frame( 'Identificación' = 1:5, 'Sexo' = c('Mujer', 'Mujer', 'Hombre', 'Mujer', 'Hombre'), 'Edad' = c(99, 34, 43, 50, 88) ) con_dtos
R
y disponibes en paquetes de R
El lenguaje de programación R
cuenta con conjuntos de datos preinstalados en un paquete llamado datasets
, el cual no requiere ser instalado una vez el mismo viene incluido en el software R
base. Una lista completa de estos conjuntos de datos incluidos en el paquete datasets
se puede ver al ejecutar el código library(help = 'datasets')
.
A continuación podrá encontrar una tabla del nombre de algunos de estos conjuntos de datos acompañado de una pequeña descripción:
knitr::include_graphics("images/47va_imagen.png")
:::example
:::
data('iris')
También pueden existir conjuntos de datos disponibles dentro de paquetes del R
. En la tabla a continuación podrá encontrar el nombre de algunos de estos conjuntos de datos acompañado del nombre del paquete donde se encuentra alojado y una pequeña descripción:
knitr::include_graphics("images/48va_imagen.png")
:::example
:::
data(package = 'dplyr') #library(dplyr) #data('starwars')
El lenguaje de programación R
proporciona operadores o funciones útiles para trabajar con matrices. En la tabla a continuación podrá encontrar un ejemplo del uso de estas funciones:
knitr::include_graphics("images/49va_imagen.png")
:::example
:::
A <- matrix(data = 1:9, nrow = 3, ncol = 3, byrow = TRUE) a <- c(4, 5, 4) b <- c(3, 4, 4) d <- c(8, 7, 7) B <- rbind(a, b, d) M_1 <- A + B M_1 M_2 <- A %*% B M_2 M_3 <- 4 * A M_3
:::example
:::
Juan <- c(4.4, 3.8, 4.2, 3.4) Andres <- c(3.2, 3.6, 4.4, 4.6) Laura <- c(4.1, 3.4, 3.2, 5.0) Notas <- rbind(Juan, Andres, Laura) Prom_persona <- rowMeans(x = Notas) Prom_persona
El lenguaje de programación R
contiene también funciones para ver matrices y conjuntos de datos y proporcionar información sobre ambos tipos de objetos. En la tabla a continuación podrá encontrar el nombre de estas funciones acompañada de una pequeña descripción:
knitr::include_graphics("images/50va_imagen.png")
Para explicar tanto las funciones anteriores como las que se veran posteriormente en lo que resta de este capitulo, se empleara el conjunto de datos Starwars
:
data("starwars") Starwars <- starwars %>% select(-c(12:14)) Starwars <- as.data.frame(Starwars)
save(Starwars, file="data_pkg/Starwars.rda", compress='xz')
#library(CursoR) #data('Starwars')
knitr::include_graphics("images/51va_imagen.png")
:::example
:::
head(x = Starwars) tail(x = Starwars) #View(x = Starwars)
Ejemplo de lo que se observa con
View()
knitr::include_graphics("images/View_star_wars.png")
:::example
:::
nrow(x = Starwars) ncol(x = Starwars) dim(x = Starwars)
:::example
:::
summary(object = Starwars)
Al igual que con los vectores, tanto en matrices como en conjunto de datos el lenguaje de programación R
permite usar el operador [ ]
para acceder a valores específicos dentro de los mismos, y al igual que con los vectores, la indexación de matrices y conjuntos de datos pueden ser de dos tipos:
En matrices y conjuntos de datos, y a diferencia de lo visto con vectores, el operador [ ]
requiere de dos argumentos para acceder a los valores de acuerdo a su posición: el primer argumento el cual se aplica a las filas y el segundo a las columnas.
:::example
:::
A <- matrix(data = 1:9, nrow = 3, ncol = 3) A A[3, ] A[, 3]
:::caution
Al usar una coma (,
) dentro del operador [ ]
se esta dando la instrucción al R
de buscar los valores solicitados en más de una dimensión. Así, el número antes de la coma será buscado en la primera dimensión del objeto, y el número después de la coma en la segunda dimensión.
:::
:::example
:::
a <- c(3, 2, 4, 6, 5, 1) a[2, 4] #a[c(2, 4)]
:::exercise
Por favor, haciendo uso del conjunto de datos Starwars
intente seleccionar las primeras 5 columnas de este conjunto de datos, luego seleccionar las primeras 20 filas de la primera columna y por último seleccionar de forma conjunta las columnas 2 y 3, y las filas 20 a la 40.
:::
# Primeras 5 columnas ---- Starwars[] # Primeras 20 filas de la columna 1 ---- Starwars[] # Filas de la 20 a la 40 de las columnas 2 y 3 ---- Starwars[]
# Primeras 5 columnas ---- Starwars[, 1:5] # Primeras 20 filas de la columna 1 ---- Starwars[1:20, 1] # Filas de la 20 a la 40 de las columnas 2 y 3 ---- Starwars[20:40, c(2, 3)]
grade_code("¡Excelente lo lograste, sigamos adelante!")
:::example
:::
names(Starwars) # Primeras 5 columnas ---- Starwars[, c('name', 'height', 'mass', 'hair_color', 'skin_color')] # Primeras 20 filas de la columna 1 ---- Starwars[1:20, 'name'] # Filas de la 20 a la 40 de las columnas 2 y 3 ---- Starwars[20:40, c('height', 'mass')]
:::example
:::
M <- matrix(seq(from = 18, to = 2, length.out = 16), ncol = 4, byrow = TRUE) M t_col <- colSums(m) t_col porc_M <- cbind( m[, 1] / t_col[1], m[, 2] / t_col[2], m[, 3] / t_col[3], m[, 4] / t_col[4] ) porc_M <- round(porc_M * 100, 2) porc_M diag(porc_M) # Vector con los resultados de la diagonal (1ra forma) c(porc_M[1, 1], porc_M[2, 2], porc_M[3, 3], porc_M[4, 4]) # Vector con los resultados de la diagonal (2da forma)
Indexar matrices y conjuntos de datos mediante el uso de vectores lógicos es muy similar a lo visto con vectores:
1- Se crea un vector lógico que contiene solo valores TRUE
y FALSE
.
2- Usando el vector lógico se indexa la matriz o conjunto de datos, devolviendo solo aquellos valores para los que dicho vector lógico es igual a TRUE
.
:::example
:::
Starwars[, 'species'] == 'Human' #Starwars[Starwars[, 'species'] == 'Human', ]
$
El operador $
permite acceder a una columna especifica en una matriz o conjuntos de datos mediante el nombre de las columnas de la forma df$name
, donde df
es el nombre del objeto y name
es el nombre de la columna. De esta forma, este operador establece una forma de tener acceso a los valores de las matrices y conjuntos de datos mediante los nombres de las variables o columnas.
:::example
:::
#Starwars[Starwars[, 'species'] == 'Human', ] Starwars[Starwars$species == 'Human', ]
El operador $
no solo permite acceder a los valores de la matriz y conjunto de datos, sino que también permite crear nuevas columnas o variables.
:::example
:::
Starwars$numero <- 1:nrow(Starwars) Starwars
subset()
La función subset()
es una de las funciones de manejo de datos más útiles en R
. Esta función permite extraer subconjuntos de datos como se haría con el operador [ ]
mediante los diferentes tipos de indexación ya vistos, pero el código es mucho más fácil de escribir.
La tabla a continuación muestra los argumentos principales de esta función:
knitr::include_graphics("images/52va_imagen.png")
:::example
:::
#Starwars[Starwars[, 'species'] == 'Human', ] #Starwars[Starwars$species == 'Human', ] subset( x = Starwars, subset = species == 'Human' )
:::example
:::
subconjunto <- subset( x = Starwars, subset = species == 'Human' & homeworld != 'Tatooine' ) subconjunto
:::example
:::
prom_peso <- subset( x = Starwars, subset = species == 'Human' & homeworld == 'Tatooine' ) mean(x = prom_peso$mass, na.rm = TRUE)
Frecuentemente, los conjuntos de datos que seran leidos en el programa R
se almacenan en dos formatos: archivos de Excel o similares con extensión .csv o archivos de texto plano con extensión .txt.
Ejemplo de almacenamiento en .csv
Ejemplo de almacenamiento en .txt
Independientemente del tipo de formato con que se guarde el conjunto de datos, se puede emplear una de las funciones incluidas en el software R
base para llevar a cabo la lectura de dicho conjunto de datos: la función read.table()
.
La tabla a continuación muestra los argumentos principales de esta función:
knitr::include_graphics("images/54va_imagen.png")
:::example
:::
#read.table(file = 'datos_csv.csv', header = TRUE, sep = ',') #read.table(file = 'datos_txt.txt', header = TRUE, sep = '\t') datos_csv <- data.frame( 'Edad' = c(34, 25, 28, 19), 'Deporte' = c(TRUE, TRUE, FALSE, FALSE), 'Fuma' = c(FALSE, TRUE, FALSE, TRUE), 'Ciudad' = c('Medellin', 'Bogota', 'Cerete', 'Pasto') ) str(datos_csv)
Ejemplo de lectura de datos
install.packages('tibble') library(tibble)
Las ventajas de los tibbles son:
:::example
:::
datos_csv <- data.frame( 'Edad' = c(34, 25, 28, 19), 'Deporte' = c(TRUE, TRUE, FALSE, FALSE), 'Fuma' = c(FALSE, TRUE, FALSE, TRUE), 'Ciudad' = c('Medellin', 'Bogota', 'Cerete', 'Pasto') ) datos_tibble <- tribble( ~Edad, ~Deporte, ~Fuma, ~Ciudad, 34, TRUE, FALSE, 'Medellin', 25, TRUE, TRUE, 'Bogota', 28, FALSE, FALSE, 'Cerete', 19, FALSE, TRUE, 'Pasto' )
R
, se especifica la clase atómica de cada una de las variables o columnas del conjunto de datos::::example
:::
datos_csv <- data.frame( 'Edad' = c(34, 25, 28, 19), 'Deporte' = c(TRUE, TRUE, FALSE, FALSE), 'Fuma' = c(FALSE, TRUE, FALSE, TRUE), 'Ciudad' = c('Medellin', 'Bogota', 'Cerete', 'Pasto') ) datos_csv datos_tibble <- tribble( ~Edad, ~Deporte, ~Fuma, ~Ciudad, 34, TRUE, FALSE, 'Medellin', 25, TRUE, TRUE, 'Bogota', 28, FALSE, FALSE, 'Cerete', 19, FALSE, TRUE, 'Pasto' ) datos_tibble
Ejemplo observado en la consola
character
(caracter) a Factor
(factor). :::example
:::
datos_csv <- data.frame( 'Edad' = c(34, 25, 28, 19), 'Deporte' = c(TRUE, TRUE, FALSE, FALSE), 'Fuma' = c(FALSE, TRUE, FALSE, TRUE), 'Ciudad' = c('Medellin', 'Bogota', 'Cerete', 'Pasto') ) str(datos_csv) datos_tibble <- tribble( ~Edad, ~Deporte, ~Fuma, ~Ciudad, 34, TRUE, FALSE, 'Medellin', 25, TRUE, TRUE, 'Bogota', 28, FALSE, FALSE, 'Cerete', 19, FALSE, TRUE, 'Pasto' ) str(datos_tibble)
:::caution
La coerción es una característica del lenguaje de programación R
que permite, implícita o explícitamente, convertir un elemento de una clase de datos en otra. Para ello se cuenta con funciones del tipo as.*
:::
knitr::include_graphics("images/56va_imagen.png")
:::example
:::
datos_csv <- as_tibble( data.frame( 'Edad' = c(34, 25, 28, 19), 'Deporte' = c(TRUE, TRUE, FALSE, FALSE), 'Fuma' = c(FALSE, TRUE, FALSE, TRUE), 'Ciudad' = c('Medellin', 'Bogota', 'Cerete', 'Pasto') ) ) str(datos_csv)
:::example
:::
amigos <- tribble( ~nombre, ~fisico, ~sentimental, 'Andres', c(edad = 23, altura = 181, masa = 98), c(soltero = TRUE, hijos = FALSE), 'Julian', c(edad = 24, altura = 179, masa = 75), c(soltero = FALSE, hijos = TRUE), 'Maria', c(edad = 19, altura = 167, masa = 71), c(soltero = TRUE , hijos = FALSE), 'sandra', c(edad = 21, altura = 174, masa = 68), c(soltero = TRUE, hijos = FALSE), 'Karol', c(edad = 26, altura = 171, masa = 70), c(soltero = FALSE, hijos = TRUE), 'Javier', c(edad = 18, altura = 164), masa = 64, c(soltero = TRUE, hijos = FALSE) ) amigos
knitr::include_graphics("images/53va_imagen.png")
Las listas son como los vectores debido a que almacenan datos en una estructura unidimensional. Sin embargo, las listas a diferencia de los vectores no almacenan valores individuales sino que almacenan objetos, como escalares, conjuntos de datos, funciones e inclusive otras listas. Para crear una lista se emplea la función list()
.
:::example
:::
lista_1 <- list( L_1 = 40:80, L_2 = 'Hola', L_3 = list(VERDADERO = TRUE, FALSO = FALSE) ) lista_1
Si se desea extraer los elementos almacenados en una lista, se pueden usar los operadores $
, [[ ]]
o [ ]
.
:::example
:::
lista_1 <- list( L_1 = 40:80, L_2 = 'Hola', L_3 = list(VERDADERO = TRUE, FALSO = FALSE) ) lista_1$L_2 # Si queremos extraer el vector con el nombre "L_2" dentro del objeto "lista_1". lista_1[[2]] # Si queremos extraer el vector con el nombre "L_2", pero esta vez no por su nombre sino por su posición dentro del objeto "lista_1". lista_1$L_3[2]
:::example
:::
amigos <- tribble( ~nombre, ~fisico, ~sentimental, 'Andres', c(edad = 23, altura = 181, masa = 98), c(soltero = TRUE, hijos = FALSE), 'Julian', c(edad = 24, altura = 179, masa = 75), c(soltero = FALSE, hijos = TRUE), 'Maria', c(edad = 19, altura = 167, masa = 71), c(soltero = TRUE, hijos = FALSE), 'sandra', c(edad = 21, altura = 174, masa = 68), c(soltero = TRUE, hijos = FALSE), 'Karol', c(edad = 26, altura = 171, masa = 70), c(soltero = FALSE, hijos = TRUE), 'Javier', c(edad = 18, altura = 164, masa = 64), c(soltero = TRUE, hijos = FALSE) ) # Selección de los elementos del anterior tibble ---- amigos$fisico[[3]][1] # Edad de Maria. amigos$fisico[[2]][1] # Edad de Julian. amigos$sentimental[[6]][2] # Hijos de Javier.
:::exercise
Sabiendo que el índice de masa corporal de una persona se calcula con base en la expresión ($IMC = \frac{Masa\ (kg)}{Altura(m)^2}$), intente por favor calcular este párametro para Karol y Javier por medio de indexación. Aquí, la altura esta expresada en centímetros, por lo cual requerirá cambiarse a metros. :::
amigos <- tribble( ~nombre, ~fisico, ~sentimental, 'Andres', c(edad = 23, altura = 181, masa = 98), c(soltero = TRUE, hijos = FALSE), 'Julian', c(edad = 24, altura = 179, masa = 75), c(soltero = FALSE, hijos = TRUE), 'Maria', c(edad = 19, altura = 167, masa = 71), c(soltero = TRUE, hijos = FALSE), 'sandra', c(edad = 21, altura = 174, masa = 68), c(soltero = TRUE, hijos = FALSE), 'Karol', c(edad = 26, altura = 171, masa = 70), c(soltero = FALSE, hijos = TRUE), 'Javier', c(edad = 18, altura = 164, masa = 64), c(soltero = TRUE, hijos = FALSE) ) # Calculo del IMC del anterior tibble ---- amigos$fisico[[5]][3] / (amigos$fisico[[5]][2] / 100)^2 # IMC de Karol amigos$fisico[[6]][3] / (amigos$fisico[[6]][2] / 100)^2 # IMC de Javier
Un arreglo es una matriz n-dimensional. Por lo tanto los arreglos, al igual que en una matriz, se pueden almacenar datos de una sola clase atómica. Para construir una arreglo se usa la función array()
.
:::example
:::
arreglo_1 <- array( data = c(11:14, 21:24, 31:34), dim = c(2, 2, 3) ) arreglo_1
Si se desea extraer los elementos almacenados en un arreglo, se usa el operador [ ]
al igual que en una matriz.
:::example
:::
arreglo_1 <- array( data = c(11:14, 21:24, 31:34), dim = c(2, 2, 3) ) arreglo_1[2, 1, 3] # Si se quiere extraer el número almacenado en la fila 2 y la columna 1 de la tercera matriz. arreglo_1[, , 2] # Si se quiere extraer la segunda matriz completa. arreglo_1[, 2, ] # Si se quiere extraer la columna 2 de todas las matrices. arreglo_1[1, , ] # Si se quiere extraer la fila 1 de todas las matrices.
En esta sección aprenderemos a crear y utilizar estructuras de control como funciones de usuario, bucles y condicionales que nos permitan automatizar nuestra manipulación y análisis de datos. Además esta sección nos permitirá entender cómo trabajan algunas de las funciones que conocemos, dejándonos entender y manipular mejor el R
como lenguaje de programación. Cuando utilizamos programación para resolver algún problema académico, laboral u de otra índole es común encontrarnos con particularidades que corresponde únicamente a nuestro problema, por ello en muchos casos funciones generales o procesos generales no aplican para nuestra situación, es allí donde entran las famosas funciones de usuario que conoceremos a lo largo de la sección. La sección cuenta con cuatro subsecciones 1) Condicionales, 2) Bucles, 3) Funciones y 4) Familia apply
.
Algunas de las funciones nuevas que utilizaremos en esta sección deberás consultar en la ayuda de R
, cada vez que no entiendas un función que utilicemos puedes ejecutar un comando de la forma ?mean()
Para los ejercicios de esta sección existen muchas posibles soluciones que realicen la misma tarea planteada, sin embargo en Solution
encontrarás solo una de todas las posibles.
3.1 Condicionales
Son conocidos como construcciones y se articulan con los operadores lógicos, de conjunción y de disyunción. Utilizan expresiones lógicas para denotar diferentes alternativas, es decir, funcionan como una condición a partir de la expresión lógica. La primera función que utilizaremos será if(){}
acompañada de print()
. Para ello recordemos con un ejemplo expresiones lógicas que ya conocemos y agreguemos la sintaxis de una condición para utilizar estas nuevas funciones.
:::example
:::
#Expresiones logicas 35 > 20 35 != 35.4 #Condiciones if(35>20) {print("Es mayor a 20")} if(35 != 35.4) {print("Son diferentes")}
Como vimos con el ejemplo la condición evalúa la expresión lógica correspondiente, en el primer caso, si 35 es mayor a 20 imprime “Es mayor”, en el segundo si 35 es diferente a 35.4 imprime “Son diferentes”. Esta sintaxis aplica para cualquier condición si la expresión lógica se cumple realice determinada acción
, en nuestro caso la acción es imprimir, pero podemos ejecutar cualquier acción que necesitemos.
Veamos qué acciones podemos ejecutar si nuestra expresión lógica no se cumple, para esto nos valdremos del argumento else{}
. Utilicemos uno de los ejemplos anteriores para visualizarlo.
:::example
:::
#Se cumple la expresion logica if(35>20) { print("Es mayor a 20") } else { print("Es menor a 20") } #No se cumple la expresion logica if(10>20) { print("Es mayor a 20") } else { print("Es menor a 20") }
La sintaxis para este caso tendría una modificación, siendo expresada por si expresión lógica se cumple realice determinada acción, de lo contrario realice otra acción
, en este caso en especifico seria, si determinado numero es mayor a 20 imprima es mayor de lo contrario imprima es menor. Podríamos además agregar una condición extra en la que verifiquemos si el número es igual a 20, para ello podemos valernos del argumento else if{}
. Para evidenciar su funcionamiento te invito a que cambies el valor de x
por distintos números incluyendo el 20.
#para cada modificacion de x ejecute "Run code" x = 18 if(x>20) { print("Es mayor a 20") } else if (x==20){ print("Es igual a 20") }else { print("Es menor a 20") }
A continuación trabajaremos con acciones diferentes a print()
y utilizaremos conceptos de operaciones básicas que manejamos anteriormente, usando además una función nueva denominada paste()
. Para este ejemplo x
es el número de rosas compradas en una floristería, en la cual se tiene una oferta, si se compran más de 10 rosas se regalan 3 rosas, si se compran exactamente 10 se regala una rosa, de lo contrario el cliente solo se lleva las rosas que compro. Para evidenciar su funcionamiento te invito a que cambies el valor de x
por distintos números.
:::example
:::
#para cada modificacion de x ejecute "Run code" x = -13 if(x<=0) { paste(x,"no es un número de compra valido") } else if (x==10){ print("Llevarás una rosa extra") paste("Total de Rosas:",x+1) } else if(x>10){ print("Llevarás tres rosas extra") paste("Total de Rosas:",x+3) }else { print("Llevarás las rosas que compraste") paste("Total de Rosas:",x) }
Trabajemos ahora un ejercicio similar, en el cual hay una oferta del 50% en bolsas de comida de perro y un descuento del 10% adicional si se compran por lo menos 10 bolsas de comida. El precio normal de una bolsa de comida son 6800 pesos, x
corresponde al número de bolsas de comida. Si se ejecuta el código con x=0
se debe imprimir en pantalla "No es un número de compra válido" , con x=8
"Total a pagar: 27200" y con x=20
"Total a pagar: 54400".
:::exercise
:::
#para cada modificacion de x ejecute "Run code" x <- 0 precio = 6800
#para cada modificacion de x ejecute "Run code" x <- 0 precio = 6800 if(x<= 0){ paste("No es un número de compra válido") } else if (x<10){ cobro = x*.5*precio paste("Total a pagar:",cobro) } else{ cobro = x*.4*precio paste("Total a pagar:",cobro) }
grade_code("Excelente lo lograste, sigamos adelante")
Ya vimos como trabajar con condiciones numéricas y algunos operadores lógicos, veamos cómo trabajar con coincidencias exactas con caracteres. Retomemos el ejercicio anterior, añadiendo que los descuentos solo aplican en dias especificos, para diferenciar cuando se aplica o no el descuento se creo la variable des
que puede ser “Hay descuento” o “No hay descuento”, veamos como funcionaria con esta nueva condición al variar los valores de x
y cambiar los valores de des
por las dos opciones válidas.
:::example
:::
#para cada modificacion de x y/o des ejecute "Run code" x <- 10 precio = 6800 des = "Hay descuento" #Cuando no hay descuento if(des == "No hay descuento"){ if(x<= 0){ paste("No es un número de compra válido") } else{ cobro = x * precio paste("Total a pagar:",cobro) } #Cuando hay descuento } else if(des == "Hay descuento"){ if(x<= 0){ paste("No es un número de compra válido") } else if (x<10){ cobro = x*.5*precio paste("Total a pagar:",cobro) } else{ cobro = x*.4*precio paste("Total a pagar:",cobro) } #Opcion de clasificación no valida } else { paste(des,"No es un opción válida de clasificación de descuento") }
Este ejemplo contiene una condición principal, que es si hay descuento o no; dependiendo de ella se despliega el cobro con o sin descuento con condiciones para cada caso, esta forma de incluir condiciones dentro de otras condiciones se conoce como anidar y aplica también para funciones, procesos y bucles. Para solucionar problemas con condicionales podemos apoyarnos de los diagramas de flujo como el siguiente, que simplifica el proceso cuando las entradas son correctas.
knitr::include_graphics("images/3ra_imagen.png")
Vamos con un ejercicio similar, en el que una heladería ofrece descuentos del 25% sobre el total de la compra los días lunes, martes y miércoles, adicionalmente si la compra supera el valor de 50.000 ofrece un 10% adicional de descuento, los otros días de la semana ofrece un 15% de descuento sobre el total de la compra si esta supera el valor de 60.000. el programa debe arrojar el total, el descuento y el total con descuento en cualquiera de los casos. Si se hacen las siguientes compras el programa debe dar las salidas enunciadas:
"Total con descuento: 59075"
Lunes total de la compra 71350
"Total con descuento: 46377.5"
Miercoles total de la compra 33700
:::exercise
:::
#para cada modificacion del dia y del valor total de la compra ejecute "Run code"
#para cada modificacion del dia y del valor total de la compra ejecute "Run code" #Recuerda esta es solo una posible solucion dia = "Miercoles" total_compra = 33700 if(dia =="Lunes" | dia =="Martes" | dia =="Miercoles" ){ if(total_compra<=0){ print("No es un valor de compra válido") } else if(total_compra>50000){ descuento = total_compra * .35 nuevo_total = total_compra - descuento print(paste("Total:", total_compra)) print(paste("Descuento:", descuento)) print(paste("Total con descuento:",nuevo_total)) } else{ print(paste("Total:", total_compra)) print(paste("Descuento:", total_compra * .25)) print(paste("Total con descuento:",total_compra*.75)) } } else if(dia =="Jueves" | dia =="Viernes" | dia =="Sabado" | dia =="Domingo"){ if(total_compra<=0){ print("No es un valor de compra válido") } else if (total_compra>60000){ print(paste("Total:", total_compra)) print(paste("Descuento:", total_compra * .15)) print(paste("Total con descuento:",total_compra*.85)) } else{ print(paste("Total:", total_compra)) print(paste("Descuento:", total_compra * 0)) print(paste("Total con descuento:",total_compra)) } } else{ print("No es una dia de la semana valido") }
grade_code("Excelente lo lograste, sigamos adelante")
En los anteriores ejemplos debíamos cambiar los valores para cada caso, ahora veamos cómo utilizar el código ágilmente sobre uno o varios vectores para obtener resultados en simultaneo para cada caso. Retomemos entonces el ejemplo 3.1.2 para utilizar la función ifelse()
y comparar varios números al mismo tiempo.
:::example
:::
#vector de prueba numeros <- c(35,10,18,70,80,5,18,34,19) #comparacion ifelse(test = numeros>20,yes = paste("Es mayor a 20"),no = paste("Es menor a 20"))
Veamos la adaptación para el ejemplo con la condición de igualdad con el numero 20.
#vector de prueba numeros <- c(35,10,18,20,70,80,5,18,34,19,20) #comparacion ifelse(test = numeros==20,yes = paste("Es igual a 20"),no = ifelse(numeros>20,paste("Es mayor a 20"),paste("Es menor a 20")))
En ejemplos simples como este es fácil adaptar la función ifelse()
para usarla con vectores, sin embargo ya hemos trabajado ejemplos un poco más elaborados en los que modificamos las variables y obtenemos unas nuevas, para estos últimos esta función es difícil de adaptar y tenemos que valernos de los bucles que aprenderemos en la siguiente subsección.
Para seguir con los condicionales realiza el siguiente ejercicio usando ifelse()
, en él debes encontrar los números pares de un vector de prueba, imprimiendo en pantalla la afirmación “Es par” cuando cumpla la condición, y “No es par” cuando no lo haga.
:::exercise
:::
#vector de prueba vec <- c(20,17,48,03,95,47,97,65,46,24,98,14,86,93,75,200,350,257,301,1,3,5,12,2) #condicion
#vector de prueba vec <- c(20,17,48,03,95,47,97,65,46,24,98,14,86,93,75,200,350,257,301,1,3,5,12,2) #condicion ifelse(vec%%2==0,paste("Es par"),paste("No es par"))
grade_code("Excelente lo lograste, sigamos adelante")
Como último ejercicio de esta subsección debes utilizar ifelse()
para clasificar los números en determinado rango, de tal manera que para los números entre 20 y 60 en el vector de prueba se imprima “Están en el intervalo” y para los que no estén entre 20 y 60 se imprima “No están en el intervalo”.
:::exercise
:::
#vector de prueba vec <- seq(1,100,length.out = 20) #condicion
#vector de prueba vec <- seq(1,100,length.out = 20) #condicion ifelse(vec>20 & vec<60,paste("Está en el intervalo"),paste("No está en el intervalo"))
grade_code("Excelente lo lograste, sigamos adelante")
3.2 Bucles
Los bucles o ciclos son estructuras de control que permiten recorrer los elementos de un objeto iterable (vector, lista, dataframe, tibble, array,...) repitiendo determinado número de veces el bloque de código o instrucción para cada iteración. Las estructuras de control asociadas con los ciclos son:
+------------+-------------------+
| Estructura | Descripción |
+============+===================+
| for()
| Para cada uno en |
+------------+-------------------+
| while()
| Mientras |
+------------+-------------------+
| break
| Interrupción |
+------------+-------------------+
| next
| Siguiente |
+------------+-------------------+
| repeat
| Repetir |
+------------+-------------------+
Para empezar trabajaremos con el ciclo for
ilustrando cómo funcionan las iteraciones en cada elemento del vector letras
el cual posee una longitud de 15.
Para empezar trabajaremos con el ciclo for
viendo la siguiente ilustracion y observando el funcionamiento de las iteraciones en cada elemento en el vector letras
del ejemplo 3.2.1. Recordemos que letras
posee 15 elementos.
knitr::include_graphics("images/for_loops.jpeg")
:::example
:::
#vector letras #ciclo for for (i in 1:length(letras)){ print(paste("la letra en la posición",i,"es",letras[i])) }
Una modificación con la cual podemos obtener el mismo resultado del fragmento de código anterior es utilizando la función seq_along()
de la siguiente forma.
#vector letras #ciclo for for (i in seq_along(letras)){ print(paste("la letra en la posición",i,"es",letras[i])) }
Si la posición no es relevante para nuestras operaciones podemos utilizar el ciclo for
de la siguiente forma, olvidándonos de la longitud y del índice de cada iteración.
#vector letras #ciclo for for (i in letras){ print(i) }
Veamos una modificación del ejemplo anterior en el cual nos interesa obtener los elementos con índices pares del vector letras
.
:::example
:::
#vector letras #ciclo for for (i in seq(2,length(letras),2)){ print(paste("la letra en la posición",i,"es",letras[i])) }
También podemos utilizar condicionales y seq_along()
de una forma estratégica para lograr el mismo objetivo.
#vector letras #ciclo for con condicional for (i in seq_along(letras)){ if(i%%2 == 0){ print(paste("la letra en la posición",i,"es",letras[i])) } }
Hagamos un ejemplo un poco más elaborado usando el ejemplo 3.1.4 del descuento de comida de perro, utilizando en esta ocasión dos vectores, x
vector con distintos números de bolsas de comida (cada número una compra diferente) y des
vector que se señala cuando se aplica descuento y cuando no (1= Hay descuento, 0= No hay descuento). Cabe recordar que las ocasiones en la que se aplica un descuento, este es de un 50% con un 10% adicional si el número de bolsas de comida es mayor a 10. El precio por bolsa es de 6800 pesos.
:::example
:::
#Información x = c(10,0,2,15,13,18,4,9,7,2,8,16) #numero de bolsas des = c(1,0,0,1,0,0,1,1,1,0,0,10) #clasificación #ciclo for for (i in seq_along(x)){ cobro = 6800 * x[i] #cuando no hay descuento if(des[i]==0){ if(x[i]<=0){ print("No es un número de compra válido") } else{ print(paste("Total a pagar:",cobro)) } #cuando hay descuento } else if(des[i]==1){ if(x[i]<=0){ print("No es un número de compra válido") } else if(x[i]<10){ print(paste("Total a pagar:",cobro*.5)) } else{ print(paste("Total a pagar:",cobro*.4)) } } else{ print(paste(des[i],"No es un opción válida de clasificación de descuento")) } }
Ya vimos cómo funciona el ciclo for con vectores, veamos un ejemplo con my_lista
, y obtengamos el resumen con summary()
para cada objeto de la lista.
:::example
:::
for (i in seq_along(my_lista)){ objeto <- my_lista[[i]] print(summary(objeto)) }
Se puede ver que el proceso con las listas es tan simple como con los vectores, por ello en el siguiente ejercicio debes hacer uso de la lista carlos_fam_g
presentada a continuación y obtener todos los nombres de los familiares de Carlos, e imprimirlos en pantalla junto con el tipo de relación que tienen y su edad. Por ejemplo para el padre se debe mostrar en pantalla : “El padre de Carlos es Juan carlos y tiene 55 años”.
+-------------+----------------+------+ | Relación | Nombre | Edad | +=============+================+======+ | Padre | Juan Carlos | 55 | +-------------+----------------+------+ | Madre | Luz Estela | 53 | +-------------+----------------+------+ | Hijo | Juan Camilo | 3 | +-------------+----------------+------+ | Hijo | Juan Fernado | 5 | +-------------+----------------+------+ | Hijo | Juliana | 1 | +-------------+----------------+------+ | Hermano | Andres Felipe | 25 | +-------------+----------------+------+ | Hermano | Maria Camila | 21 | +-------------+----------------+------+
:::exercise
:::
# carlos_fam_g
# carlos_fam_g for (objeto in carlos_fam_g){ for (i in seq_along(objeto[[1]])){ print(paste("Relacion con carlos:",objeto[[1]][i],"su nombre es",objeto[[2]][i], "y tiene",objeto[[3]][i],"años")) } }
grade_code("Excelente lo lograste, sigamos adelante")
Como se puede notar el ejercicio se ve más sencillo de lo que es, veamos otro ejercicio para reforzar el aprendizaje del ciclo for
. Para este ejercicio usaremos los datos amigos
y retomaremos el ejercicio 2.4.1 calculando el IMC ($IMC = \frac{Masa\ (kg)}{Altura(m)^2}$) para todas las personas del conjunto de datos imprimiendo en pantalla una expresión del tipo: “Andres tiene un altura de 1.81 m, una masa de 98 kg y su IMC es 29.91”
:::exercise
:::
# amigos
# amigos for (i in seq_along(amigos$nombre)){ nombre <- amigos$nombre[i] masa <- amigos$fisico[[i]][3][[1]] altura <- amigos$fisico[[i]][2][[1]]/100 IMC <- masa/altura^2 print(paste(nombre,"tiene una altura de",altura,"m, una masa de",masa,"u su IMC es",round(IMC,2))) }
grade_code("Excelente lo lograste, sigamos adelante")
Modifica el código del ejercicio anterior y úsalo para incluir la variable IMC en la lista de fisico del dataset amigos
. Recuerda la indexación y creación de variables tanto en los dataframe como en las listas.
:::exercise
:::
# amigos
# amigos for (i in seq_along(amigos$nombre)){ masa <- amigos$fisico[[i]][3][[1]] altura <- amigos$fisico[[i]][2][[1]]/100 IMC <- masa/altura^2 amigos$fisico[[i]][4] <- round(IMC,2) names(amigos$fisico[[i]]) <- c("edad","altura","masa","IMC") } amigos$fisico
grade_code("Excelente lo lograste, sigamos adelante")
Ya sabemos un poco mas de como acceder y crear variables con el ciclo for
, ahora tendrás que usarlo para crear una variable adicional en el dataset amigos
. La nueva variable se obtuvo de preguntarle a los personajes cuantos meses llevaban solteros, sin embargo se descubrió que los personajes que no están solteros mintieron al respecto. Las respuestas están alojadas en el vector tsoltero
y tu tarea es crear la variable adicional tiempo_soltero
en el dataset amigos, asignando a los mentirosos el valor de NA
.
:::exercise
:::
# amigos tsoltero <- c(4,1,9,5,2,6) #tiempo soltero en meses amigos$tiempo_soltero <- 0 #tiempo_soltero
# amigos tsoltero <- c(4,1,9,5,2,6) #tiempo soltero en meses amigos$tiempo_soltero <- 0 #tiempo_soltero for (i in seq_along(amigos$nombre)){ soltero <- amigos$sentimental[[i]][1][[1]] if (soltero == T){ amigos$tiempo_soltero[i]<- tsoltero[i] } else{ amigos$tiempo_soltero[i] <- NA } } amigos
grade_code("Excelente lo lograste, sigamos adelante")
En R
también está presente el ciclo while()
el cual permite repetir la ejecución de un grupo de instrucciones mientras se cumpla una condición, debemos ser precavidos ya que es muy fácil programar un bucle infinito en el cual la condición siempre se cumpla. Iniciemos adaptando el ejemplo 3.2.1 para las primeras 10 letras del vector letras
, usando además un contador. Veamos cómo funciona.
:::example
:::
i =1 #contador while(i<=10){ print(letras[i]) i =i+1 }
El siguiente ejemplo consiste en la comparación de números de un vector x
, que solo funciona mientras los números sean cada vez más grandes, al terminar el ciclo se muestra la razón y el índice por el cual se finalizó.
:::example
:::
x = c(5,8,12,19,48,95,106,208,406,512,683,791,790,805,900,931) i = 1 while(x[i]<x[i+1]){ print(paste(x[i],"es menor que",x[i+1])) i=i+1 } print(paste(x[i],"no es menor que",x[i+1],",error en el indice",i+1,"del vector x"))
Veamos con un ejemplo cómo usar el ciclo while
para generar los primeros 20 números de la sucesión de Fibonacci.
:::example
:::
a = 0 b = 1 count = 1 while(count<=20){ print(a) c<-a+b a<-b b<-c count = count +1 }
Como se puede ver el ciclo while
puede adaptarse para funcionar igual que el ciclo for
, sin embargo su uso en el lenguaje R
es poco frecuente y suele ser más común utilizar el ciclo for
acompañado de condicionales. Veamos los anteriores ejemplos adaptados al ciclo for
utilizando un nuevo argumento denominado break
. El ejemplo 3.2.5 utilizando el ciclo for
se ve de la siguiente manera:
:::example
:::
for (i in seq_along(letras)){ print(letras[i]) if(i==10){ break } }
La adaptación del ejemplo 3.2.6 sería la siguiente:
x = c(5,8,12,19,48,95,106,208,406,512,683,791,790,805,900,931) for (i in seq_along(x)){ if (x[i]<x[i+1]){ print(paste(x[i],"es menor que",x[i+1])) }else{ print(paste(x[i],"no es menor que",x[i+1],",error en el indice",i+1,"del vector x")) break } }
Ahora debes adaptar el ejemplo 3.2.7 de la sucesión de Fibonacci usando el ciclo for
.
:::exercise
:::
a=0 b=1 for (i in 1:20){ print(a) c<-a+b a<-b b<-c }
grade_code("Excelente lo lograste, sigamos adelante")
Otro de los ciclos que posee R
es repeat{}
, que es incluso de menor uso que el ciclo while
. En general repeat{}
repite un grupo de instrucciones indefinidamente, por ello el uso del argumento break
resulta obligatorio en este ciclo. Veamos algunos de los ejemplos anteriores adaptados al ciclo repeat{}
:::example
:::
Iniciemos adaptando el ejemplo 3.2.2
# letras i=0 repeat{ i=i+2 if(i<=length(letras)){ print(paste("la letra en la posición",i,"es",letras[i])) } else{ break } }
Veamos la adaptación del ejercicio 3.2.2 con el ciclo repeat{}
# amigos j=0 repeat{ j=j+1 if(is.na(amigos$nombre[j])){break} nombre <- amigos$nombre[j] masa <- amigos$fisico[[j]][3][[1]] altura <- amigos$fisico[[j]][2][[1]]/100 IMC <- masa/altura^2 print(paste(nombre,"tiene una altura de",altura,"m, una masa de",masa,"u su IMC es",round(IMC,2))) }
Para finalizar esta subsección entendamos cómo trabaja el argumento next
con un ejemplo simple, en el cual evitaremos imprimir los números de 8 y 12 en la serie de números de 1 a 15.
:::example
:::
for (i in 1:15){ if(i == 8 | i == 12){ next } print(i) }
Apliquemos también el argumento next
al ejemplo 3.2.3 del descuento de comida para perro, en este caso el argumento nos ayuda a hacer un poco más corto el código desarrollado anteriormente, evitando seguir el proceso cuando encontramos opciones de descuento o de compra inválidos.
:::example
:::
x = c(10,0,2,15,13,18,4,9,7,2,8,16) #numero de bolsas des = c(1,0,0,1,0,0,1,1,1,0,0,10) #clasificación for (i in seq_along(x)){ cobro = 6800*x[i] if(x[i]<=0){ print("No es un número de compra válido") next } if(des[i]!=0 & des[i]!=1){ print(paste(des[i],"No es un opción válida de clasificación de descuento")) next } if(des[i]==1 & x[i]<10){ cobro = cobro *.5 } else if(des[i]==1 & x[i]>=10){ cobro = cobro * .4 } print(paste("Total a pagar:",cobro)) }
Modifiquemos la respuesta del ejercicio 3.2.1 utilizando el ciclo for
en compañía del argumento next
para múltiples compras en la heladería, de modo que obtengamos el número de compra, el total correspondiente, el descuento y el total con descuento para cada caso.
:::example
:::
dia = c("Domingo", "Lunes","Miercoles","jueves","Domingo","Martes","Viernes") total_compra = c(69500,71350,33700,54000,0,38000,52300) for (i in seq_along(dia)){ print(paste("Número de compra", i)) descuento = 0 if(total_compra[i]<=0){ print("No es un valor de compra válido") next } if(dia[i] =="Lunes" | dia[i] =="Martes" | dia[i] =="Miercoles"){ if(total_compra[i]>50000){ descuento = total_compra[i] * .35 }else{ descuento = total_compra[i] * .25 } }else if(dia[i] =="Jueves" | dia[i] =="Viernes" | dia[i] =="Sabado" | dia[i] =="Domingo"){ if(total_compra[i]>60000){ descuento = total_compra[i] * .15 } } else{ print("No es una dia de la semana valido") next } print(paste("Total:", total_compra[i])) print(paste("Descuento:", descuento)) print(paste("Total con descuento:",total_compra[i]-descuento)) }
Como se ha dicho anteriormente algunos de los ciclos son de poco uso en el lenguaje R
, por lo que es más común utilizar funciones vectoriales como ifelse()
y funciones derivadas de la familia apply
. Primero apliquemos ifelse()
a algunos de los ejemplos y ejercicios de esta subsección modificandolos como dataframe o tibble. La familia apply se verá en una subsección posterior.
Retomando el ejemplo 3.1.3 de la promoción en la floristería, observemos su adaptación en un tibble usando ifelse()
para multiples compras
:::example
:::
#promocion de rosas tibble(rosas= c(2,5,10,21,17,10,2,0,8,3), rosas_extra = ifelse(rosas<=0,NA, #numero de rosas validas ifelse(rosas<10,0, # rosas menores a 10 ifelse(rosas==10,1,3))), # Adicion de rosas en 10 unidades y mayores rosas_total = rosas +rosas_extra )
La siguiente adaptación corresponde a la promoción de comida de perro.
#descuento comida de perro tibble(bolsas= c(10,0,2,15,13,18,4,9,7,2,8,16), des = c(1,0,0,1,0,0,1,1,1,0,0,10), descuento = ifelse(bolsas<=0,NA, #numero de bolsas validas ifelse(des==0,0, #ocasiones sin descuento ifelse(des!=1,NA, #descuento valido ifelse(bolsas<10,.5,.6)))), # descuento del 50% general y 60% por al menos diaz bolsas cobro = ifelse(bolsas<=0,NA, ifelse(des==0,bolsas*6800, ifelse(des!=1,NA,(1-descuento)*bolsas*6800))) )
Y como último ejemplo de esta subsección veremos la adaptación del ejercicio 3.1.2 como un tible con ifelse()
.
#descuentos heladeria dias_semana = c("Lunes","Martes","Miercoles","Jueves","Viernes","Sabado","Domingo") tibble(dia = c("Domingo", "Lunes","Miercoles","jueves","Domingo","Martes","Viernes"), total_compra = c(69500,71350,33700,54000,0,38000,53200), descuento_porcentaje = ifelse(total_compra<= 0,NA, #Valor de compra valido ifelse(dia %in% dias_semana, #dias validos ifelse(dia %in% dias_semana[1:3], # Descuento de lunes a miercoles ifelse(total_compra>50000,.35,.25), # Descuento de lunes a miercoles por compras superiores a 50mil ifelse(total_compra>60000,.15,0)),NA)), # Descuento por compras mayores a 60mil en el resto de dias descuento_valor = total_compra * descuento_porcentaje, total_a_pagar = total_compra - descuento_valor )
3.3 Funciones
R
en su instalación base cuenta por lo menos con 1302 funciones para realizar diversas tareas, sin embargo es común crear nuevas funciones que se ajusten a nuestras necesidades. Pero antes de crear una función debemos entenderla y reconocer sus 3 componentes: 1) argumentos, 2) cuerpo y 3) entorno. Iniciemos creando con function()
una función simple para ilustrar los tres componentes y sus atributos.
knitr::include_graphics("images/4ta_imagen.png")
:::example
:::
# Funcion fun_1 <- function(x,y){ # comentario x+y } # Argumentos formals(fun_1) # Cuerpo body(fun_1) # Entorno environment(fun_1) # Atributos attr(fun_1,"srcref")
En general cualquier función creada en lenguaje R
posee componentes y atributos, sin embargo algunas funciones base como sum()
mean()
y otras no poseen estos componentes, debido a que llaman código C directamente. Intenta hallar los componentes y atributos para la funciones prod()
y abs()
.
:::exercise
:::
# Argumentos formals(prod) formals(abs) # Cuerpo body(prod) body(abs) # Entorno environment(prod) environment(abs) # Atributos attr(prod,"srcref") attr(abs,"srcref")
grade_code("Excelente lo lograste, sigamos adelante")
Como se puede ver estas funciones no poseen componentes y atributos propios de una función creada en R
sin embargo no todas las funciones base llaman código C. Veamos los componentes y atributos de la función rowMeans()
# Argumentos formals(rowMeans) # Cuerpo body(rowMeans) # Entorno environment(rowMeans) # Atributos attr(rowMeans,"srcref")
De igual forma, se puede hacer uso de la función function()
en conjunto con otras funciones u operadores, en lo que se denomina como funciones anónimas. Estas se caracterizan por ser breves (se pueden escribir en una sola línea de código) y por no poseer frecuentemente los {}
. Veamos un ejemplo de una función anónima para la integral $\pi \int_{0}^{6} \frac{x}{12} \sqrt{36-x^2}$, donde la función anónima corresponde al integrando.
:::example
:::
integrate(function(x) pi * (x/12) * sqrt(36- x^2),0,6 )
El ejemplo muestra que el integrando es lo suficientemente corto como para escribirlo en una línea de código, sin embargo observemos una escritura un poco más extensa para el mismo ejemplo.
integrando <- function(x){ pi * (x/12) * sqrt(36- x^2) } integrate(integrando,0,6 )
Una cuestión importante en las funciones, es el léxico que se maneja, en él que debemos entender que los argumentos son válidos dentro de la función y que crearlos o nombrarlos en el exterior no siempre los afecta. Veamos las salidas del siguiente ejemplo para entender mejor este concepto.
:::example
:::
# Función fun_2 <- function() { x = 5 y=10 x+y } #variables independientes x = 7 y = 3 # Usando fun_2 fun_2()
Retomemos fun_1
y selecciona la salida correcta para el siguiente fragmento de código.
:::exercise
:::
#funcion fun_1 <- function(x,y){ # suma de x y y x+y } #variables independientes x= 30 y = x x = 5 #usando fun_1 fun_1(6,7)
question(" ", answer("[1] 30", message = "No estas en lo correcto, sigue intentandolo."), answer("[1] 60", message = "No estas en lo correcto, sigue intentandolo."), answer("[1] 10", message = "No estas en lo correcto, sigue intentandolo."), answer("[1] 13",correct = TRUE, message = "Estas en lo correcto.."), answer("[1] 35", message = "No estas en lo correcto, sigue intentandolo."), allow_retry = TRUE, random_answer_order = TRUE )
¿Pero qué sucedería con el léxico, si nuestra función no posee argumentos pero requiere de una variable? ¿o si los argumentos de nuestra función no son declarados y se requiere una variable?. Veamos primero una función sin argumentos y cómo funciona la denominada búsqueda dinámica.
:::example
:::
# Función fun_3 <- function() x+y # Usando fun_2 fun_3()
Como nuestra función requiere internamente de un x
y un y
, obtenemos un error que nos notifica que el objeto x
no se encuentra. Es aquí donde podemos aplicar el concepto de búsqueda dinámica, agregando x
y y
como variables independientes.
# Variables independientes x = 5 y = 10 # Función fun_3 <- function() x+y # Usando fun_2 fun_3()
Ensayemos con el caso en el que nuestra función tiene un argumento pero falta una variable interna.
:::example
:::
# Función fun_4 <- function(x) x+y # Usando fun_2 fun_4(10)
Apliquemos el concepto de búsqueda dinámica añadiendo como variable independiente y
e incluyendo el argumento x
requerido al usar la función.
#variable independiente y = 15 # Función fun_4 <- function(x) x+y # Usando fun_2 fun_4(10)
Ya que entendimos ciertos conceptos básicos de las funciones creemos algunas que cumplan las mismas tareas que las funciones base, como por ejemplo mean()
.
:::example
:::
my.mean <- function(x){ #calculo sum(x) / length(x) } #prueba my.mean(1:956) #comparación mean(1:956)
Personalicemos un poco my.mean
con la función message()
para que envíe un mensaje mientras hace las operaciones necesarias y además incluyamos return()
para devolver los resultados de las operaciones. Para ver la salida de los mensajes debes copiar la función en R
y ejecutarla allí, la versión dinámica del curso no permite mostrar en pantalla los mensajes.
my.mean <- function(x){ message("Calulando...") calc <- sum(x) / length(x) message("Cálculo terminado") return(calc) } #prueba my.mean(1:956) #comparación mean(1:956)
Es tu turno de crear funciones y comparar el resultado con una función predeterminada de R
. En esta ocasión debes crear la funciones my.var
y my.sd
para comparar tus resultados con las funciones var()
y sd()
. Recuerda que la varianza obedece a la siguiente ecuación: $\mathrm{Var}(x) = \frac{1}{n - 1} \sum_{i=1}^n (x_i - \bar{x}) ^2$. Compara tus resultados con los vectores de prueba.
:::exercise
:::
#vectores de prueba x = seq(1,50,5) y = seq(1,15,length.out = 30) #Funciones my.var <-function(){} my.sd <-function(){} #pruebas my.var(x) my.sd(x) my.var(y) my.sd(y) #comparacion var(x) sd(x) var(y) sd(y)
#vectores de prueba x = seq(1,50,5) y = seq(1,15,length.out = 30) #Funciones my.var <-function(x){ var = sum((x -mean(x))^2) / (length(x)-1) return(var) } my.sd <-function(){} #pruebas my.var(x) my.sd(x) my.var(y) my.sd(y) #comparacion var(x) sd(x) var(y) sd(y)
#vectores de prueba x = seq(1,50,5) y = seq(1,15,length.out = 30) #Funciones my.var <-function(x){ var = sum((x -mean(x))^2) / (length(x)-1) return(var) } my.sd <-function(x){ sd = sqrt(my.var(x)) return(sd) } #pruebas my.var(x) my.sd(x) my.var(y) my.sd(y) #comparacion var(x) sd(x) var(y) sd(y)
grade_code("Excelente lo lograste, sigamos adelante")
Retomemos el ejemplo 3.1.4 de descuento de comida para perro y creemos una función que haga todo el trabajo cada vez que lo necesitemos. Al ser una función, cambiaremos algunos argumentos y detendremos el proceso con stop()
cada vez que se encuentre un error, sea en los valores de compra o en los de descuento. Además se incluirá un argumento predeterminado que define si la salida es impresiones en pantalla o un vector.
:::example
:::
descuento_perros <- function(bolsas,descuentos,to_vector = FALSE){ total <- c() if(length(bolsas) != length(descuentos)){ stop("La longitud de bolsas y descuentos no es la misma") } for( i in seq_along(bolsas)){ cobro = 6800*bolsas[i] if(bolsas[i]<=0){ stop(paste("El número de bolsas en la posición",i,"no es válido")) } if(descuentos[i]!=0 &descuentos[i]!=1){ stop(paste("El código de descuento en la posición",i,"no es válido")) } if(descuentos[i]==1 & bolsas[i]<10){ cobro = cobro *.5 } else if(descuentos[i]==1 & bolsas[i]>=10){ cobro = cobro * .4 } if(to_vector == F){ print(paste("Total a pagar:",cobro)) } else{ total <- c(total,cobro) } } if(to_vector==T){ return(total) } }
Creada la función podemos realizar múltiples pruebas sin necesidad de escribir repetidamente el código. Veamos algunas pruebas e intenta otras por tu cuenta. Para ejecutar las pruebas debes comentar las que presenten errores, ya que para ilustrar su funcionamiento hay pruebas que arrojan error. Estas están marcadas con el comentario #error
al final.
#Prueba 1 descuento_perros(c(10,0,2,15,13,18,4,9,7,2,8,16),c(1,0,0,1,0,0,1,1,1,0,0,10)) #error #Prueba 2 descuento_perros(c(10,2,15,18,16),c(1,0,0,1,10)) # error #prueba 3 descuento_perros(c(10,2,15,18,16),c(1,0,0,1)) #error #prueba 4 descuento_perros(c(10,2,15,18,16),c(1,0,0,1,0)) #prueba 5 descuento_perros(c(10,2,15,18,16),c(1,0,0,1,0),to_vector = TRUE)
Es tu turno de adaptar una función al ejercicio de las rosas tratado en el ejemplo 3.1.3. Como ya sabes en esta sección hay muchas posibles soluciones a los problemas planteados, sin embargo en solution
encontraras una sola de todas las posibles. Verifica los resultados de la función con los dos objetos de prueba. Para ejecutar las pruebas debes comentar las que presenten errores, ya que para ilustrar su funcionamiento hay pruebas que arrojan error, estas están marcadas con el comentario #error
al final.
:::exercise
:::
# Objetos de prueba val_1 <- -13 val_2 <- 16 vec_1 <- c(2,5,10,21,17,10,2,0,8,3) vec_2 <- c(5,10,8,19,21,16,9,3) #función promocion_rosas <- function(){} #Pruebas promocion_rosas(val_1) #error promocion_rosas(val_2) promocion_rosas(vec_1) #error promocion_rosas(vec_2)
# Objetos de prueba val_1 <- -13 val_2 <- 16 vec_1 <- c(2,5,10,21,17,10,2,0,8,3) vec_2 <- c(5,10,8,19,21,16,9,3) #función promocion_rosas <- function(rosas){ for (i in seq_along(rosas)){ rosas_total = rosas[i] if (rosas[i]<=0){ stop(paste("El número de rosas en la posición",i,"no es válido")) }else if(rosas[i]==10){ rosas_total = rosas[i] + 1 }else if(rosas[i]>10){ rosas_total = rosas[i] +3 } print(paste("levarás",rosas_total - rosas[i], "rosas extra, total de rosas",rosas_total)) } } #Pruebas promocion_rosas(val_1) #error promocion_rosas(val_2) promocion_rosas(vec_1) #error promocion_rosas(vec_2)
grade_code("Excelente lo lograste, sigamos adelante")
Implementa ahora una función para el ejercicio 3.1.2 de descuentos en la heladeria. Recuerda el operador %in% utilizado en el ejemplo 3.2.13. Verifica los resultados de la función con los dos objetos de prueba. Para ejecutar las pruebas debes comentar las que presenten errores, ya que para ilustrar su funcionamiento hay pruebas que arrojan error, estas están marcadas con el comentario #error
al final.
:::exercise
:::
#objetos de prueba dia_x = "Miercoles" total_x = 33700 vec_dias_1 = c("Domingo", "Lunes","Miercoles","jueves","Domingo","Martes","Viernes") vec_total_1 = c(69500,71350,33700,54000,0,38000,53200) vec_dias_2 = c("Domingo", "Lunes","Miercoles","Jueves","Domingo","Martes","Viernes") vec_total_2 = c(69500,71350,33700,54000,12000,38000,53200) # funcion descuento_helados <- function(){} #pruebas descuento_helados(dia_x,total_x) descuento_helados(vec_dias_1,vec_total_1) #error descuento_helados(vec_dias_2,vec_total_2)
#objetos de prueba dia_x = "Miercoles" total_x = 33700 vec_dias_1 = c("Domingo", "Lunes","Miercoles","jueves","Domingo","Martes","Viernes") vec_total_1 = c(69500,71350,33700,54000,0,38000,53200) vec_dias_2 = c("Domingo", "Lunes","Miercoles","Jueves","Domingo","Martes","Viernes") vec_total_2 = c(69500,71350,33700,54000,12000,38000,53200) # funcion descuento_helados <- function(dias,totales){ dias_semana = c("Lunes","Martes","Miercoles","Jueves","Viernes","Sabado","Domingo") if(length(dias)!=length(totales)){ stop("La longitud de dias y totales no es la misma") } for (i in seq_along(dias)){ descuento = 0 if(totales[i]<=0){ stop(paste("El valor proporcionado en la posición",i,"no es válido")) } if(!dias[i] %in% dias_semana ){ stop(paste("El día proporcionado en la posición",i,"no es válido")) }else if(dias[i] %in% dias_semana[1:3]){ descuento = descuento + .25 if (totales[i]>50000){ descuento = descuento + .1 } }else if(totales[i]>60000){ descuento = descuento + .15 } print(paste("Total:",totales[i],"Descuento:",totales[i]*descuento,"Total a pagar:" ,totales[i]*(1-descuento))) } } #pruebas descuento_helados(dia_x,total_x) descuento_helados(vec_dias_1,vec_total_1) #error descuento_helados(vec_dias_2,vec_total_2)
grade_code("Excelente lo lograste, sigamos adelante")
Continuando con la teoría acerca de las funciones, trabajaremos el argumento especial ...
, el cual tiene dos usos comunes: 1) Para extender una función y 2) Para permitir que la función posea cualquier número de argumentos adicionales. El primer caso es usado comúnmente cuando se anidan funciones. Veamos un ejemplo de este primer caso utilizando dos funciones my_fun_1
y my_fun_2
.
:::example
:::
#Funcion 1 : suma my_fun_1<- function(v,w,x,y,z){v+w+x+y+z} my_fun_1(1,2,3,4,5) #Funcion 2 : suma * 2 my_fun_2 <- function(...){ my_fun_1(...)*2 } my_fun_2(1,2,3,4,5)
Para este ejemplo el argumento especial ...
permite que my_fun_2
herede los mismos argumentos que my_fun_1
, permitiendo hacer operaciones sobre el calculo de my_fun_1
. Otro ejemplo simple del uso de ...
que puede resultar un poco más familiar es una adaptación de las funciones my.var
y my.sd
en la solución del ejercicio 3.3.3. Veamos esta adaptación:
:::example
:::
#Funcion de calculo de varianza my.var <-function(x){ var = sum((x -mean(x))^2) / (length(x)-1) return(var) } #Funcion de calculo de sd my.sd <-function(...){ sd = sqrt(my.var(...)) return(sd) } # Prueba my.sd(1:15) sd(1:15)
Tratemos una adaptación un poco más elaborada, en la cual se extiendan al mismo tiempo las funciones de descuento_perros
y descuento_helados
cada una con sus respectivos argumentos.
:::example
:::
#objetos de prueba vec_dias_2 = c("Domingo", "Lunes","Miercoles","Jueves","Domingo","Martes","Viernes") vec_total_2 = c(69500,71350,33700,54000,12000,38000,53200) #Funcion descuentos descuentos <- function(...,perros=F,helados=F){ if(helados==T & perros == F){ message("Descuento en heladería") descuento_helados(...) }else if(perros == T & helados==F){ message("Descuento en comida para perro") descuento_perros(...) }else{ stop("Seleccione adecuadamente la opción de descuento") } } #pruebas descuentos(c(10,2,15,18,16),c(1,0,0,1,0),perros = T) descuentos(vec_dias_2,vec_total_2,helados = T) descuentos(c(10,2,15,18,16),c(1,0,0,1,0),perros = T,to_vector=T)
Aunque el ejercicio de la floristería no corresponde exactamente a un descuento, en el siguiente problema debes volver a crear la función descuentos
añadiendo la función promocion_rosas()
usando el argumento especial ...
.
:::exercise
:::
#objetos de prueba vec_dias_2 = c("Domingo", "Lunes","Miercoles","Jueves","Domingo","Martes","Viernes") vec_total_2 = c(69500,71350,33700,54000,12000,38000,53200) #Funcion descuentos descuentos <- function(...,perros=F,helados=F,rosas=F){} #pruebas descuentos(c(10,2,15,18,16),c(1,0,0,1,0),perros = T) descuentos(vec_dias_2,vec_total_2,helados = T) descuentos(c(10,2,15,18,16),c(1,0,0,1,0),perros = T,to_vector=T) descuentos(c(5,6,8,10,12,47),rosas = T)
#objetos de prueba vec_dias_2 = c("Domingo", "Lunes","Miercoles","Jueves","Domingo","Martes","Viernes") vec_total_2 = c(69500,71350,33700,54000,12000,38000,53200) #Funcion descuentos descuentos <- function(...,perros=F,helados=F,rosas=F){ if(helados==T & perros == F & rosas ==F){ message("Descuento en heladería") descuento_helados(...) }else if(perros == T & helados==F & rosas==F){ message("Descuento en comida para perro") descuento_perros(...) }else if(rosas==T & perros==F & helados==F){ }else{ stop("Seleccione adecuadamente la opción de descuento") } } #pruebas descuentos(c(10,2,15,18,16),c(1,0,0,1,0),perros = T) descuentos(vec_dias_2,vec_total_2,helados = T) descuentos(c(10,2,15,18,16),c(1,0,0,1,0),perros = T,to_vector=T) descuentos(c(5,6,8,10,12,47),rosas = T)
#objetos de prueba vec_dias_2 = c("Domingo", "Lunes","Miercoles","Jueves","Domingo","Martes","Viernes") vec_total_2 = c(69500,71350,33700,54000,12000,38000,53200) #Funcion descuentos descuentos <- function(...,perros=F,helados=F,rosas=F){ if(helados==T & perros == F & rosas ==F){ message("Descuento en heladería") descuento_helados(...) }else if(perros == T & helados==F & rosas==F){ message("Descuento en comida para perro") descuento_perros(...) }else if(rosas==T & perros==F & helados==F){ message("Promoción de rosas") promocion_rosas(...) }else{ stop("Seleccione adecuadamente la opción de descuento") } } #pruebas descuentos(c(10,2,15,18,16),c(1,0,0,1,0),perros = T) descuentos(vec_dias_2,vec_total_2,helados = T) descuentos(c(10,2,15,18,16),c(1,0,0,1,0),perros = T,to_vector=T) descuentos(c(5,6,8,10,12,47),rosas = T)
grade_code("Excelente lo lograste, sigamos adelante")
El otro uso del argumento especial ...
es en funciones que reciben cualquier número de argumentos, como ejemplo de ellas tenemos sum()
, prod()
, paste()
, paste0()
y otras más. Veamos cómo funcionan estas y creemos una que reciba cualquier cantidad de argumentos.
:::example
:::
#Funciones base sum(1,3,5,7,89,c=5,b=8) prod(3,5,d=8,9) paste("Hola","Soy una función","con",ar=4,"argumentos") paste0("c","i","n","c","o") #Funcion fun_5 <- function(...){ list(...) } #pruebas fun_5(1,3,5,7,89,c=5,b=8) fun_5("Hola","Soy una función","con",ar=4,"argumentos")
fun_5
recibe cualquier cantidad de elementos y los guarda en una lista. El uso que le demos a ella depende de nuestras necesidades y habilidades con las listas.
3.4 Familia apply
Se conoce como familia apply
al conjunto de funciones usadas para aplicar funciones en matrices, dataframe, arreglos y listas. Corresponden a una de las características distintivas de R
como lenguaje de programación y se utilizan frecuentemente para automatizar tareas complejas. Las funciones de la familia apply
se caracterizan por recibir como argumentos a un objeto y al menos una función. Las funciones pertenecientes a esta familia son las siguientes:
apply()
lapply()
sapply()
eapply()
mapply()
rapply()
tapply()
vapply()
Algunas de estas funciones tienen aplicaciones sumamente específicas y profundizar en ellas no resulta apropiado para un curso corto. Por esta razón, se profundizará solo en algunas de ellas iniciando con la función apply()
.
apply(X,MARGIN,FUN,...)
Esta función recibe 3 argumentos esenciales y argumentos adicionales inherentes a una función específica, donde:
X
: Una matriz o un objeto que pueda coercionarse a una matriz.MARGIN
: 1 para operar sobre filas, 2 para operar sobre columnas.FUN
: Operador o función aplicada....
: Argumentos adicionales de la función aplicada.apply()
devuelve generalmente un vector y en los casos donde la función devuelve un vector de longitud n devuelve una matriz. Veamos algunos ejemplos.
:::example
:::
#objetos de prueba df = data.frame(x=1:10,y=11:20,z=21:30) m = matrix(1:25,ncol = 5) #producto de elementos por filas apply(X = df,MARGIN = 1,FUN = prod) apply(X = m,MARGIN = 1,FUN = prod) #producto de elementos por columna apply(X = df,MARGIN = 2,FUN = prod) apply(X = m,MARGIN = 2,FUN = prod) #raiz cuadrada orientacíon filas apply(df,1,sqrt)
También podemos usar apply()
con funciones de usuario e incluso con funciones anónimas. Para ilustrar un ejemplo con estas funciones recordemos el ejemplo de la floristería, tomando como objeto de prueba un dataset que contiene la venta de tres días en la semana.
:::example
:::
#objetos de prueba rosas <- data.frame(lunes=c(2,5,10,21,17,10,2,8,3), martes=c(5,10,8,19,21,16,9,3,19), miercoles=c(10,4,8,19,29,1,2,10,2)) #apply por columna apply(rosas,2,promocion_rosas) #funcion anonima filas apply(rosas,1, function(x) ifelse(x<10,x,ifelse(x==10,x+1,x+3))) #funcion anonima columnas apply(rosas,2, function(x) ifelse(x<10,x,ifelse(x==10,x+1,x+3)))
Para entender cómo funcionan los argumentos adicionales al usar apply()
utilizaremos la función quantile()
, sobre los tres días de ventas de rosas. Primero utilizaremos la función sin argumentos adicionales, de la siguiente forma:
:::example
:::
#objetos de prueba rosas <- data.frame(lunes=c(2,5,10,21,17,10,2,8,3), martes=c(5,10,8,19,21,16,9,3,19), miercoles=c(10,4,8,19,29,1,2,10,2)) #apply por dias apply(rosas,2,quantile)
Recuerda para conocer los argumentos de una función y la ayuda de la misma puedes ejecutar ?quantile
. Ahora usemos los argumentos adicionales para seleccionar cuantiles de interés diferentes a los que están por defecto.
#objetos de prueba rosas <- data.frame(lunes=c(2,5,10,21,17,10,2,8,3), martes=c(5,10,8,19,21,16,9,3,19), miercoles=c(10,4,8,19,29,1,2,10,2)) #apply por dias apply(rosas,2,quantile,probs=c(.33,.66,.99))
Recordemos el ejemplo 2.3.3.4 en el cual determinamos la representación en porcentual de cada elemento de la columna sobre la suma total de la misma. En aquella ocasión determinar esa representación porcentual tomó aproximadamente 8 líneas de código. Ahora con apply()
, debes lograrlo en una sola línea.
:::exercise
:::
#Matriz de prueba 1 m <- matrix(seq(18, 2, length.out = 16), ncol = 4, byrow = TRUE) m #porcentaje columna
#Matriz de prueba 1 m <- matrix(seq(18, 2, length.out = 16), ncol = 4, byrow = TRUE) m #porcentaje columna apply(m, 2, function(x) round(100*x/sum(x), 2))
grade_code("Excelente lo lograste, sigamos adelante")
Intenta ahora la estandarización por columnas de la siguiente matriz. Recuerda que la estandarización para este caso corresponde a restar la media de la columna y dividir por la desviación estándar a cada elemento de la misma columna.
:::exercise
:::
set.seed(20)
#Matriz de prueba 1 m <- matrix(seq(8, 2, length.out = 16), ncol = 4, byrow = TRUE) m #estandarización
set.seed(20)
#Matriz de prueba 1 m <- matrix(sample(1:8, 16, replace = TRUE), ncol = 4, byrow = TRUE) m #estandarización apply(m,2,function(x) (x-mean(x))/sd(x))
grade_code("Excelente lo lograste, sigamos adelante")
La siguiente función de la familia apply
en la que profundizaremos es lapply()
lapply(X,FUN,...)
Esta función recibe 2 argumentos esenciales y argumentos adicionales inherentes a una función específica, donde:
X
: Es una lista o un objeto que pueda coercionarse a una lista.FUN
: Operador o función aplicada....
: Argumentos adicionales de la función aplicada.lapply()
devuelve una lista. Veamos algunos ejemplos, iniciando con la adaptación del ejemplo 3.4.1
:::example
:::
#objetos de prueba df = data.frame(x=1:10,y=11:20,z=21:30) m = matrix(1:25,ncol = 5) #producto de elementos por columnas lapply(X = df,FUN = prod) lapply(X = as.data.frame(m),FUN = prod) #raiz cuadrada de cada elemento lapply(df,sqrt)
Al igual que apply()
, lapply()
admite funciones anónimas, usemos el ejemplo 3.4.2 para verlo.
:::example
:::
#venta de rosas rosas <- data.frame(lunes=c(2,5,10,21,17,10,2,8,3), martes=c(5,10,8,19,21,16,9,3,19), miercoles=c(10,4,8,19,29,1,2,10,2)) #promoción lapply(rosas, function(x) ifelse(x<10,x,ifelse(x==10,x+1,x+3)))
Veamos un ejemplo involucrando el descuento en comida de perro, en el cual se registraron ventas en tres días de la semana en una lista ventas
, cada una con una lista contiene el número de bolsas compradas y el código de descuento. En este ejemplo usaremos además una función anónima que llama una función de usuario.
:::example
:::
#ventas ventas = list(lunes=list(bolsas=c(10,2,15,18,16),des=c(1,0,0,1,0)), martes=list(bolsas=c(11,13,1,4,6,9),des=c(0,0,1,1,0,1)), miercoles=list(bolsas=c(3,10,4,1),des=c(1,1,0,1))) #descuento comida de perros lapply(ventas,function(x) descuento_perros(x[[1]],x[[2]],to_vector = T))
Tratemos un ejemplo más familiar recordando el dataset amigos
y calculemos el IMC de cada individuo utilizando lapply()
.
:::example
:::
#datos amigos$fisico #Calculo IMC lapply(amigos$fisico,function(list) unname(list[3]/(list[2]/100)**2))
Para terminar con la función lapply()
veamos un ejemplo en el que utilicemos una función anónima para llamar la función apply()
y aplicarla sobre tres matrices tal cual como se hizo en el ejercicio 3.4.2.
:::example
:::
#Matrices de prueba m1 <- matrix(seq(18, 2, length.out = 16), ncol = 4, byrow = TRUE) m2 <- matrix(1:16, ncol = 4) m3 <- matrix(seq(10, 74, length.out = 16), ncol = 4, byrow = TRUE) #lista de matrices mm <- list(m1,m2,m3) #porcentaje lapply(mm,function(m) apply(m,2,function(x) round(100*x/sum(x),2)))
La última función de la familia apply
que trataremos será sappply()
, la cual es muy similar a lapply()
salvo por la devolución, ya que sapply()
devuelve, cuando es posible un objeto mas simple (vector o matriz) en lugar de una lista.
sapply(X,FUN,...,simplify = TRUE)
Esta función recibe 2 argumentos esenciales, y argumentos adicionales inherentes a una función específica, además permite modificar dos argumentos por defecto,donde:
X
: Es una lista o un objeto que pueda coercionarse a una lista.FUN
: Operador o función aplicada....
: Argumentos adicionales de la función aplicada.simplify
: TRUE, devuelve un vector cuando sea posible; FALSE, cumple la misma función que lapply()
Para entender los casos en los que sapply()
funciona debes adaptar los ejemplo de lapply()
, iniciando con el ejemplo 3.4.4.
:::exercise
:::
#objetos de prueba df = data.frame(x=1:10,y=11:20,z=21:30) m = matrix(1:25,ncol = 5) #producto de elementos por columnas sapply() sapply() #raiz cuadrada de cada elemento sapply()
#objetos de prueba df = data.frame(x=1:10,y=11:20,z=21:30) m = matrix(1:25,ncol = 5) #producto de elementos por columnas sapply(X = df,FUN = prod) sapply(X = as.data.frame(m),FUN = prod) #raiz cuadrada de cada elemento sapply(df,sqrt)
grade_code("Excelente lo lograste, sigamos adelante")
Intenta ahora con el ejemplo 3.4.5 de la promoción de rosas.
:::exercise
:::
#venta de rosas rosas <- data.frame(lunes=c(2,5,10,21,17,10,2,8,3), martes=c(5,10,8,19,21,16,9,3,19), miercoles=c(10,4,8,19,29,1,2,10,2)) #promoción
#venta de rosas rosas <- data.frame(lunes=c(2,5,10,21,17,10,2,8,3), martes=c(5,10,8,19,21,16,9,3,19), miercoles=c(10,4,8,19,29,1,2,10,2)) #promoción sapply(rosas, function(x) ifelse(x<10,x,ifelse(x==10,x+1,x+3)))
grade_code("Excelente lo lograste, sigamos adelante")
Por último para evidenciar un caso en el que la simplificación no es posible intenta la adaptación del ejemplo 3.4.6.
:::exercise
:::
#ventas ventas = list(lunes=list(bolsas=c(10,2,15,18,16),des=c(1,0,0,1,0)), martes=list(bolsas=c(11,13,1,4,6,9),des=c(0,0,1,1,0,1)), miercoles=list(bolsas=c(3,10,4,1),des=c(1,1,0,1))) #descuento comida de perros
#ventas ventas = list(lunes=list(bolsas=c(10,2,15,18,16),des=c(1,0,0,1,0)), martes=list(bolsas=c(11,13,1,4,6,9),des=c(0,0,1,1,0,1)), miercoles=list(bolsas=c(3,10,4,1),des=c(1,1,0,1))) #descuento comida de perros sapply(ventas,function(x) descuento_perros(x[[1]],x[[2]],to_vector = T))
grade_code("Excelente lo lograste, sigamos adelante")
A partir de las funciones de la familia apply
nombradas anteriormente, se puede obtener una noción básica del funcionamiento de la familia completa. Para abordar y conocer mejor las otras funciones de esta familia de funciones, te recomendamos revisar la documentación de R
.
knitr::include_graphics("images/1ra_imagen.png")
tidyverse
El tidyverse
es una colección de paquetes R
que trabajan en armonía con el objetivo de cubrir todo el espectro de análisis de datos dentro de R
. Los paquetes dentro del tidyverse
(al menos los abordados en este curso) son:
Para usar los paquetes del tidyverse
se deben instalar primero. Para esto, los paquetes se pueden instalar de forma individual:
install.packages('readr') install.packages('tidyr') install.packages('dplyr') install.packages('ggplot2')
y luego cargarlos una vez se necesiten:
library(readr) library(tidyr) library(dplyr) library(ggplot2)
O simplemente se puede instalar y cargar el paquete tidyverse
:
install.packages('tidyverse') library(tidyverse) tidyverse_update() # Si desea actualizar el tidyverse.
En la naturaleza, los conjuntos de datos vienen en muchos formatos diferentes.
:::example
:::
library(DSR) table1 %>% rename('País' = country, 'Año' = year, 'Casos' = cases, 'Población' = population) %>% mutate(País = case_when( País == 'Afghanistan' ~ 'Afganistán', País == 'Brazil' ~ 'Brasil', País == 'China' ~ 'China' ))%>% kable() %>% kable_styling(full_width = FALSE, bootstrap_options = (c('striped', 'bordered')))
table2 %>% rename('País' = country, 'Año' = year, 'Variables' = key, 'Valor' = value) %>% mutate(País = case_when( País == 'Afghanistan' ~ 'Afganistán', País == 'Brazil' ~ 'Brasil', País == 'China' ~ 'China'), Variables = case_when( Variables == 'cases' ~ 'Casos', Variables == 'population' ~ 'Población') ) %>% kable() %>% kable_styling(full_width = FALSE, bootstrap_options = (c('striped', 'bordered')))
table3 %>% rename('País' = country, 'Año' = year, 'Tasa' = rate) %>% mutate(País = case_when( País == 'Afghanistan' ~ 'Afganistán', País == 'Brazil' ~ 'Brasil', País == 'China' ~ 'China' ))%>% kable() %>% kable_styling(full_width = FALSE, bootstrap_options = (c('striped', 'bordered')))
Los conjuntos de datos anteriores muestran los mismos datos organizados en cuatro formas diferentes. Sin embargo el conjunto de datos que cumple las siguientes tres reglas es mucho más fácil para trabajar en R
:
knitr::include_graphics("images/5ta_imagen.png")
Los datos que satisfacen estas reglas se conocen como datos ordenados.
:::exercise
Teniendo en cuenta las reglas sobre datos ordenados, ¿cual de los cuatro conjuntos de datos anteriormente mencionados considera cumple con este principio? :::
question(" ", answer("Conjunto de datos 4", message = "No estas en lo correcto, sigue intentandolo."), answer("Conjunto de datos 1", correct = TRUE, message = "Estas en lo correcto... en este conjunto de datos cada variable se coloco en su propia columna, cada observación en su propia fila y cada valor en su propia celda."), answer("Conjunto de datos 2", message = "No estas en lo correcto, sigue intentandolo."), answer("Conjunto de datos 3", message = "No estas en lo correcto, sigue intentandolo."), allow_retry = TRUE, random_answer_order = TRUE )
Los datos ordenados funcionan bien en R
porque R
es un lenguaje de programación vectorizado. Los conjuntos de datos en R
están construidos a partir de vectores y las operaciones de R
están optimizadas para trabajar con vectores. Los datos ordenados aprovechan estas dos características.
knitr::include_graphics("images/6ta_imagen.png")
:::caution
Los datos ordenados fueron popularizados por Hadley Wickham, y sirven como base para muchos paquetes y funciones de R
. Puede obtener más información sobre datos ordenados leyendo Tidy Data, un documento escrito por Hadley Wickham y publicado en el Journal of Statistical Software.
:::
tidyr
El paquete tidyr
tiene como objetivo ayudarle a ordenar sus datos. Contiene varias funciones que alteran el diseño de los conjuntos de datos, al tiempo que conserva los valores:
pivot_wider()
La función pivot_wider()
es usada cuando se tiene una observación dispersa en múltiples filas.
:::example
:::
datos_2 <- DSR::table2 %>% rename('País' = country, 'Año' = year, 'Variables' = key, 'Valor' = value) %>% mutate(País = case_when( País == 'Afghanistan' ~ 'Afganistán', País == 'Brazil' ~ 'Brasil', País == 'China' ~ 'China'), Variables = case_when( Variables == 'cases' ~ 'Casos', Variables == 'population' ~ 'Población') )
save(datos_2,file = "data_pkg/datos_2.rda", compress='xz')
datos_2
knitr::include_graphics("images/7ma_imagen.png")
datos_2_ancho <- pivot_wider( data = datos_2, # El nombre del conjunto de datos a ordenar. names_from = Variables, # Argumento que indica el nombre de la columna donde se encuentran las variables. values_from = Valor # Argumento que indica el nombre de la columna que contiene los valores de las variables. ) datos_2_ancho
pivot_longer()
La función pivot_longer()
permite resolver las situaciones en donde se tienen columnas que realmente no representan variables, sino valores de una misma variable.
:::example
:::
datos_4_a <- DSR::table4 %>% rename('País' = country) %>% mutate(País = case_when( País == 'Afghanistan' ~ 'Afganistán', País == 'Brazil' ~ 'Brasil', País == 'China' ~ 'China' ))
save(datos_4_a,file = "data_pkg/datos_4_a.rda", compress='xz')
datos_4_a
knitr::include_graphics("images/8va_imagen.png")
datos_4_a_largo <- pivot_longer( data = datos_4_a, # El nombre del conjunto de datos a ordenar. cols = c('1999', '2000'), # Argumento donde se indican las columnas que pueden ser una variable. names_to = 'Año', # Argumento donde se indica el nombre de la columna a crear a partir de los datos almacenados anteriormente como columna de datos. values_to = 'Casos' # Argumento donde se indica el nombre de la columna a crear a partir de los datos almacenados anteriormente como valores de celda. ) datos_4_a_largo
:::exercise
Teniendo en cuenta el ejemplo planteado anteriormente con la función pivot_longer()
, por favor intente hacer lo mismo con el siguiente conjunto de datos llamado datos_4_b con el fin obtener la estructura de datos ordenados presentada en la imagen a continuación.
:::
datos_4_b <- DSR::table5 %>% rename('País' = country) %>% mutate(País = case_when( País == 'Afghanistan' ~ 'Afganistán', País == 'Brazil' ~ 'Brasil', País == 'China' ~ 'China' ))
save(datos_4_b,file = "data_pkg/datos_4_b.rda", compress='xz')
datos_4_b
knitr::include_graphics("images/9na_imagen.png")
datos_4_b_largo <- pivot_longer( data = datos_4_b, cols = c('1999', '2000'), names_to = 'Años', values_to = 'Población' ) datos_4_b_largo
grade_code("¡Muy bien!, a partir de este cambio cada variable se coloco en su propia columna, cada observación en su propia fila y cada valor en su propia celda.")
:::caution
Como se observó, la función pivot_longer()
hace lo opuesto a pivot_wider()
. Por lo tanto, ambas funciones son complementarias, es decir, si al resultado de aplicar la función pivot_wider()
se le aplica la función pivot_longer()
se llega al conjunto de datos original. Otra observación interesante es que pivot_longer()
alarga los conjuntos de datos, mientras que pivot_wider()
los hace más anchos.
:::
separate()
y unite()
La función separate()
lo que hace es dividir una columna en múltiples columnas, tomando como separador algún símbolo, mientras que la función unite()
toma múltiples columnas y las une en una única columna, separando los elementos mediante un separador.
:::example
:::
datos_3 <- DSR::table3 %>% rename('País' = country, 'Año' = year, 'Tasa' = rate) %>% mutate(País = case_when( País == 'Afghanistan' ~ 'Afganistán', País == 'Brazil' ~ 'Brasil', País == 'China' ~ 'China' ))
save(datos_3,file = "data_pkg/datos_3.rda", compress='xz')
datos_3
knitr::include_graphics("images/10ma_imagen.png")
datos_3_separado <- separate( data = datos_3, # El nombre del conjunto de datos a ordenar. col = Tasa, # Argumento donde se indica el nombre de la columna que se quiere dividir. into = c('Casos', 'Población'), # Argumento donde se indica los nombres de las nuevas variables. sep = '/'#, # Argumento donde se indica el símbolo que separa las dos variables en una misma columna. #convert = TRUE # Opción que permite hacer la conversión de tipo caracter a numérico. ) datos_3_separado
:::exercise
:::
El siguiente conjunto de datos contiene casos de tuberculosis registrados en distintos años. Dichos datos se encuentran en el Informe Gobal de Tuberculosis de la Organización Mundial de la Salud, disponible para descargar aquí. Este conjunto de datos proporciona un ejemplo realista de datos desordenados.
data('who') Tuberculosis <- DSR::who Tuberculosis
La característica más peculiar del anterior conjunto de datos es su sistema de codificación. Las columnas cinco a sesenta codifican cuatro partes de información que separadas significan lo siguiente:
1) Las primeras tres letras de cada columna indican si los casos de tuberculosis corresponden a casos nuevos o antiguos. En este conjunto de datos, cada columna contiene solo nuevos casos.
2) Las siguientes dos letras describen el tipo de caso de tuberculosis:
- rel
significa casos de recaída,
- ep
significa casos de tuberculosis extrapulmonar,
- sn
significa casos de tuberculosis pulmonar que no pudieron ser diagnosticados por un frotis pulmonar (frotis negativo),
- sp
significa casos de tuberculosis pulmonar que podrían diagnosticarse por un frotis pulmonar (frotis positivo).
3) La sexta parte describe el sexo de los pacientes con tuberculosis: m
para hombres y f
para mujeres.
4) La última parte describe el grupo de edad de los pacientes con tuberculosis. El conjunto de datos agrupa los casos en siete grupos de edad:
- 014
significa pacientes de 0 a 14 años de edad,
- 1524
significa pacientes de 15 a 24 años de edad,
- 2534
representa pacientes de 25 a 34 años de edad,
- 3544
significa pacientes que tienen entre 35 y 44 años,
- 4554
significa pacientes de 45 a 54 años de edad,
- 5564
significa pacientes de 55 a 64 años de edad,
- 65
significa pacientes que tienen 65 años o más.
El ejercicio de repaso propuesto consiste en que haciendo uso de las funciones mecionadas del paquete tidyr
trate de ordenar el conjunto de datos Tuberculosis
, e intente obtener un conjunto de datos similar a como se observa en la imagen a continuación.
knitr::include_graphics("images/11va_imagen.png")
save(Tuberculosis, file = 'data_pkg/Tuberculosis.rda', compress='xz')
Tuberculosis_2 <- pivot_longer( data = , cols = c(), names_to = 'codificacion', values_to = 'valor' ) Tuberculosis_3 <- ( data = Tuberculosis_2, col = codificacion, into = c('new', 'type', 'sexage'), sep = ' ' ) Tuberculosis_4 <- separate( data = , col = sexage, into = c(' ', ' '), sep = 1 # Aqui 1 indica el primer elemento dentro de la variable sexage, es decir m o f. ) Tuberculosis_5 <- pivot_wider( data = Tuberculosis_4, names_from = type, values_from = Valor )
Tuberculosis_2 <- pivot_longer( data = Tuberculosis, cols = c(5:60), names_to = 'codificacion', values_to = 'valor' ) Tuberculosis_3 <- separate( data = Tuberculosis_2, col = codificacion, into = c('new', 'tipo', 'sexage'), sep = '_' ) Tuberculosis_4 <- separate( data = Tuberculosis_3, col = sexage, into = c('sex', 'age'), sep = 1 # Aqui 1 indica el primer elemento dentro de la variable sexage. ) Tuberculosis_5 <- pivot_wider( data = Tuberculosis_4, names_from = tipo, values_from = valor )
En esta parte del curso sobre la manipulación de datos se empleara el conjunto de datos penguins
, el cual resultó de una investigación donde se examinó el dimorfismo sexual ecológico entre pingüinos del género Pygoscelis.
knitr::include_graphics("images/Pygoscelis_penguins.png")
Descargo de responsabilidad: El conjunto de datos penguins
hace parte del paquete palmerpenguins. Este conjunto de datos debe entenderse como datos de muestra para aprender sobre herramientas de manipulación y visualización.
Cita de datos: Gorman KB, Williams TD, Fraser WR (2014) Dimorfismo sexual ecológico y variabilidad ambiental dentro de una comunidad de Pingüinos antárticos (Género Pygoscelis). PLoS ONE 9 (3): e90081. https://doi.org/10.1371/journal.pone.0090081
Estructura general de lo datos: A continuación podrá hechar un vistazo de la estructura del conjunto de datos penguins
:
palmerpenguins::penguins
1) En species
podrá encontrar las tres especies existentes del genéro de pingüinos Pygoscelis.
2) En island
podrá encontrar el nombre de las islas donde fueron ubicadas las colonias de pingüinos.
5) En flipper_length_mm
se registró la longitud de la aleta de las tres especies de pingüinos.
6) En body_mass_g
se registró la masa corporal de cada uno de los pingüinos.
7) En sex
podrá encontrar la información sobre el sexo (FEMALE para hembras y MALE para machos) de cada uno de los pingüinos.
dplyr
Se suele decir que la manipulación y la limpieza de los datos suele ocupar un 80% del tiempo en el análisis de datos. También es sabido que esta no es una experiencia alegre. Sin embargo, existen herramientas disponibles que ayudan en esta tarea.
El paquete dplyr
es un paquete que permite obtener partes de los datos de una manera rápida, fácil de entender y fácil de replicar. Aprender y usar este paquete hará del proceso de manipulación y limpieza de datos una tarea más agradable.
knitr::include_graphics("images/tidyverse_war.jpg")
El paquete dplyr
tiene como objetivo proporcionar una función para cada verbo básico de la manipulación de datos. Estos verbos se pueden organizar en tres categorías según el componente del conjunto de datos sobre el que trabajan:
filter()
La función filter()
permite elegir y extraer filas que satisfacen ciertas condiciones. La sintaxis general de filter()
es: filter(dataset, condition)
.
Si desea seleccionar un grupo específico de valores de una variable de tipo carácter, se puede usar el operador de comparación ==
.
:::example
:::
knitr::include_graphics("images/12va_imagen.png")
filter( .data = penguins, species == 'Adelie', island == 'Dream' )
:::caution
Si bien en el ejemplo se uso el operador de comparación igual a (==
), se pueden utilizar otros operadores. Por ejemplo, filter(.data = penguins, species != 'Adelie')
, seleccionará todas las filas diferentes de (!=
) Adelie.
:::
Si desea filtrar variables numéricas en función de sus valores, puede hacerlo por medio de los operadores >
, >=
, <
, <=
, ==
y !=
.
:::example
:::
filter( .data = penguins, body_mass_g < 4400 )
filter( .data = penguins, body_mass_g <= 4400, body_mass_g >= 3800 ) filter( .data = penguins, between(body_mass_g, 3800, 4400) )
Para filtrar filas vacías se puede usar la función is.na()
dentro de filter()
.
:::example
:::
filter( .data = penguins, !is.na(culmen_length_mm) )
slice()
La función slice()
permite indexar filas por sus ubicaciones dentro del conjunto de datos. Esta permite seleccionar, eliminar y duplicar filas.
:::example
:::
knitr::include_graphics("images/13va_imagen.png")
slice( .data = penguins, c(1:4) )
slice_head( .data = penguins, n = 4 ) slice_tail( .data = penguins, n = 4 )
slice_sample( .data = penguins, n = 4 )
slice_min( .data = penguins, culmen_depth_mm, n = 4 ) slice_max( .data = penguins, body_mass_g, n = 4 )
arrange()
La función arrange()
permite reordenar las filas un conjunto de datos en función del valor de una determinada variable. Esto puede ser útil si se desea ver rápidamente qué mediciones tuvieron los valores más altos o más bajos.
:::example
:::
knitr::include_graphics("images/14va_imagen.png")
arrange( .data = penguins, desc(culmen_depth_mm) )
arrange( .data = penguins, culmen_depth_mm )
:::caution
Al usar la función arrange()
siempre se pondrán los valores de NA
al final del conjunto de datos.
:::
select()
La función select()
permite elegir y extraer columnas de interés de un conjunto de datos.
:::example
:::
knitr::include_graphics("images/15va_imagen.png")
select( .data = penguins, species, culmen_length_mm, culmen_depth_mm, body_mass_g ) select( .data = penguins, species, culmen_length_mm:culmen_depth_mm, body_mass_g ) select( .data = penguins, -island, -flipper_length_mm, -sex ) select( .data = penguins, -(species:sex), species, culmen_length_mm, culmen_depth_mm, body_mass_g )
Si se tiene una gran cantidad de columnas o variables con una estructura similar, se puede utilizar la concordancia parcial mediante la adición de starts_with()
, ends_with()
o contains()
en la sentencia de selección.
:::example
:::
select( .data = penguins, starts_with('culmen') ) select( .data = penguins, ends_with('mm') ) select( .data = penguins, contains('length') )
Puede identificar el nombre de las columnas inicialmente, y luego referirse a ellas dentro de la función select()
por medio de la función one_of()
o utilizando el operador !!
.
:::example
:::
variables_cualitativas <- c('species', 'island', 'sex') select( .data = penguins, one_of(variables_cualitativas) ) select( .data = penguins, !!variables_cualitativas )
La función select_if()
permite seleccionar columnas en base a su tipo de dato. Para esto, se puede emplear las funciones is.character()
, is.numeric()
, is.integer()
, is.double()
, is.logical()
, is.factor()
.
:::example
:::
select_if( penguins, is.factor ) select_if( penguins, ~!is.numeric(.) )
:::exercise
En el ejemplo anterior se seleccionaron solo las columnas o variables cuyo valor es de tipo distinto al numérico. Intente por favor seleccionar esta vez aquellas columnas o variables numéricas. :::
select_if( penguins, is.numeric ) select_if( penguins, ~!is.factor(.) )
grade_code("¡Muy bien! Sigue así y lograras grandes avances.")
select()
para renombrar columnasPuede cambiar el nombre de las columnas o variables con la función select()
.
:::example
:::
select( .data = penguins, gender = sex )
rename()
La función rename()
permite cambiar los nombres de las columnas o variables.
:::example
:::
knitr::include_graphics("images/16va_imagen.png")
rename( .data = penguins, gender = sex )
mutate()
A menudo es útil agregar nuevas variables o columnas que son funciones de las ya existentes. Esto se puede hacer por medio de la función mutate()
.
:::example
:::
knitr::include_graphics("images/17va_imagen.png")
mutate( .data = penguins, body_mass_kg = body_mass_g / 1000 ) transmute( .data = penguins, body_mass_kg = body_mass_g / 1000 )
mutate( .data = penguins, body_mass_g_VS_prom_body_mass = body_mass_g - round(mean(body_mass_g, na.rm = TRUE), digits = 1), body_mass_g_VS_min_body_mass = body_mass_g - min(body_mass_g, na.rm = TRUE) )
Para cambiar el nombre de los valores de las columnas o variables cualitativas, se puede usar la función recode()
dentro de la función mutate()
.
:::example
:::
mutate( .data = penguins, sex_2 = recode( .x = sex, 'FEMALE' = 'Hembra', 'MALE' = 'Macho' ) )
Si desea convertir una columna numérica en una columna o variable cualitativa, puede hacer uso de la función case_when()
.
:::example
:::
mutate( .data = penguins, body_mass_2 = case_when( body_mass_g < 3400 ~ 'Liviano', body_mass_g >= 3400 & body_mass_g <= 4400 ~ 'Normal', body_mass_g > 4400 ~ 'Pesado' ) )
relocate()
Una forma fácil de cambiar el orden de las columnas en el conjunto de datos es mediante el uso de la función relocate()
.
:::example
:::
knitr::include_graphics("images/18va_imagen.png")
relocate( .data = penguins, species:sex, .before = culmen_length_mm )
:::exercise
En el ejemplo anterior la columna culmen_length_mm
se movio hacia la última columna por medio de la función .before()
. Intente por favor haciendo uso de la función .after()
mover esa misma columna para que sea la primera columna.
:::
relocate( .data = penguins, species:sex, .after = culmen_length_mm )
summarise()
/summarize()
y group_by()
La función summarise()
(o summarize()
) permite obtener un nuevo conjunto de datos el cual contiene un resumen de una determinada columna, calculando un valor único de los múltiples valores en esa columna.
:::example
:::
summarise( .data = penguins, Prom_length = round(mean(culmen_length_mm, na.rm = TRUE), digits = 2), Prom_depth = round(mean(culmen_depth_mm, na.rm = TRUE), digits = 2) ) Promedio <- function(x){ x = na.omit(x) Suma = sum(x) Total = length(x) Media = Suma/Total print(Media) } summarise( .data = penguins, Prom_length = Promedio(culmen_length_mm), Prom_depth = Promedio(culmen_depth_mm) )
Usar la función summarise()
puede ser útil por sí sola, pero es aún más útil cuando se usa para saber las diferencias entre grupos. Para hacer esto, se puede combinar con la función group_by()
.
:::example
:::
knitr::include_graphics("images/19va_imagen.png")
Grupos <- group_by( .data = penguins, species ) Grupos summarise( .data = Grupos, Prom_lenght = round(mean(culmen_length_mm, na.rm = TRUE), digits = 2), Prom_depth = round(mean(culmen_depth_mm, na.rm = TRUE), digits = 2) )
:::caution La agrupación permite comparar rápidamente diferentes subconjuntos de los datos. La agrupación permite enmarcar la pregunta de análisis en términos de comparar grupos de observaciones, en lugar de observaciones individuales. Esta forma hace que sea más fácil hacer y responder preguntas complejas sobre los datos. :::
summarise()
/summarize()
y across()
A menudo es útil realizar la misma operación en varias columnas. La función across()
permite realizar esto.
:::example
:::
knitr::include_graphics("images/20va_imagen.png")
summarise( .data = penguins, across( .cols = c(culmen_length_mm, culmen_depth_mm, flipper_length_mm), .fns = mean, na.rm = TRUE ) ) summarise( .data = penguins, across( .cols = ends_with('mm'), .fns = mean, na.rm = TRUE ) ) summarise( .data = penguins, across( .cols = where(is.numeric), .fns = mean, na.rm = TRUE ), across( .cols = where(is.factor), .fns = nlevels ) )
%>%
Un enfoque para realizar análisis de datos complejos consiste en crear objetos intermedios para usar en dicho análisis. Este es un flujo de trabajo muy común.
:::example
:::
a1 <- rename( .data = penguins, gender = sex ) a2 <- filter( .data = a1, gender == 'FEMALE' ) a3 <- mutate( .data = a2, body_mass_kg = body_mass_g / 1000 ) mutate( filter( rename( .data = penguins, gender = sex ), gender == 'FEMALE' ), body_mass_kg = body_mass_g / 1000 )
Para solucionar el problema que puede resultar del enfoque anterior, el paquete dplyr
proporciona el operador de tubería (%>%
). El %>%
es un operador que permite encadenas funciones. Lo que hace es tomar la salida de una función y pasarla como entrada de la siguiente función.
:::example
:::
knitr::include_graphics("images/21va_imagen.png")
Peso_fem_kg <- penguins %>% rename(gender = sex) %>% filter(gender == 'FEMALE') %>% mutate(body_mass_kg = body_mass_g / 1000) Peso_fem_kg
:::exercise
Intente por favor calcular el valor medio de la longitud de la aleta (flipper_length_mm
) de las tres especies de pinguinos del conjunto de datos penguins
, pero que sean solo de sexo (sex
) macho (MALE
).
:::
penguins %>% filter(sex == 'MALE') %>% summarise( across( .cols = flipper_length_mm, .fns = mean, na.rm = TRUE ) )
_join()
A menudo los datos se pueden almacenar en múltiples conjuntos de datos. En algún momento se querrá acceder a la información de dichos conjuntos de datos, por lo cual necesitará una forma de poder combinarlos. A este proceso se le denomina unión (join) por la sencilla razón de que unira dichos conjuntos de datos.
El paquete dplyr
cuenta con un conjunto de funciones de combinación para realizar este procedimiento.
Datos_a <- tribble( ~ID , ~x1, '1', 'a1', '2', 'a2' ) Datos_b <- tribble( ~ID , ~x2, '2', 'b1', '3', 'b2' )
save(Datos_a, file = 'data_pkg/Datos_a.rda', compress='xz') save(Datos_b, file = 'data_pkg/Datos_b.rda', compress='xz')
inner_join()
Esta unión retorna todas las columnas del primer y segundo conjunto de datos, pero solo retorna las filas del primer conjunto de datos que coinciden con el segundo conjunto de datos.
:::example
:::
knitr::include_graphics("images/22va_imagen.png")
inner_join( x = Datos_a, y = Datos_b, by = 'ID' )
:::caution
Generalmente, las uniones llevadas a cabo usando la función inner_join()
no son apropiadas para su uso en el análisis de datos dado que es muy fácil perder observaciones.
:::
left_join()
Esta unión retorna todas las columnas del primer y segundo conjunto de datos, pero solo retorna las filas del conjunto de datos que se especifica en x
como argumento.
:::example
:::
knitr::include_graphics("images/23va_imagen.png")
left_join( x = Datos_a, y = Datos_b, by = 'ID' )
right_join()
Esta función es opuesta a la función left_join()
, en el sentido de que solo retorna las filas del conjunto de datos que se especifica en y
como argumento.
:::example
:::
knitr::include_graphics("images/24va_imagen.png")
right_join( x = Datos_a, y = Datos_b, by = 'ID' )
full_join()
Esta unión retorna toda las columnas y filas de ambos conjunto de datos. De esta forma, retorna una fila para cualquier observación independiente si coinciden o no.
:::example
:::
knitr::include_graphics("images/25va_imagen.png")
full_join( x = Datos_a, y = Datos_b, by = 'ID' )
:::caution
Si deseas ver un resumen de las posibilidades que ofrece el paquete dplyr
y profundizar más en su uso, puedes mirar su hoja de trucos aquí.
:::
La visualización de datos es la práctica de convertir datos en una representación gráfica. Para comprender cuán significativa es la visualización de los datos, un hecho simple es que a los cerebros humanos les resulta más difícil comprender datos complejos cuando están codificados en números y texto en comparación con los gráficos.
Es impensable cualquier sector profesional sin el uso de elementos de visualización, pues estos facilitan la transmisión de información. De las misma forma, es erróneo considerar a la visualización de datos como un recurso secundario o adicional, finalmente prescindible.
:::caution El concepto de visualización de datos no es nuevo. De hecho, la visualización de datos ha existido durante siglos. Aquí podrás encontrar una visualización que corresponde a una línea de tiempo sobre el desarrollo de los gráficos estadísticos. :::
Una gramática de gráficos es un marco que sigue un enfoque en capas para describir y construir visualizaciones o gráficos de manera estructurada.
knitr::include_graphics("images/26va_imagen.png")
Para explicar el concepto de la gramática de gráficos en capas implementado en el paquete ggplot2
se empleara el conjunto de datos pokemon
. Estos datos es uno de los muchos conjuntos de datos del proyecto Datos de miércoles cuyo propósito consiste en buscar que lo usuarios de R
desarrollen habilidades de visualización y procesado de datos usando las herramientas del tidyverse
.
knitr::include_graphics("images/27va_imagen.png")
Como puedes ver, este conjunto de datos contiene siete variables:
id
se describe la identificación de cada pokémon.nombre
podrá encontrar el nombre del pokémon.tipo
se describe el tipo de pokémon (eléctrico, agua, veneno, fantasma, hielo, psíquico, entre otros).ataque
podrá encontrar el daño que el pokémon puede causar en ataques.defensa
podrá encontrar la resistencia del pokémon al daño ante ataques.velocidad
se describe la velocidad del pokémon al atacar en cada ronda.puntos_vida
se describe la cantidad de daño que puede resistir cada pokémon.pokemon <- readr::read_csv("https://raw.githubusercontent.com/cienciadedatos/datos-de-miercoles/master/datos/2019/2019-07-10/pokemon.csv") pokemon <- pokemon %>% mutate(ataque = ataque + fuerza_especial_ataque, defensa = defensa + fuerza_especial_defensa) %>% select(ID_poke, nombre_ingles, tipo_1, ataque, defensa, velocidad, puntos_vida) %>% rename('id' = ID_poke, 'nombre' = nombre_ingles, 'tipo' = tipo_1) #filter(nombre %in% c('Charmander', 'Bulbasaur', 'Butterfree', 'Pikachu', 'Squirtle')) save(pokemon, file = 'data_pkg/pokemon.rda', compress='xz') pokemon_2 <- pokemon %>% select(nombre_ingles, tipo_2) %>% rename('nombre' = nombre_ingles, 'tipo_2' = tipo_2) save(pokemon_2, file = 'data_pkg/pokemon_2.rda', compress='xz')
Estas son las capas que determinan la representación visual de los datos. Para asociar el gráfico a un conjunto de datos en específico se emplea el argumento data
. Luego se define un mapeo haciendo uso de la función aes()
dentro de ggplot()
para seleccionar las variables a graficar. Por último, con geoms
se indica cómo se representaran los datos en el gráfico.
:::example
:::
pokemon <- pokemon %>% mutate( fuerza_total = ataque + defensa + velocidad + puntos_vida ) ggplot( data = pokemon, aes(x = ataque, y = fuerza_total) ) + geom_point() #ggplot( #data = pokemon, #aes(x = ataque, y = fuerza_total) #) + #geom_point( #size = 3.4, #colour = 'yellow', #fill = 'yellow', #alpha = 0.4, #shape = 'square filled' #)
Lista de colores en
R
y formas de puntos
knitr::include_graphics("images/colores_1.png")
knitr::include_graphics("images/colores_2.png")
knitr::include_graphics("images/colores_3.png")
knitr::include_graphics("images/colores_4.png")
knitr::include_graphics("images/colores_5.png")
knitr::include_graphics("images/colores_6.png")
knitr::include_graphics("images/colores_7.png")
knitr::include_graphics("images/puntos.png")
:::exercise
En el ejemplo anterior se uso la función aes()
para indicarle al geom_point()
cuáles serían las posiciones x
y y
para cada punto. Sin embargo otra propiedad estetica que se puede modificar es el color de los puntos por medio del argumento colour
. Intente por favor modificar el código anterior de modo que se coloreen los puntos de acuerdo al tipo de pokemon.
:::
pokemon <- pokemon %>% mutate( fuerza_total = ataque + defensa + velocidad + puntos_vida )
ggplot( # Primera forma data = pokemon, aes(x = ataque, y = fuerza_total, colour = tipo, fill = tipo) ) + geom_point() ggplot( # Segunda forma data = pokemon, aes(x = ataque, y = fuerza_total) ) + geom_point(aes(colour = tipo, fill = tipo))
:::caution
Las asignaciones estéticas, definidas con la función aes()
, describen cómo las variables se asignan a propiedades visuales o estéticas. Además de una posición horizontal (x
) y vertical (y
), cada punto puede tener también un tamaño (size
), un color (colour
y fill
) y una forma (shape
).
:::
Las facetas son una de las capas más importantes para construir una visualización de datos efectiva. Las facetas consisten de múltiples gráficos de lado a lado utilizados para mostrar los niveles de una variable categórica. Hay dos tipos de facetado:
:::example
:::
pokemon <- pokemon %>% mutate( fuerza_total = ataque + defensa + velocidad + puntos_vida ) ggplot( data = pokemon, aes(x = ataque, y = fuerza_total, colour = tipo, fill = tipo) ) + geom_point() #+ #facet_wrap(~ tipo) #ggplot( #data = pokemon, #aes(x = ataque, y = fuerza_total, colour = tipo, fill = tipo) #) + #geom_point() + #facet_wrap(~ tipo, ncol = 3, dir = 'h') # "h" de horizontal. #ggplot( #data = pokemon, #aes(x = ataque, y = fuerza_total, colour = tipo, fill = tipo) #) + #geom_point() + #facet_wrap(~ tipo, nrow = 6, dir = 'v') # "v" de vertical.
:::example
:::
pokemon_2 <- pokemon %>% mutate( fuerza_total = ataque + defensa + velocidad + puntos_vida ) %>% inner_join(pokemon_2, 'nombre') pokemon_2 #ggplot( #data = pokemon_2, #aes(x = ataque, y = fuerza_total) #) + #geom_point() + #facet_grid(tipo ~ tipo_2) #ggplot( #data = pokemon_2, #aes(x = ataque, y = fuerza_total) #) + #geom_point() + #facet_grid(vars(tipo), vars(tipo_2))
La capa 5 de estadísticas (stat_
), permite presentar resumenes estadísticos dentro de los gráficos por medio de una transformación estadística de los datos.
:::example
:::
pokemon <- pokemon %>% mutate( fuerza_total = ataque + defensa + velocidad + puntos_vida ) Modelo <- lm( formula = fuerza_total ~ ataque, data = pokemon ) summary(Modelo) Int_Pend <- tribble( ~Intercepto, ~Pendiente, 138.24, 1.96 ) #ggplot( #data = pokemon, #aes(x = ataque, y = fuerza_total) #) + #geom_point() + #geom_abline( #data = Int_Pend, #aes(intercept = Intercepto, slope = Pendiente) #) #ggplot( #data = pokemon, #aes(x = ataque, y = fuerza_total) #) + #geom_point() + #stat_smooth(method = 'lm', colour = 'black') #ggplot( #data = pokemon, #aes(x = ataque, y = fuerza_total, colour = tipo, fill = tipo) #) + #geom_point() + #facet_wrap(~ tipo) + #stat_smooth(method = 'lm', colour = 'black')
:::caution
Además de la función geom_point()
, existen otras funciones geom_
que permiten indicar la forma en como se desea representar los datos en la visualización. Cada una de estas funciones pueden estar asociadas con una función stat_
que se encargan de realizar los cálculos para obtener los parámetros necesarios para realizar una determinada gráfica. Aquí podrás encontrar una lista de estas funciones.
:::
:::example
:::
Un sistema de coordenadas asigna la posición de los objetos geométricos en la superficie de una gráfica. Con el paquete ggplot2
, dicha posición se especifica mediante dos coordenadas (x
y y
).
:::example
:::
pokemon <- pokemon %>% mutate( fuerza_total = ataque + defensa + velocidad + puntos_vida ) ggplot( data = pokemon, aes(x = ataque, y = fuerza_total) ) #+ #geom_point() + #coord_polar()
Existen dos tipos de sistema de coordenadas comúnenmente usados en el paquete ggplot2
:
:::example
:::
knitr::include_graphics("images/Astronauts_tidytuesday.png")
La capa 7 de tema, ayuda a que las gráficas sean estéticamente agradables o coincidan con una guía de estilo existente, sin afectar lo ya hecho a partir de las capas anteriormente presentadas.
pokemon <- pokemon %>% mutate( fuerza_total = ataque + defensa + velocidad + puntos_vida ) ggplot( data = pokemon, aes(x = ataque, y = fuerza_total, colour = tipo, fill = tipo) ) + geom_point(alpha = 0.4) + facet_wrap(~ tipo, ncol = 6) + stat_smooth(method = 'lm', colour = 'black') #+ #labs( #x = 'Ataque', #y = 'Fuerza total', #title = 'Relación entre ataque y fuerza total de acuerdo al tipo de pokemon' #) + #scale_colour_manual(values = c('cyan', 'gray34', 'deeppink', 'orangered', 'yellow', 'turquoise', 'springgreen', 'red', 'black', 'darkmagenta', 'green', 'blue', 'maroon', 'violetred', 'chocolate', 'coral4', 'thistle', 'pink4')) + #scale_fill_manual(values = c('cyan', 'gray34', 'deeppink', 'orangered', 'yellow', 'turquoise', 'springgreen', 'red', 'black', 'darkmagenta', 'green', 'blue', 'maroon', 'violetred', 'chocolate', 'coral4', 'thistle', 'pink4'))
El paquete ggplot2
viene con una serie de temas integrados. Estos son:
knitr::include_graphics("images/32va_imagen.png")
:::example
:::
pokemon <- pokemon %>% mutate( fuerza_total = ataque + defensa + velocidad + puntos_vida ) ggplot( data = pokemon, aes(x = ataque, y = fuerza_total, colour = tipo, fill = tipo) ) + geom_point(alpha = 0.4) + facet_wrap(~ tipo, ncol = 6) + stat_smooth(method = 'lm', colour = 'black') + labs( x = 'Ataque', y = 'Fuerza total', title = 'Relación entre ataque y fuerza total de acuerdo al tipo de pokemon' ) + scale_colour_manual(values = c('cyan', 'gray34', 'deeppink', 'orangered', 'yellow', 'turquoise', 'springgreen', 'red', 'black', 'darkmagenta', 'green', 'blue', 'maroon', 'violetred', 'chocolate', 'coral4', 'thistle', 'pink4')) + scale_fill_manual(values = c('cyan', 'gray34', 'deeppink', 'orangered', 'yellow', 'turquoise', 'springgreen', 'red', 'black', 'darkmagenta', 'green', 'blue', 'maroon', 'violetred', 'chocolate', 'coral4', 'thistle', 'pink4')) + theme_bw()
:::caution
ggplot2
no está limitado a los temas que tiene integrados. Puede hacer uso de otros paquetes, como ggthemes
de Jeffrey Arnold, para disponer de muchos más temas. Aquí podrá encontrar información sobre este paquete.
:::
Los temas integrados anteriores cuentan con componentes individuales que a su
vez pueden ser modificados usando la función theme()
. Una manera general del código que se puede emplear para esto es de la forma: plot + theme(element.name = element_function())
.
Estos pueden afectar la apariencia del gráfico en su conjunto. Estos son:
knitr::include_graphics("images/33va_imagen.png")
:::example
:::
pokemon <- pokemon %>% mutate( fuerza_total = ataque + defensa + velocidad + puntos_vida ) ggplot( data = pokemon, aes(x = ataque, y = fuerza_total, colour = tipo, fill = tipo) ) + geom_point(alpha = 0.4) + facet_wrap(~ tipo, ncol = 6) + stat_smooth(method = 'lm', colour = 'black') + labs( x = 'Ataque', y = 'Fuerza total', title = 'Relación entre ataque y fuerza total de acuerdo al tipo de pokemon' ) + scale_colour_manual(values = c('cyan', 'gray34', 'deeppink', 'orangered', 'yellow', 'turquoise', 'springgreen', 'red', 'black', 'darkmagenta', 'green', 'blue', 'maroon', 'violetred', 'chocolate', 'coral4', 'thistle', 'pink4')) + scale_fill_manual(values = c('cyan', 'gray34', 'deeppink', 'orangered', 'yellow', 'turquoise', 'springgreen', 'red', 'black', 'darkmagenta', 'green', 'blue', 'maroon', 'violetred', 'chocolate', 'coral4', 'thistle', 'pink4')) + theme_bw() + theme( plot.background = element_rect(colour = 'gray34', fill = 'white', size = 1.4), plot.title = element_text(size = 10, colour = 'black', face = 'bold') )
Estos pueden controlar la apariencia de los ejes. Estos son:
knitr::include_graphics("images/34va_imagen.png")
:::example
:::
pokemon <- pokemon %>% mutate( fuerza_total = ataque + defensa + velocidad + puntos_vida ) ggplot( data = pokemon, aes(x = ataque, y = fuerza_total, colour = tipo, fill = tipo) ) + geom_point(alpha = 0.4) + facet_wrap(~ tipo, ncol = 6) + stat_smooth(method = 'lm', colour = 'black') + labs( x = 'Ataque', y = 'Fuerza total', title = 'Relación entre ataque y fuerza total de acuerdo al tipo de pokemon' ) + scale_colour_manual(values = c('cyan', 'gray34', 'deeppink', 'orangered', 'yellow', 'turquoise', 'springgreen', 'red', 'black', 'darkmagenta', 'green', 'blue', 'maroon', 'violetred', 'chocolate', 'coral4', 'thistle', 'pink4')) + scale_fill_manual(values = c('cyan', 'gray34', 'deeppink', 'orangered', 'yellow', 'turquoise', 'springgreen', 'red', 'black', 'darkmagenta', 'green', 'blue', 'maroon', 'violetred', 'chocolate', 'coral4', 'thistle', 'pink4')) + theme_bw() + theme( plot.background = element_rect(colour = 'gray34', fill = 'white', size = 1.4), plot.title = element_text(size = 10, colour = 'black', face = 'bold'), axis.text.x = element_text(size = 10, face = 'bold', angle = -45, hjust = 0, vjust = 1), axis.text.y = element_text(size = 10, face = 'bold'), axis.title = element_text(size = 12, face = 'bold') )
Estos pueden controlar la apariencia de todas las leyendas. Estos son:
knitr::include_graphics("images/35va_imagen.png")
:::example
:::
pokemon <- pokemon %>% mutate( fuerza_total = ataque + defensa + velocidad + puntos_vida ) ggplot( data = pokemon, aes(x = ataque, y = fuerza_total, colour = tipo, fill = tipo) ) + geom_point(alpha = 0.4) + facet_wrap(~ tipo, ncol = 6) + stat_smooth(method = 'lm', colour = 'black') + labs( x = 'Ataque', y = 'Fuerza total', title = 'Relación entre ataque y fuerza total de acuerdo al tipo de pokemon' ) + scale_colour_manual(values = c('cyan', 'gray34', 'deeppink', 'orangered', 'yellow', 'turquoise', 'springgreen', 'red', 'black', 'darkmagenta', 'green', 'blue', 'maroon', 'violetred', 'chocolate', 'coral4', 'thistle', 'pink4')) + scale_fill_manual(values = c('cyan', 'gray34', 'deeppink', 'orangered', 'yellow', 'turquoise', 'springgreen', 'red', 'black', 'darkmagenta', 'green', 'blue', 'maroon', 'violetred', 'chocolate', 'coral4', 'thistle', 'pink4')) + theme_bw() + theme( plot.background = element_rect(colour = 'gray34', fill = 'white', size = 1.4), plot.title = element_text(size = 10, colour = 'black', face = 'bold'), axis.text.x = element_text(size = 10, face = 'bold', angle = -45, hjust = 0, vjust = 1), axis.text.y = element_text(size = 10, face = 'bold'), axis.title = element_text(size = 12, face = 'bold'), legend.position = 'none' )
Estos pueden controlar la apariencia de los gráficos facetados. Estos son:
knitr::include_graphics("images/36va_imagen.png")
:::example
:::
pokemon <- pokemon %>% mutate( fuerza_total = ataque + defensa + velocidad + puntos_vida ) ggplot( data = pokemon, aes(x = ataque, y = fuerza_total, colour = tipo, fill = tipo) ) + geom_point(alpha = 0.4) + facet_wrap(~ tipo, ncol = 6) + stat_smooth(method = 'lm', colour = 'black') + labs( x = 'Ataque', y = 'Fuerza total', title = 'Relación entre ataque y fuerza total de acuerdo al tipo de pokemon' ) + scale_colour_manual(values = c('cyan', 'gray34', 'deeppink', 'orangered', 'yellow', 'turquoise', 'springgreen', 'red', 'black', 'darkmagenta', 'green', 'blue', 'maroon', 'violetred', 'chocolate', 'coral4', 'thistle', 'pink4')) + scale_fill_manual(values = c('cyan', 'gray34', 'deeppink', 'orangered', 'yellow', 'turquoise', 'springgreen', 'red', 'black', 'darkmagenta', 'green', 'blue', 'maroon', 'violetred', 'chocolate', 'coral4', 'thistle', 'pink4')) + theme_bw() + theme( plot.background = element_rect(colour = 'gray34', fill = 'white', size = 1.4), plot.title = element_text(size = 10, colour = 'black', face = 'bold'), axis.text.x = element_text(size = 10, face = 'bold', angle = -45, hjust = 0, vjust = 1), axis.text.y = element_text(size = 10, face = 'bold'), axis.title = element_text(size = 12, face = 'bold'), legend.position = 'none', strip.background = element_rect(colour = 'black', fill = 'gray64'), strip.text = element_text(size = 10, colour = 'black', face = 'bold') )
:::caution
Si deseas conocer más sobre la gramática de gráficos en capas propuesto por Hadley Wickham, te invitamos a revisar su articulo titulado A layered grammar of graphics que cubre en detalle su propuesta en la construcción de gráficos y también habla sobre el paquete ggplot2
.
:::
ggplot2
Ya vimos como funciona la gramática de gráficas y las distintas capas que ponemos agregar y utilizar para crearlos, es hora de que pongamos en practica estos conceptos. Iniciemos viendo las capas individualmente partiendo de un esquema que no posee datos, para ello debemos cargar el paquete ggplot2
y ejecutar la siguiente linea de código.
:::example
:::
# Cargando paquete library(ggplot2) # Esquema base ggplot()
Generalmente en la función ggplot()
asignamos los datos que vamos a graficar usando el argumento data. Supongamos que tenemos un set de datos llamado "mis_datos", en este caso la función del esquema base con la capa de los datos quedaría de la forma ggplot(data=mis_datos)
.
Continuemos manejando el esquema de un gráfico sin usar datos. El siguiente paso es agregar la capa de estética, la cual puede añadirse directamente en la función ggplot()
con el argumento mapping = aes()
o de manera independiente con la función aes()
. Para el segundo caso debemos agregar una capa usando el operador +
. Veamos los dos casos estableciendo valores arbitrarios tanto para x como para y.
# Cargando paquete library(ggplot2) # Esquema base con mapping ggplot(mapping = aes(x=0,y=0)) # Esquema base con aes() ggplot()+ aes(x=0,y=0)
Después de estas dos capas se agrega la capa de geometrías, que en general depende de el tipo de gráfico que necesitemos o el que mejor se adapte a nuestros datos. Para conocer algunos gráficos agruparemos algunos de ellos en 7 conjuntos: 1) Gráficos de distribución, 2) Gráficos de correlación, 3) Gráficos de clasificación y 4) Mapas.
Estos gráficos se utilizan generalmente en variables continuas como edad, altura, masa, etc. Para iniciar esta sección de gráficos haremos el gráfico de densidad para la altura de los personajes de Starwars.
Starwars
:::example
:::
ggplot(data = Starwars)+ # Datos aes(x=height)+ # Estética geom_density() # Geometría
Sabemos que los personajes de Starwars no son todos del mismo genero, entonces modifiquemos el gráfico anterior utilizando facetas para distinguir por genero usando facet_wrap()
y eliminado los personajes que no tiene genero definido. Ademas usemos el operador tubería %>%
para realizar una operación secuencial que una directamente nuestros datos con el gráfico.
Starwars %>% filter(!is.na(gender)) %>% ggplot(data=.)+ # Datos (.) aes(x=height)+ # Estética geom_density()+ # Geometría facet_wrap(~gender) #Faceta
Otra forma de diferenciar por género sin usar las facetas, es otorgar color a la variable distintiva, esto podemos hacerlo en la capa estética del gráfico agregando para este caso “color= gender”. Veamos cómo quedaría el gráfico.
Starwars %>% filter(!is.na(gender)) %>% ggplot(data=.)+ # Datos (.) aes(x=height,color=gender)+ # Estética con color geom_density() # Geometría
Ahora hagamos modificaciones de imagen para personalizar y embellecer los dos gráficos anteriores, para ello utilizaremos argumentos como “alpha” y "fill" agregando ademas la capa de tema. También nos apoyaremos en la libreríapatchwork
para fusionar los dos gráficos en uno solo.
patchwork
para entender su funcionamiento #libreria library(patchwork) base <- Starwars %>% filter(!is.na(gender)) %>% ggplot(data=.)+ # Datos (.) aes(x=height,color=gender,fill=gender)+ # Estética con color y relleno geom_density(alpha=.3) # Geometría (base +theme_classic()) / # base con tema clásico (base +theme_dark()+facet_wrap(~gender)) #base con faceta y tema oscuro
Sigamos usando la misma variable “height” de Starwars para aprender cómo se hacen los gráficos de histograma, en este caso reuniremos todos los tipos de gráficos que hicimos en el ejemplo anterior en uno solo. Fijate muy bien en la sintaxis del código y todos los comentarios en él. El argumento “alpha” hace referencia a transparencia y el argumento “fill” a relleno.
:::example
:::
# Gráfico base base <- Starwars %>% filter(!is.na(gender)) %>% ggplot(data=.)+ # Datos (.) aes(x=height)+ # Estética básica geom_histogram(alpha=.5) # Geometría # opciones sobre la base base / (base +aes(fill=gender)+theme_classic()) / #base + estética con color + tema clásico (base +aes(fill=gender)+theme_dark()+facet_wrap(~gender)) #base + estética con color + faceta + tema clasico
Veamos otro tipo de gráfico que es conocido como Boxplot, para estos gráficos seguiremos usando los mismos datos que en los anteriores ejemplo, agregando las capas de estadísticas y coordenadas. Veamos un ejemplo para la variable “height” para cada genero.
:::example
:::
# Gráfico base Starwars %>% filter(!is.na(gender)) %>% ggplot(data=.)+ # Datos (.) aes(x=gender,y=height)+ # Estética básica geom_boxplot(fill=NA)+ # geometría sin relleno stat_summary(fun=mean,geom="point", color="red") + # Estadísticas con geometría coord_flip() # Coordenadas invertidas
El siguiente gráfico es conocido como el gráfico de violín y lo implementaremos igual que el boxplot salvo por la geometría.
:::example
:::
# Gráfico base Starwars %>% filter(!is.na(gender)) %>% ggplot(data=.)+ # Datos (.) aes(x=gender,y=height,fill=gender)+ # Estética básica con relleno geom_violin(alpha=.3)+ # geometría con transparencia stat_summary(fun=mean,geom="point", color="red") + # Estadísticas con geometría coord_flip() # Coordenadas invertidas
Hasta el momento hemos visto gráficas con una sola geometría, ahora veamos gráficas con varias geometrías que incluya las dos gráficas anteriores y una tercera geometría conocida como geom_jitter()
.
:::example
:::
# Gráfico base Starwars %>% filter(!is.na(gender)) %>% ggplot(data=.)+ # Datos (.) aes(x=gender,y=height,fill=gender,color=gender)+ # Estética color y con relleno geom_boxplot(alpha=.2)+ # geometría boxplot con transparencia geom_violin(alpha=.3)+ # geometría violín con transparencia geom_jitter()+ stat_summary(fun=mean,geom="point",size=6,shape=23,color="black") + # Estadísticas con geometría puntos coord_flip() + # Coordenadas invertidas theme_light() #tema
Los siguientes gráficos corresponde a ilustraciones que muestran la relación entre dos o más variables continuas. Veamos el primero de ellos el cual se denomina gráfico de dispersión entre dos variables. Usaremos los datos de masa y altura de Starwars
para relacionarlas en este gráfico.
:::example
:::
Starwars %>% filter(!is.na(gender)) %>% filter(mass<quantile(Starwars$mass,probs = .99,na.rm = T)) %>% ggplot(data = .)+ #Datos aes(x=height,y=mass,color=gender)+ #estética geom_point() # geometría
Usando las mismas variables del ejemplo anterior veamos un gráfico de densidad de dos dimensiones. En general este gráfico es útil cuando nuestras variables a relacionar tiene una gran cantidad de observaciones que son difíciles de procesar por un geom_point()
.
:::example
:::
Starwars %>% filter(!is.na(gender)) %>% filter(mass<quantile(Starwars$mass,probs = .99,na.rm = T)) %>% ggplot(data = .)+ #Datos aes(x=height,y=mass)+ # Estética stat_density_2d(aes(fill = ..density..), geom = "raster", contour = FALSE) #Estadística con geometría
Otro tipo de gráfico que podemos realizar es el que relaciona el tiempo con una variable continua, estas son las denominadas series de tiempo. Para el siguiente ejemplo tomaremos la variable de precio del petróleo de el dataset Seatbelts
, el cual corresponde a una serie temporal que muestra los totales mensuales de conductores de automóviles en Gran Bretaña que murieron o resultaron gravemente heridos desde enero de 1969 hasta diciembre de 1984. Para mayor información del dataset ejecuta ?Seatbelts
:::example
:::
as.matrix(Seatbelts) %>% data.frame() %>% mutate(date=seq(as.Date("1969-01-01"),as.Date("1984-12-01"), by="month")) %>% ggplot(data=.)+ #Datos aes(x=date,y=PetrolPrice)+ #Estética geom_line()+ # Geometría de lineas geom_point()+ # Geometría de puntos scale_x_date(date_breaks = "12 month",date_labels = "%y-%m")+ #Coordenadas theme_light() # Tema
Ya vimos cómo relacionar gráficamente dos variables continuas y una variable continua con el tiempo, veamos ahora cómo relacionar más de dos variables continuas en un gráfico. Para ello nos valdremos de la matriz de correlación de las 4 variables numéricas del dataset iris
. Consulta la información del dataset con ?iris
para obtener más información.
:::example
:::
iris %>% select_if(is.numeric) %>% cor(method = "pearson") %>% as_tibble() %>% mutate(name=colnames(iris)[1:4]) %>% relocate(name) %>% pivot_longer(cols = 2:5,names_to="name_2",values_to="Corr") %>% ggplot(data=.)+ #Datos aes(x=name,y=name_2,fill=Corr)+ # Estética geom_tile()+ # Geometría scale_fill_gradientn(colours = topo.colors(5),limits=c(-1,1),breaks=seq(-1,1,.2)) #Coordenadas
Los gráficos de clasificación corresponden generalmente a conteos o resúmenes numéricos de variables categóricas. Como primer ejemplo veamos un gráfico de barras de el número de autos que tienen 4, 6 u 8 cilindros en el dataset mtcars
. Para este caso la función geom_bar()
hace automáticamente el conteo de los autos, width
corresponde al grosor de las barras.
:::example
:::
mtcars %>% ggplot(data = .)+ #Datos aes(x=factor(cyl),fill=factor(cyl))+ #Estética geom_bar(width = .5) + #Geometría theme_linedraw() #tema
En el casos de que nuestros datos tengan el conteo o variable de interés de nuestra variable categórica, podemos usar el argumento stat=”identity”
en geom_bar()
para que este no sea calculado por la función, en cuyo caso debemos asignar adicionalmente el conteo o variable y
.
mtcars %>% group_by(cyl) %>% summarise(n=n()) %>% ggplot(data = .)+ #Datos aes(x=factor(cyl),fill=factor(cyl),y=n)+ # Estética geom_bar(stat ="identity", width = .5)+ # Geometría coord_flip() # Coordenadas invertidas
Otra forma de generar un gráfico de barras es combinar dos geometrías y crear el gráfico denominado “lollipop”, el cual es una visualización un poco más llamativa que cumple el mismo fin con algunos toques estéticos.
:::example
:::
data.frame( x=letters, y=sample(1:60,26,replace = T)) %>% ggplot(data=.)+ #Datos aes(x=x,y=y)+ #Estética base geom_point(color="blue", size=4,alpha=0.5)+ #Geometría de puntos geom_segment(aes(x=x, xend=x, y=0, yend=y),color="skyblue")+ #Geometría de segmentos con estética adicional theme_classic() # Tema
Retomemos los datos del número de cilindros de mtcars
con su respectivo conteo y veamos un gráfico de barras modificado utilizando coord_polar()
. Recuerda consultar la documentación de esta función para tener mayor claridad respecto al gráfico.
:::example
:::
mtcars %>% group_by(cyl) %>% summarise(n=n()) %>% ggplot(data=.)+ #Datos aes(x=factor(cyl),y=n, fill=factor(cyl))+ # Estética geom_bar(stat = "identity")+ #Geometría scale_y_continuous(breaks = seq(0,16,2))+ #coordenadas coord_polar(theta="y") #Coordenadas
Otro gráfico que se puede realizar con coord_polar()
usando los datos del número de cilindros de mtcars
es el siguiente.
:::example
:::
mtcars %>% ggplot(data=.) + #Datos aes(x = factor(1), fill = factor(cyl))+ #Estética geom_bar(width = 1, color="white")+ # Geometría coord_polar(theta = "y",start = pi / 3)+ #Coordenadas scale_fill_brewer(palette = 7)+ #Coordenadas theme_void() #Tema
Otra forma de representar los datos del número de cilindros de mtcars
con su respectivo conteo es con el gráfico de rosca, el cual combina dos geometrías y aplicar retoques estéticos para lograr una visualización más llamativa. Para este caso se necesita manipular los datos de una forma específica que veremos a continuación.
:::example
:::
mtcars %>% group_by(cyl) %>% summarise(n=n()) %>% mutate(frac=n/sum(n), ymax=cumsum(frac), ymin=c(0,head(ymax,-1)), pos=(ymax+ymin)/2, label=paste0(cyl,"\n valor:",n)) %>% ggplot(data=.)+ #Datos aes(ymax=ymax, ymin=ymin, xmax=4, xmin=3, fill=factor(cyl))+ #Estética geom_rect() + #Geometría 1 geom_text( x=2.5, aes(y=pos, label=label, color=factor(cyl)), size=4)+ #Geometría 2 xlim(c(1, 4))+ # Coordenadas limites eje x coord_polar(theta="y")+ #Coordenadas scale_fill_viridis_d() + # Coordenadas escala de relleno scale_color_viridis_d() + # Coordenadas escala de color theme_void() #Tema
Como último gráfico de esta sección tenemos las barras apiladas, que pueden ser útiles para ver conteos en simultáneo de dos variables categóricas. Para este caso representamos el conteo de cilindros y carburadores de los automóviles de mtcars
.
:::example
:::
mtcars %>% ggplot(data=.)+ #Datos aes(x=factor(cyl),fill=factor(carb))+ #Estética geom_bar()+ #Geometría scale_fill_viridis_d(option = "plasma") #Coordenadas
La cuestión con los mapas es un poco más compleja y para graficarlos tenemos múltiples geometrías que nos pueden ayudar como geom_map()
, geom_sf()
, geom_polygon()
, etc. Sin embargo el uso de cada una de ellas depende exclusivamente del formato de los datos y para ello debemos relacionarnos más con análisis geoespacial. Para no adentrarnos y extendernos en los formatos de datos espaciales presentaremos dos breves ejemplos nombrando paquetes útiles para graficar mapas.
El primer ejemplo consiste en graficar el número de asesinatos por cada 100.000 personas en los distintos estados de norte america. Para graficar este mapa usamos los datos USArrests
, te invito a que consultes su estructura y lo explores detalladamente.
:::example
:::
USArrests %>% ggplot(data=.)+#Datos aes(x=long,y=lat,group=group,order=order,fill=Murder)+ #Estética geom_polygon(color="white")+ # Geometría scale_fill_distiller(palette = 4)+ #Coordenadas labs(title = "Asesinatos USA - 1973")+ # Etiquetas theme_void() #Tema
El segundo ejemplo es un gráfico de homicidios en Colombia por departamentos para el año 2013.
:::example
:::
departamentos %>% ggplot(data=.)+ #Datos geom_polygon(aes(x=long,y=lat,group=group,order=order,fill=homicidios))+ #Geometría de forma geom_text(data = . %>% distinct(depto,lab_long,lab_lat), aes(x=lab_long,y=lab_lat, label=depto),size=1.5)+ #Geometría de texto scale_fill_viridis_c()+ #Coordenadas escala de color labs(title = "Homicidios Colombia - 2013")+ # Etiquetas theme_void() #Tema
Otros paquetes paquetes que podemos utilizar para graficar mapas y datos geoespaciales son tmap
, leaflet
, ggmap
y algunas de sus dependencias, aunque estas tres son las más relacionadas con el tidyverse
.
Ya en este punto de seguro se preguntará ¿es posible ir más alla de lo que se ha visto hasta ahora? La respuesta es definitivamente un si. Un ejemplo de ello es la famosa visualización de Hans Rosling donde representaba la población mundial, la expectativa de vida y varios indicadores económicos mediante una visualización animada.
Afortunadamente, hacer este tipo de visualizaciones se ha vuelto hoy en día bastante simple mediante el uso de paquetes de R
.
Al crear gráficas animadas, el gráfico no se mueve sino que en realidad son muchos gráficos individuales que luego se unen como si tratase de cuadros de película:
knitr::include_graphics("images/37va_imagen.png")
:::example
:::
library(ggrepel) gapminder_2 <- gapminder %>% filter(country == 'China') gapminder %>% rename( 'Continente' = continent ) %>% mutate( Continente = recode( .x = Continente, 'Africa' = 'África', 'Americas' = 'América', 'Asia' = 'Asia', 'Europe' = 'Europa', 'Oceania' = 'Oceanía' ) ) %>% ggplot( data = ., aes(x = gdpPercap, y = lifeExp) ) + geom_point(aes(size = pop, colour = Continente, fill = Continente), alpha = 0.4) + facet_wrap(~ year) + scale_colour_manual(values = c('yellow', 'red', 'black', 'cyan', 'green')) + scale_fill_manual(values = c('yellow', 'red', 'black', 'cyan', 'green')) + scale_size_area(guide = FALSE, max_size = 14) + scale_x_log10() + labs(x = 'PIB per cápita', y = 'Expectativa de vida al nacer') + theme_bw() + theme( axis.text = element_text(size = 12, face = 'bold'), axis.title = element_text(size = 14, face = 'bold'), plot.title = element_text(size = 14, face = 'bold'), legend.position = 'top', legend.title = element_text(size = 14, face = 'bold'), legend.text = element_text(size = 12), strip.text = element_text(size = 12, face = 'bold'), strip.background = element_rect(colour = 'black', fill = 'gray64') ) + geom_text_repel( data = gapminder_2, aes(label = country), size = 5, box.padding = unit(2.8, 'lines'), point.padding = unit(0.8, 'lines'), segment.color = 'black' )
gapminder::gapminder
knitr::include_graphics("images/ejemplo_gapminder.png")
knitr::include_graphics("images/ejemplo_gapminder_animado.gif")
Para usar el paquete gganimate
se debe instalar primero. Para esto, el paquete se pueden instalar de dos formas:
#install.packages('gganimate') #devtools::install_github('thomasp85/gganimate')
y luego se debe cargar una vez se necesite:
#library(gganimate)
transition_*()
Las funciones transition_*()
son un conjunto de funciones encargadas de interpretar los datos y distribuirlos en subconjuntos de datos de acuerdo a una de sus variables, la variable de transición.
:::example
:::
gapminder %>% rename( 'Continente' = continent ) %>% mutate( Continente = recode( .x = Continente, 'Africa' = 'África', 'Americas' = 'América', 'Asia' = 'Asia', 'Europe' = 'Europa', 'Oceania' = 'Oceanía' ) ) %>% ggplot( data = ., aes(x = gdpPercap, y = lifeExp) ) + geom_point(aes(colour = Continente, fill = Continente, size = pop), alpha = 0.4) + scale_colour_manual(values = c('yellow', 'red', 'black', 'cyan', 'green')) + scale_fill_manual(values = c('yellow', 'red', 'black', 'cyan', 'green')) + scale_size_area(max_size = 14, guide = FALSE) + scale_x_log10() + labs(x = 'PIB per cápita', y = 'Expectativa de vida al nacer') + theme_bw() + theme( axis.text = element_text(size = 12, face = 'bold'), axis.title = element_text(size = 14, face = 'bold'), legend.position = c(0.8, 0.2), legend.title = element_text(size = 14, face = 'bold'), legend.text = element_text(size = 12) ) #+ #transition_time(year)
knitr::include_graphics("images/1ra_animacion.gif")
:::caution
Además de la función transition_time()
, existen otras funciones transition_
que permiten realizar ditintos gráficos animados. Aquí podrás encontrar una lista de estas funciones y de otras funciones implementadas en el paquete gganimate
.
:::
shadow_*()
Con el conjunto de funciones shadow_*()
se pueden aplicar sombras que muestran los puntos de datos anteriores. Estas funciones son shadow_wake()
, shadow_trail()
y shadow_mark()
.
:::example
:::
gapminder %>% rename( 'Continente' = continent ) %>% mutate( Continente = recode( .x = Continente, 'Africa' = 'África', 'Americas' = 'América', 'Asia' = 'Asia', 'Europe' = 'Europa', 'Oceania' = 'Oceanía' ) ) %>% ggplot( data = ., aes(x = gdpPercap, y = lifeExp) ) + geom_point(aes(colour = Continente, fill = Continente, size = pop), alpha = 0.4) + scale_colour_manual(values = c('yellow', 'red', 'black', 'cyan', 'green')) + scale_fill_manual(values = c('yellow', 'red', 'black', 'cyan', 'green')) + scale_size_area(max_size = 14, guide = FALSE) + scale_x_log10() + labs(x = 'PIB per cápita', y = 'Expectativa de vida al nacer') + theme_bw() + theme( axis.text = element_text(size = 12, face = 'bold'), axis.title = element_text(size = 14, face = 'bold'), legend.position = c(0.8, 0.2), legend.title = element_text(size = 14, face = 'bold'), legend.text = element_text(size = 12) ) #+ #transition_time(year) + #shadow_trail()
knitr::include_graphics("images/2da_animacion.gif")
{frame_time}
El paquete gganimate
proporciona un etiquetado dinámico ({frame_time}
) que facilita comprender a que transición se refiere cada punto de dato presentado en la gráfica.
:::example
:::
gapminder %>% rename( 'Continente' = continent ) %>% mutate( Continente = recode( .x = Continente, 'Africa' = 'África', 'Americas' = 'América', 'Asia' = 'Asia', 'Europe' = 'Europa', 'Oceania' = 'Oceanía' ) ) %>% ggplot( data = ., aes(x = gdpPercap, y = lifeExp) ) + geom_point(aes(colour = Continente, fill = Continente, size = pop), alpha = 0.4) + scale_colour_manual(values = c('yellow', 'red', 'black', 'cyan', 'green')) + scale_fill_manual(values = c('yellow', 'red', 'black', 'cyan', 'green')) + scale_size_area(max_size = 14, guide = FALSE) + scale_x_log10() + labs(x = 'PIB per cápita', y = 'Expectativa de vida al nacer', title = 'Año: {frame_time}') + theme_bw() + theme( axis.text = element_text(size = 12, face = 'bold'), axis.title = element_text(size = 14, face = 'bold'), legend.position = c(0.8, 0.2), legend.title = element_text(size = 14, face = 'bold'), legend.text = element_text(size = 12), plot.title = element_text(size = 14, face = 'bold') ) #+ #transition_time(year) + #shadow_trail()
knitr::include_graphics("images/3ra_animacion.gif")
El paquete gganimate
permite también guardar los gráficos animados como un GIF.
:::example
:::
Graf_animado <- gapminder %>% rename( 'Continente' = continent ) %>% mutate( Continente = recode( .x = Continente, 'Africa' = 'África', 'Americas' = 'América', 'Asia' = 'Asia', 'Europe' = 'Europa', 'Oceania' = 'Oceanía' ) ) %>% ggplot( data = ., aes(x = gdpPercap, y = lifeExp) ) + geom_point(aes(colour = Continente, fill = Continente, size = pop), alpha = 0.4) + scale_colour_manual(values = c('yellow', 'red', 'black', 'cyan', 'green')) + scale_fill_manual(values = c('yellow', 'red', 'black', 'cyan', 'green')) + scale_size_area(max_size = 14, guide = FALSE) + scale_x_log10() + labs(x = 'PIB per cápita', y = 'Expectativa de vida al nacer', title = 'Año: {frame_time}') + theme_bw() + theme( axis.text = element_text(size = 12, face = 'bold'), axis.title = element_text(size = 14, face = 'bold'), legend.position = c(0.8, 0.2), legend.title = element_text(size = 14, face = 'bold'), legend.text = element_text(size = 12), plot.title = element_text(size = 14, face = 'bold') ) #+ #transition_time(year) + #shadow_trail() #animate(Graf_animado, fps = 10, width = 950, height = 650) #anim_save('Graf_animado.gif')
:::caution
Si deseas ver un resumen de las posibilidades que ofrece el paquete ggplot2
y profundizar más en su uso, puedes mirar su hoja de trucos aquí. De igual forma, si deseas conocer más sobre el paquete gganimate
puedes mirar su hoja de trucos aquí.
:::
La comunicación de resultados es un aspecto muy importante en cualquier campo de la ciencia y en el análisis estadístico no es la excepción. A lo largo de los años se usado el enfoque manual (cortar y pegar) para comunicar resultados en forma de informes, artículos científicos, presentaciones académicas, libros, sitios web, etc. Sin embargo este enfoque manual se a visto reemplazado por la automatización permitiendo emitir resultados reproducibles y dinámicos con mayor simplicidad. En R
y en muchos otros lenguajes de programación el formato de documento Markdown abrió las puertas a la interacción entre código de programación y texto plano, dando como resultado documentos que integran sus características y emiten distintos formatos de acuerdo a las necesidades del usuario. Markdown en conjunto con Pandoc permiten compilar formatos como PDF, HTML, Word, LaTex, PowerPoint, Beamer, etc. con una sintaxis bastante simple, dando como resultado documentos básicos susceptibles de personalización con algunos conocimientos de CSS y LaTex.
Aunque la sintaxis de Markdown es simple y admite muchos lenguajes incluyendo todos los inherentes a los formatos de salida, en R existe su propia versión en el paquete rmarkdown
creado a principios de 2014. Esta versión en conjunto con knitr
se han convertido en una de las herramientas más conocidas, divulgadas y avaluadas en toda la comunidad de programadores de R
, por lo que conocerla y manejarla resulta apropiado para los usuarios de R
.
Como primer paso para el uso de rmarkdown
es necesario instalarlo:
#install.packages("rmarkdown",dependencies = T)
:::caution
Si deseas ver un resumen de las posibilidades que ofrece el paquete rmarkdown
puedes mirar su hoja de trucos aquí.
:::
No obstante si cuenta con la IDE de Rstudio no es explícitamente necesario que lo instale ya que el lo hace por usted. Tenga en cuenta que rmarkdown
maneja la sintaxis de Markdown, para familiarizarse con ella puede tomar el Tutorial de Rmarkdown.
Como primer paso para aprender sobre las opciones de rmarkdown
debemos conocer las plantillas que posee abriendo Rstudio
y creando un nuevo Rmarkdown de la siguiente forma:
En la siguiente imagen se ilustra el panel de opciones de Rmarkdown mostrando los formatos de salida que podemos usar para generar documentos ya sean HTML,PDF, Word, presentaciones, Shiny y otras plantillas.
Generar cualquier tipo de documento, presentación, Shiny o plantilla es tan simple como seleccionar la opción que prefiera y dar click en OK. Después de pulsar OK se abrirá un archivo Rmd con instrucciones básicas correspondientes al formato seleccionado, para crear el archivo con el formato de interés debe buscar el icono de knit
o ejecutar el comando CTRL + SHIFT + K
.
knitr::include_graphics("images/rmarkdownflow.png")
Cada uno de los archivos posee la extensión .Rmd y cuenta por lo menos con tres tipos de contenidos:
---
```
El encabezado YAML se encuentra en la parte inicial del documento y denota características del archivo, como título, autor, fecha, descripción, opciones de tabla de contenido, condiciones estéticas, opciones de referencias, opciones de figuras, etc. Además este encabezado YAML establece las opciones de render para los distintos formatos, viéndose en su estado más básico para cada uno de ellos de la siguiente manera:
--- title: "Untitled" output: ****** ---
Donde ******
puede ser reemplazado por los distintos formatos que podemos usar. Los formatos por defecto que podemos renderizar son los siguientes:
html_document
pdf_document
word_document
ioslides_presentation
slidy_presentation
beamer_presentation
powerpoint_presentation
github_document
odt_document
md_document
latex_document
Para el formato html y para las presentaciones ioslides, podemos agregar un argumento extra que nos permita obtener un documento reactivo y dinámico como el curso que estás realizando en este momento. Veamos el YAML que corresponde a cada uno.
Es importante mencionar que cambiar el output
es el equivalente a seleccionar alguna de las opciones en el panel de Rmarkdown mostrado anteriormente en la animación. Estos dos últimos formatos interactivos los encuentras en la sección de shiny
en el mismo panel. Además, podemos renderizar otros formatos provenientes de paquetes, como los siguientes:
#install.packages("learnr") #install.packages("rmdshower") #install.packages("flexdashboard") #install.packages("rmdformats") #install.packages("xaringan") #install.packages("prettydoc") #install.packages("tufte") #install.packages("rticles") #install.packages("revealjs")
Dando para cada caso como resultado un YAML del tipo:
:::exercise
:::
Para este ejercicio debes volver a Rstudio y explorar cada uno de los formatos disponibles en el panel de Rmarkdown, abre y ejecuta con knitr
cada uno de ellos viendo sus diferencias y las salidas obtenidas. No dudes en pedir ayuda si tienes problemas en la ejecución o si tienes dudas con respecto a los diferentes formatos.
knitr::include_graphics("images/rmarkdown_wizards.png")
R en conjunto con diversos paquetes ofrece múltiples alternativas de render, sin embargo debemos mencionar que Markdown fue diseñado originalmente para la salida html, por lo que este formato posee características más ricas en comparación con los demás formatos de salida. Las siguientes subsecciones están orientadas al manejo de estas características para los distintos formatos. Tenga presente que este curso ofrece solo un preámbulo de las posibilidades que brinda Rmarkdown, si desea profundizar en cada uno de los formatos, consulte R Markdown: The Definitive Guide
Estas opciones aplican para los formatos html
y pdf
y denotan el funcionamiento de la tabla de contenido con el argumento toc: true
. Adicionalmente, para especificar la profundidad de los encabezados utilizamos el argumento toc_depth
. Veamos el YAML que corresponde para estos dos formatos:
Como se observa el máximo encabezado que se mostrará en la tabla de contenido para el formato html será de título de segundo nivel, mientras que para el pdf será de tercer nivel. Un opción adicional que se puede incluir en el formato html corresponde a la tabla de contenido flotante con el argumento toc_float:true
, el cual la mostrará permanentemente al lado izquierdo del contenido principal. Veamos el uso de este argumento en el YAML:
--- title: "Untitled" output: html_document: toc: true toc_depth: 2 toc_float: true ---
Al igual que con la tabla de contenido las opciones para figuras solo están disponibles para los formatos html
y pdf
, su configuración solo incluye tres aspectos: 1) alto; 2) ancho y 3) subtítulo, los argumentos en los dos formatos son exactamente los mismos dando como resultado YAML del tipo:
Si combinados las opciones de tabla de contenido con las opciones de figura el resultado de los YAML seria el siguiente:
Estas opciones son para los formatos pdf y html y determinan la estética de los marcos de datos en el documento de salida. Veamos la siguiente tabla que muestra las opciones disponibles.
+----------+----------------------------------------+----------------+-----------------+
| Opción | Descripción | pdf_document | html_document |
+==========+========================================+================+=================+
| default | Llama print.data.frame
| Disponible | Disponible |
+----------+----------------------------------------+----------------+-----------------+
| kable | Usa la función knitr::kable
| Disponible | Disponible |
+----------+----------------------------------------+----------------+-----------------+
| tibble | Usa la función tibble::print.tbl_df
| Disponible | Disponible |
+----------+----------------------------------------+----------------+-----------------+
| paged | Usa la función rmarkdown::paged_table
| No disponible | Disponible |
+----------+----------------------------------------+----------------+-----------------+
Supongamos que queremos usar la opción kable para un pdf y la opción paged para un html, sus YAML correspondientes haciendo uso de las opciones de tabla de contenido y de figuras tendrán el siguiente aspecto:
Las opciones de referencias bibliográficas son limitadas, sin embargo están disponibles para todos los formatos haciendo uso del lenguaje BibTeX. Lo primero es tener un archivo con la extensión correspondiente (.bib) que contenga las citaciones de interés, veamos un ejemplo del contenido de un archivo con esta extensión:
@Manual{rbase, title = {R: A Language and Environment for Statistical Computing}, author = {{R Core Team}}, organization = {R Foundation for Statistical Computing}, address = {Vienna, Austria}, year = {2020}, url = {https://www.R-project.org/}, } @Manual{curso_actual, title = {CursoR: Un Curso Amigable Para R}, author = {Duvan Nieves and Leonardo Lopez}, year = {2020}, note = {R package version 0.1.0}, url = {https://github.com/Duvancho321/Curso_R}, }
En nuestro YAML podemos agregar otras opciones como autores, fecha, subtítulos y otras que conciernen a formatos más específicos. Por el momento veamos las opciones nombradas anteriormente reuniendo todo lo que hemos aprendido del YAML.
Los trozos o fragmentos de código funcionan de forma similar a un Script y pueden producir salidas de texto, gráficos o tablas. La forma de insertarlos es usando el comando CTRL + ALT + I
o Cmd + Option + I
en macOS. Un fragmento de código R luce de la siguiente forma:
Fijémonos en los tres botones que se ven en la parte derecha del chunk, el primero nos da opciones de configuración que trataremos con detalle más adelante, el segundo ejecuta todos los trozos de código de arriba y el último ejecuta el fragmento actual.
sintaxis Markdown titulos negrilla citaciones codigo html ecuaciones importar imagenes hiperbinculos pie de pagina
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.