1 R vs Python

1.1 Enlaces

1.2 Integrated Development Environment (IDE)

options(warn = -1)
suppressWarnings(suppressPackageStartupMessages(library(kableExtra)))
# Crear los datos
categorias <- c("Gratuito", "Usuarios", "Enfoque", "Ajuste de Modelos", 
                "Visualización GrÔfica", "Interacción con la Nube", "Documentación")

R <- c("Gratuito", "AcadƩmicos", "Estadƭstico", "Sƭ", "Sƭ", "Sƭ", "Buena")
Python <- c("Gratuito", "Desarrolladores", "Multipropósito", "En desarrollo", 
            "En desarrollo", "SĆ­", "Menos completa")

# Crear el data frame combinando los datos
df_combined <- data.frame(CategorĆ­a = categorias, R = R, Python = Python)

# Crear la tabla utilizando kableExtra con mayor ancho
df_combined %>%
  kbl(col.names = c("CategorĆ­a", "R", "Python"), booktabs = TRUE, align = "l") %>%
  kable_styling(full_width = FALSE, position = "center", font_size = 24) %>%
  row_spec(0, bold = TRUE, background = "#D3D3D3") %>%
  kable_paper("striped", full_width = F) %>%
  column_spec(1, width = "8cm") %>%  # Ajustar ancho de la primera columna
  column_spec(2, width = "8cm") %>%  # Ajustar ancho de la segunda columna
  column_spec(3, width = "8cm")      # Ajustar ancho de la tercera columna
CategorĆ­a R Python
Gratuito Gratuito Gratuito
Usuarios AcadƩmicos Desarrolladores
Enfoque Estadístico Multipropósito
Ajuste de Modelos SĆ­ En desarrollo
Visualización GrÔfica Sí En desarrollo
Interacción con la Nube Sí Sí
Documentación Buena Menos completa

2 Instalación y Configuración de R-STUDIO

Para comenzar a usar R, el primer paso es instalarlo en tu computadora. R es compatible con casi todas las plataformas, incluyendo los sistemas operativos mƔs comunes. Windows, Mac OS X y Linux. Links de descarga para R y RStudio.

RStudio es un entorno de desarrollo integrado (IDE) disponible para R, el cual tiene un buen editor con resaltado de sintaxis, un visor de objetos de R y un gran número de características agradables que estÔn integradas.Ademas, esta dedicado a la computación estadística y grÔficos.

2.1 ā€œMundoā€ Tidyverse en R-Studio

El Tidyverse es una colección de paquetes del R que permiten preparar, procesar y graficar bases de datos. Se destacan los siguientes:

  • ggplot: permite crear visualizaciones elegantes de los datos de una manera relativamente sencilla.

  • stringr: permite manipular cadenas de caracteres con el fin de realizar sustituciones, detectar duplicados, analizar patrones, etc.

  • tidyr: tiene como objetivo obtener datos ordenados. Destacan funciones como gather para crear factores con base en nombres de columnas y separate para crear factores separando los caracteres de una columna.

  • readr: permite importar y exportar bases de datos en diferentes formatos y tiene implementada la función problems que detecta problemas en nuestras bases.

Para mÔs información visitar la pÔgina web:
https://www.tidyverse.org/packages/

2.2 Creación de reportes en R con R Markdown:

3 Conceptos bÔsicos de programación en R:

# Crear los datos
comandos <- c("x == y", "x != y", "x > y", "x < y", "x >= y", "x <= y", "&", "|", "!", "isTRUE(A)")
significado <- c("x es igual a y", "x no es igual a y", "x es mayor que y", "x es menor que y",
                 "x es mayor o igual que y", "x es menor o igual que y", "y", "o", "No", "EvalĆŗa si A es cierta")

# Crear el data frame
tabla <- data.frame(Comando = comandos, Significado = significado)

# Visualizar la tabla utilizando kableExtra con mayor ancho
tabla %>%
  kbl(col.names = c("Comando", "Significado"), booktabs = TRUE) %>%
  kable_styling(full_width = TRUE, position = "center", font_size = 24) %>%
  row_spec(0, bold = TRUE, background = "#D3D3D3") %>%
  kable_paper("striped", full_width = F) %>%
  column_spec(1, width = "6cm") %>%  # Ajustar ancho de la primera columna
  column_spec(2, width = "12cm")     # Ajustar ancho de la segunda columna
Comando Significado
x == y x es igual a y
x != y x no es igual a y
x > y x es mayor que y
x < y x es menor que y
x >= y x es mayor o igual que y
x <= y x es menor o igual que y
& y

3.1 Ejemplos de operadores de comparación en R:

# Asignación de valores
a <- 1
b <- 3

# Operaciones y comentarios explicativos

# Āæb es diferente de a?
b != a  # TRUE
## [1] TRUE
# Āæes a igual a b?
isTRUE(a == b)  # FALSE
## [1] FALSE
# Negar que a es menor que b
!(a < b)  # FALSE
## [1] FALSE
# Āæes a menor que b o b menor que a?
(a < b | b < a)  # TRUE
## [1] TRUE
# Āæes a menor o igual a b o b igual a a?
(a <= b & b == a)  # FALSE
## [1] FALSE

3.2 Creación de Objetos

R es un lenguaje orientado a objetos. Los objetos pueden ser usados para guardar valores y pueden madificarse mediante funciones como por ejemplo sumar dos objetos o calcular la media.

X <- 4
Y <- 2

3.2.1 R como calculadora

Puedes usar el programa R como una calculadora, basta con conocer cuÔles son los signos y comandos a utilizar para realizar las operaciones. Copia los comandos en tu script de R y ejecútalos para ver los resultados.

#suma
Z <- X +Y

Z
## [1] 6
#multiplicación
2*2
## [1] 4
#división
2/2
## [1] 1
#potencia
4^2
## [1] 16
#raĆ­z cuadrada
sqrt(16)
## [1] 4

3.3 Condicional if-else.

En R, la sintƔxis del condicional consiste en:

  • if (A): evalĆŗa si se cumple la condición A.
  • else if (B): si no se cumple la condición o condiciones anteriores, entonces evalĆŗe si se cumple la condición B.
  • else: si no se cumple ninguna de las condiciones anteriores entonces haga lo siguiente.

Ejemplo:

a<-9
 if (a<0){
 print("a es negativo")
 }else if (a>0){
 print("a es positivo")
 }else{
 print("a es igual a cero")
 }
## [1] "a es positivo"

3.4 Bucles for:

Usado para repetir un bloque específico de código, siguiendo una secuencia dada.

suma<-0
 for (i in 1:10){
 suma<-suma+i
 }
 suma
## [1] 55
lista<-c("a","b","c","d")
 for (j in lista){
 print(j)
 }
## [1] "a"
## [1] "b"
## [1] "c"
## [1] "d"
vector<-c(0.1, 0.3,-0.4, 1.5,-2.1)
 for (k in vector){
 print(abs(k))
 }
## [1] 0.1
## [1] 0.3
## [1] 0.4
## [1] 1.5
## [1] 2.1
x1<-cbind(1:3,4:6) # Matriz 1
 x2<-cbind(3:5,4:6) # Matriz 2
 x3<-cbind(5:7,9:11) # Matriz 3
 l1<-list(x1,x2,x3) # Lista de matrices
 for(i in l1){
 print(i)
 }
##      [,1] [,2]
## [1,]    1    4
## [2,]    2    5
## [3,]    3    6
##      [,1] [,2]
## [1,]    3    4
## [2,]    4    5
## [3,]    5    6
##      [,1] [,2]
## [1,]    5    9
## [2,]    6   10
## [3,]    7   11

3.5 Bucles for considerando un next:

# Imprimir solo los impares del 1 al 10
for (i in 1:10) {
  if (i %% 2 == 0) {
    next  # Saltar los nĆŗmeros pares
  }
  print(i)  # Imprimir los nĆŗmeros impares
}
## [1] 1
## [1] 3
## [1] 5
## [1] 7
## [1] 9

3.6 Bucles while (Condición):

Usado para repetir un bloque específico de código siempre que una condición dada sea verdadera.

a <- 1
while (a < 5) {
  a <- a + 1
  print(a)
}
## [1] 2
## [1] 3
## [1] 4
## [1] 5
a<-1
 b<-3
 while(a<15 &b>0.2){
 a<-a+1
 b<-round(b/2,2)
 print(c(a,b))
 }
## [1] 2.0 1.5
## [1] 3.00 0.75
## [1] 4.00 0.38
## [1] 5.00 0.19

3.7 Bucles repeat:

El bucle repeat funciona igual que el while, pero considera un bucle infinito que se ā€œrompeā€ con un break.

a <- 1
b <- 3
repeat {
  a <- a + 1
  b <- round(b / 2, 2)
  print(c(a, b))
  if (a > 15 | b < 0.2) {
    break
  }
}
## [1] 2.0 1.5
## [1] 3.00 0.75
## [1] 4.00 0.38
## [1] 5.00 0.19

3.8 Creación de funciones en R.

En una función tenemos tres tipos de elementos:

  1. Argumentos: tambiƩn conocidos como valores de entrada.

  2. Cuerpo: operaciones que han de realizarse. Se deben localizar entre corchetes.

  3. Resultado: también conocidos como valores de salida y que se ubican en la última expresión que se ejecuta.

Nota: Tratar al mÔximo de no usar nombres de posibles funciones que ya existen para no entrar en conflictos con los códigos en R.

# Sumar los nĆŗmeros impares hasta un m:
 sumar<-function(m){
 sum1<-0
 for (i in 1:m){
 if(!i%%2){
 next
 }else{
 sum1<-sum1+i
 }
 }
 return(sum1)
 }
sumar(5)
## [1] 9
sumar(7)
## [1] 16

Ejemplo de una función en R

 multiplos<-function(a,b,n){
 a1<-vector() #MĆŗltiplos de a menores o iguales a n
 b1<-vector() #MĆŗltiplos de b menores o iguales a n
 ca1<-1
 cb1<-1
 for (i in 1:n){
 if(!i%%a){
 a1[ca1]<-i
 ca1<-ca1+1
 }
 if(!i%%b){
 b1[cb1]<-i
 cb1<-cb1+1
 }else{
 next
 }
 }
 return(list(Multiplos_a=a1,Multiplos_b=b1))
 }
 multiplos(3,5,20) # a=3, b=5, n=20
## $Multiplos_a
## [1]  3  6  9 12 15 18
## 
## $Multiplos_b
## [1]  5 10 15 20
 multiplos(4,7,35) # a=4, b=7, n=35
## $Multiplos_a
## [1]  4  8 12 16 20 24 28 32
## 
## $Multiplos_b
## [1]  7 14 21 28 35

3.9 Guardando y cargando funciones de una forma prƔctica.

Una forma prƔctica para guardar y luego utilizar mis funciones de manera prƔctica consiste en:

  1. Considerar un Ćŗnico archivo .R que contenga todas las funciones que vamos creando.

  2. Utilizar la función source para leer el archivo con mis funciones y poder utilizarlas. Esta función tiene como argumento principal la url de mi computador donde he guardado mi archivo .R que contiene las funciones creadas.

Ejmplo:

# Ejemplo de una función en R
sumar <- function(a, b) {
  return(a + b)
}

restar <- function(a, b) {
  return(a - b)
}

Cargar las funciones desde el archivo

source("ruta/a/tu/archivo/mis_funciones.R")

# Ahora puedes utilizar las funciones cargadas
resultado_suma <- sumar(3, 5)
resultado_resta <- restar(10, 4)

print(resultado_suma)
## [1] 8
print(resultado_resta)
## [1] 6

4 Actividad #1: Creación y Aplicación de Funciones Propias en R

Como parte del desarrollo de habilidades fundamentales en programación y anÔlisis de datos, esta actividad tiene como objetivo que construyas tus propias funciones en R para resolver tareas comunes del anÔlisis de datos de manera automatizada, estructurada y reutilizable.

La base de datos que debes usar para esta actividad se encuentra disponible en el siguiente enlace:

base_ciencia_datos_estudiantes.csv

Puedes descargarla directamente desde el navegador o leerla directamente en R con la siguiente instrucción, usando el enlace RAW:

url <- "https://raw.githubusercontent.com/Kalbam/Datos/main/base_ciencia_datos_estudiantes.csv"
#datos <- read.csv(url)
  1. Construir una función llamada filtro en R que haga lo siguiente:

a.Tenga como entradas un ā€œdata frameā€, una variable y un valor particular de la variable.

  1. Identifique si la entrada correspondiente de la variable es un nombre o un nĆŗmero de columna.

  2. Arroje como resultado un nuevo ā€œdata frameā€ filtrado por el valor particular de la variable de interĆ©s.

  3. Generalizar esta función con varias variables y varias categorías a la base de datos estudiada.

  1. Diseña mínimo cinco (5) funciones propias en R que sean útiles para el anÔlisis de datos. Las funciones pueden estar enfocadas en:

    • Transformación o limpieza de variables.
    • Exploración estadĆ­stica.
    • Generación de visualizaciones.
    • Validación de datos o estructuras.
    • CĆ”lculo de indicadores o comparaciones.
  2. Aplica tus funciones a la base de datos proporcionada. Muestra ejemplos reales de uso y explica en qué situaciones se utilizaría cada función.

  3. Escribe y documenta tu código correctamente. Usa nombres claros para tus funciones, comenta su propósito y estructura adecuadamente tu script.

Debes entregar dos archivos separados:

  • mis_funciones.R o .Rmd: contiene Ćŗnicamente las cinco funciones creadas por ti.
  • uso_funciones.R o .Rmd: contiene ejemplos donde llamas y aplicas esas funciones sobre un conjunto de datos.

5 Visualización de Bases de datos en R.

5.1 Cargar paquetes

Lo primero que tenemos que hacer es cargar los paquetes que vamos a utilizar para el anƔlisis. En este caso vamos a usar:

library(tidyverse)# Incluye paquetes de importación, visualización entre otros
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## āœ” dplyr     1.1.4     āœ” readr     2.1.5
## āœ” forcats   1.0.0     āœ” stringr   1.5.1
## āœ” ggplot2   3.5.2     āœ” tibble    3.2.1
## āœ” lubridate 1.9.4     āœ” tidyr     1.3.1
## āœ” purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## āœ– dplyr::filter()     masks stats::filter()
## āœ– dplyr::group_rows() masks kableExtra::group_rows()
## āœ– dplyr::lag()        masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(dplyr)# Manipulación de Datos
library(ggplot2)# Visualización de datos 
library(readxl)# Importación de datos
require(tibble)# Tablas

Recordar que si no ha instalado estos paquetes debe correr primero el comando: install.packages("nombre del paquete")

5.2 Abrir una Base de R-STUDIO y Resumir**

5.2.1 Cargar base incorporada en R-Studio

R ya incorpora una serie de bases de datos que te pueden resultar de utilidad para empezar a explorar las posibilidades de anƔlisis estadƭstico que te ofrece este programa.

Como ejemplo vamos a explorara la base de datos llamada ā€œcarsā€.

# Cargar la base
data(cars)
# Visualizar los encabezados
head(cars)
# Resumir con algunas estadĆ­sticas las variables de la base 
summary(cars)
##      speed           dist       
##  Min.   : 4.0   Min.   :  2.00  
##  1st Qu.:12.0   1st Qu.: 26.00  
##  Median :15.0   Median : 36.00  
##  Mean   :15.4   Mean   : 42.98  
##  3rd Qu.:19.0   3rd Qu.: 56.00  
##  Max.   :25.0   Max.   :120.00

5.2.2 Incluir grƔficas

Puedes agregar fƔcilmente grƔficos a tu anƔlisis. Por ejemplo:

data(pressure)
head(pressure)
hist(pressure$temperature)

boxplot(pressure$temperature)

5.3 Extensiones.

Dentro de un amplio nĆŗmero de extensiones que se pueden encontrar en bases de datos se encuentran:

Algunos paquetes utilizados para importar y exportar bases de datos en R son:

Algunos paquetes utilizados para importar y exportar bases de datos en R y Python son:

  • utils: permite importar y exportar datos en varios formatos.
  • rgdal: permite importar y exportar datos geoespaciales.
  • readr: permite importar y exportar datos en varios formatos.
  • readxl: Importar datos en formato ā€œExcelā€.
  • haven: Importar y exportar datos en ā€œSPSSā€, ā€œStataā€ y ā€œSASā€.
  • httr: Herramientas para trabajar con URLs y archivos HTTP.
  • rvest: Ćŗtil para extraer información de las pĆ”ginas web.
  • xml2: Leer archivos HTML o XML.
  • csv: Importar y exportar datos .csv.
  • pandas: Importar y exportar datos .csv, Excel, SAS, SPSS, html, stata, sql, etc.

Ejemplo: Para los ejemplos de esta sección utilizaremos un dataset que contiene información sobre el Hurto de Motocicletas

https://github.com/Kalbam/Datos/raw/main/Delito_Hurto_Motocicletas.csv

Otra forma

datos<-read.csv("Delito_Hurto_Motocicletas.csv",
 sep=",",header=TRUE,
 fileEncoding = "UTF-8")

Nombres de variables

names(datos)# nombres de variables
##  [1] "FECHA"         "DEPARTAMENTO"  "MUNICIPIO"     "DIA"          
##  [5] "HORA"          "BARRIO"        "ZONA"          "CLASE.SITIO"  
##  [9] "EDAD"          "GENERO"        "ARMA.EMPLEADA" "MOVIL.AGRESOR"
## [13] "MOVIL.VICTIMA" "MARCA"         "MODELO"        "LINEA"        
## [17] "COLOR"         "ESTADO.CIVIL"  "PROFESIONES"   "ESCOLARIDAD"  
## [21] "CODIGO.DANE"   "X2015"

Dimensiones

dim(datos)
## [1] 27223    22

Categorias de Departamento

table(datos$DEPARTAMENTO)
## 
##                              AMAZONAS          ANTIOQUIA             ARAUCA 
##                 92                 25               6107                196 
##          ATLƁNTICO            BOLƍVAR             BOYACƁ             CALDAS 
##               1254                402                 63                111 
##            CAQUETƁ           CASANARE              CAUCA              CESAR 
##                348                449               1584               1010 
##              CHOCƓ            CƓRDOBA       CUNDINAMARCA            GUAINƍA 
##                318                521               3402                  3 
##            GUAJIRA           GUAVIARE              HUILA          MAGDALENA 
##                852                 27                841                375 
##               META             NARIƑO NORTE DE SANTANDER           PUTUMAYO 
##                854                721               1637                245 
##            QUINDƍO          RISARALDA         SAN ANDRƉS          SANTANDER 
##                155                295                 74                584 
##              SUCRE             TOLIMA              VALLE             VAUPƉS 
##                420                357               3882                  3 
##            VICHADA 
##                 16

Tipos de datos

str(datos)
## 'data.frame':    27223 obs. of  22 variables:
##  $ FECHA        : chr  "01/01/2015 12:00:00 AM" "01/01/2015 12:00:00 AM" "01/01/2015 12:00:00 AM" "01/01/2015 12:00:00 AM" ...
##  $ DEPARTAMENTO : chr  "ANTIOQUIA" "ANTIOQUIA" "ANTIOQUIA" "ANTIOQUIA" ...
##  $ MUNICIPIO    : chr  "CAREPA" "COPACABANA" "EL BAGRE" "MARINILLA" ...
##  $ DIA          : chr  "Jueves" "Jueves" "Jueves" "Jueves" ...
##  $ HORA         : chr  "7:30" "14:45" "4:00" "0:00" ...
##  $ BARRIO       : chr  "PUEBLO NUEVO" "VDA. CABUYAL" "BIJAO" "CENTRO" ...
##  $ ZONA         : chr  "URBANA" "URBANA" "URBANA" "URBANA" ...
##  $ CLASE.SITIO  : chr  "VIAS PUBLICAS" "VIAS PUBLICAS" "VIAS PUBLICAS" "VIAS PUBLICAS" ...
##  $ EDAD         : int  26 22 29 26 61 28 29 39 33 24 ...
##  $ GENERO       : chr  "MASCULINO" "MASCULINO" "FEMENINO" "FEMENINO" ...
##  $ ARMA.EMPLEADA: chr  "LLAVE MAESTRA" "ARMA DE FUEGO" "LLAVE MAESTRA" "LLAVE MAESTRA" ...
##  $ MOVIL.AGRESOR: chr  "A PIE" "PASAJERO MOTOCICLETA" "A PIE" "A PIE" ...
##  $ MOVIL.VICTIMA: chr  "A PIE" "CONDUCTOR MOTOCICLETA" "A PIE" "A PIE" ...
##  $ MARCA        : chr  "YAMAHA" "YAMAHA" "AUTECO" "YAMAHA" ...
##  $ MODELO       : num  2012 2000 2008 1997 2006 ...
##  $ LINEA        : chr  "FZ16" "RX 115" "PLATINO" "DT125" ...
##  $ COLOR        : chr  "NEGRO" "BLANCO" "NEGRO" "AZUL" ...
##  $ ESTADO.CIVIL : chr  "UNION LIBRE" "SOLTERO" "CASADO" "UNION LIBRE" ...
##  $ PROFESIONES  : chr  "POLICIA" "NO REPORTADO" "NO REPORTADO" "NO REPORTADO" ...
##  $ ESCOLARIDAD  : chr  "TECNICO" "SECUNDARIA" "PRIMARIA" "TECNICO" ...
##  $ CODIGO.DANE  : int  5147000 5212000 5250000 5440000 5607000 85010000 85001000 20250000 20001000 20001000 ...
##  $ X2015        : int  1 1 1 1 1 1 1 1 1 1 ...

5.4 Visualizar la base de datos

Se realiza un diagrama de barras para verificar cómo se encuentra la base de datos.

barplot(table(datos$DEPARTAMENTO),las=2)

Se organizan las barras de mayor a menor frecuencia.

barplot(sort(table(datos$DEPARTAMENTO),decreasing=TRUE),
 las=2)

Se realiza un resumen de la variable municipio y luego se visualizan los municipios con frecuencia mayor a 150.

resum_1<-table(datos$MUNICIPIO)
 barplot(sort(resum_1[resum_1>150],decreasing=TRUE),
 las=2)

Visualización de la variable Genero

 barplot(table(datos$GENERO))

resum_2<-table(datos$GENERO)
resum_2
## 
##                  FEMENINO    MASCULINO NO REPORTADO 
##           92         4710        22420            1
names(resum_2)
## [1] ""             "FEMENINO"     "MASCULINO"    "NO REPORTADO"

Se organizan los valores NA de la variable GENERO en a1

a1<-names(resum_2)[1]
a1
## [1] ""

Se guardan todos los valores similares de la base en a2

a2<-which(datos$GENERO==a1)
length(a2)
## [1] 92

Se visualizan los primeros 10 valores NA de la base de datos y se observa que en la fila 81,366,728… 1798 se encuentran NA de la variable GENERO

a2[1:10]
##  [1]   81  366  728  924 1331 1364 1490 1525 1785 1798
datos$FECHA[a2[1]]
## [1] "04/01/2015 12:00:00 AM,VALLE,CALI (CT),Domingo,19:40,OBRERO E9,URBANA,\"DROGUERIAS, FARMACIAS\",36,MASCULINO,LLAVE MAESTRA,A PIE,A PIE,SUZUKI,2009,viva 115,AZUL,UNION LIBRE,NO REPORTADO,SECUNDARIA,76001000,1"
datos$FECHA[a2[2]]
## [1] ",NORTE DE SANTANDER,PAMPLONA,Domingo,0:45,BARRIO EL CAMELLON,URBANA,\"BARES, CANTINAS Y SIMILARES\",27,MASCULINO,LLAVE MAESTRA,A PIE,A PIE,YAMAHA,2003,CRIPTON 110,AZUL,SOLTERO,NO REPORTADO,TECNICO,54518000,1"

5.5 Visualización de la base antes de ser procesada.

kable(datos[70:87,], caption= "Tabla 1: Base de datos, Delito de Hurto de Motocicleta") %>%
  kable_styling(full_width = F) %>%
  column_spec(2, width = "20em") %>%
  scroll_box(width = "900px", height = "450px")
Tabla 1: Base de datos, Delito de Hurto de Motocicleta
FECHA DEPARTAMENTO MUNICIPIO DIA HORA BARRIO ZONA CLASE.SITIO EDAD GENERO ARMA.EMPLEADA MOVIL.AGRESOR MOVIL.VICTIMA MARCA MODELO LINEA COLOR ESTADO.CIVIL PROFESIONES ESCOLARIDAD CODIGO.DANE X2015
70 04/01/2015 12:00:00 AM HUILA HOBO Domingo 12:30 CENTRO URBANA VIAS PUBLICAS 34 MASCULINO LLAVE MAESTRA A PIE A PIE HONDA 1990 LINEA STANDARD AZUL UNION LIBRE NO REPORTADO PRIMARIA 41349000 1
71 04/01/2015 12:00:00 AM MAGDALENA SANTA MARTA (CT) Domingo 13:30 URBANIZACION MARIA CECILIA URBANA VIAS PUBLICAS 23 MASCULINO ARMA DE FUEGO PASAJERO MOTOCICLETA A PIE YAMAHA 2013 FZ16 NEGRO GRAFITO SOLTERO NO REPORTADO SECUNDARIA 47001000 1
72 04/01/2015 12:00:00 AM META FUENTE DE ORO Domingo 8:00 VEREDA PUERTO POVEDA RURAL PLAYA 23 MASCULINO LLAVE MAESTRA A PIE CONDUCTOR MOTOCICLETA BAJAJ 2014 DISCOVER ROJO UNION LIBRE NO REPORTADO PRIMARIA 50287000 1
73 04/01/2015 12:00:00 AM META VILLAVICENCIO (CT) Domingo 1:00 VALLES DE ARAGON URBANA VIAS PUBLICAS 25 MASCULINO LLAVE MAESTRA A PIE A PIE YAMAHA 2015 FZ16 NEGRO SOLTERO NO REPORTADO SECUNDARIA 50001000 1
74 04/01/2015 12:00:00 AM PUTUMAYO MOCOA (CT) Domingo 16:00 OBRERO I URBANA VIAS PUBLICAS 64 MASCULINO CONTUNDENTES A PIE A PIE BAJAJ 2010 DISCOVER AZUL CASADO NO REPORTADO SECUNDARIA 86001000 1
75 04/01/2015 12:00:00 AM RISARALDA PEREIRA (CT) Domingo 15:00 VDA. SAN JOSE RURAL PARQUEADERO 24 MASCULINO LLAVE MAESTRA A PIE A PIE YAMAHA 1997 RX 100 MARRON SOLTERO NO REPORTADO SECUNDARIA 66001000 1
76 04/01/2015 12:00:00 AM SUCRE SINCELEJO (CT) Domingo 11:30 CENTRO URBANA VIAS PUBLICAS 47 MASCULINO LLAVE MAESTRA A PIE CONDUCTOR MOTOCICLETA AUTECO 2014 BOXER NEGRO CASADO NO REPORTADO SECUNDARIA 70001000 1
77 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 7:00 UNION DE VIVIENDA POPULAR E16 URBANA FRENTE A RESIDENCIAS - VIA PUBLICA 24 MASCULINO LLAVE MAESTRA A PIE A PIE AKT 2008 AK 100 NEGRO UNION LIBRE NO REPORTADO SECUNDARIA 76001000 1
78 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 9:00 SANTA ELENA E10 URBANA VIAS PUBLICAS 33 MASCULINO LLAVE MAESTRA A PIE A PIE TVS 2007 SPORT NEGRO UNION LIBRE NO REPORTADO SECUNDARIA 76001000 1
79 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 16:00 VISTAHERMOSA E1 URBANA VIAS PUBLICAS 28 MASCULINO LLAVE MAESTRA A PIE A PIE AUTECO 2011 BOXER BLANCO SOLTERO NO REPORTADO SECUNDARIA 76001000 1
80 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 18:00 LA LIBERTAD E10 URBANA VIAS PUBLICAS 31 FEMENINO ARMA DE FUEGO A PIE A PIE YAMAHA 2015 BWS NEGRO UNION LIBRE NO REPORTADO SECUNDARIA 76001000 1
81 04/01/2015 12:00:00 AM,VALLE,CALI (CT),Domingo,19:40,OBRERO E9,URBANA,ā€œDROGUERIAS, FARMACIASā€,36,MASCULINO,LLAVE MAESTRA,A PIE,A PIE,SUZUKI,2009,viva 115,AZUL,UNION LIBRE,NO REPORTADO,SECUNDARIA,76001000,1 NA NA NA NA
82 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 20:30 ATANASIO GIRARDOT E8 URBANA VIAS PUBLICAS 34 MASCULINO LLAVE MAESTRA A PIE A PIE HONDA 2013 ECO NEGRO SOLTERO NO REPORTADO SECUNDARIA 76001000 1
83 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 22:40 LOS GUAYACANES E5 URBANA VIAS PUBLICAS 21 FEMENINO ARMA DE FUEGO CONDUCTOR MOTOCICLETA CONDUCTOR MOTOCICLETA YAMAHA 2014 CRYPTON 115 AZUL SOLTERO NO REPORTADO SECUNDARIA 76001000 1
84 04/01/2015 12:00:00 AM VALLE TRUJILLO Domingo 0:40 CENTRO URBANA VIAS PUBLICAS 23 MASCULINO LLAVE MAESTRA A PIE A PIE YAMAHA 1996 DT125 BLANCO SOLTERO NO REPORTADO SECUNDARIA 76828000 1
85 05/01/2015 12:00:00 AM ANTIOQUIA ENVIGADO Lunes 22:00 EL DORADO URBANA VIAS PUBLICAS 28 MASCULINO ARMA DE FUEGO PASAJERO MOTOCICLETA CONDUCTOR MOTOCICLETA BMW 2015 RNO REPORTADO1200C ROJO SOLTERO NO REPORTADO SUPERIOR 5266000 1
86 05/01/2015 12:00:00 AM ANTIOQUIA GUARNE Lunes 0:00 SAN FRANCISCO URBANA VIAS PUBLICAS 26 MASCULINO LLAVE MAESTRA A PIE A PIE AUTECO 2014 PULSAR AMARILLO SOLTERO NO REPORTADO SECUNDARIA 5318000 1
87 05/01/2015 12:00:00 AM ANTIOQUIA TURBO Lunes 9:00 CENTRO URBANA VIAS PUBLICAS 33 FEMENINO NO REPORTADO A PIE A PIE BAJAJ 2011 DISCOVER NEGRO CASADO NO REPORTADO SECUNDARIA 5837000 1

5.6 Paquete stringr

El paquete stringr es parte del ecosistema de R y forma parte de la familia de paquetes tidyverse, desarrollado por Hadley Wickham y otros colaboradores. EstƔ diseƱado especƭficamente para facilitar el trabajo con cadenas de texto (strings) en R.

5.7 Principales funcionalidades del paquete stringr

  1. Manipulación de cadenas:
    • Concatenación: Puedes unir mĆŗltiples cadenas en una sola.
    • Extracción de subcadenas: Permite extraer partes especĆ­ficas de una cadena, como un substring.
    • Cambio de mayĆŗsculas y minĆŗsculas: Convertir cadenas a mayĆŗsculas (str_to_upper()) o minĆŗsculas (str_to_lower()).
    • Reemplazo de patrones: Reemplazar partes de una cadena que coinciden con un patrón especĆ­fico.
  2. BĆŗsqueda y coincidencia de patrones:
    • Patrones regulares (regex): stringr facilita la bĆŗsqueda de patrones dentro de cadenas utilizando expresiones regulares, que son una herramienta poderosa para encontrar y manipular texto basado en patrones especĆ­ficos.
    • Detección de patrones: Funciones como str_detect() permiten identificar si un patrón especĆ­fico existe dentro de una cadena.
  3. Modificación y limpieza de cadenas:
    • Eliminación de espacios: str_trim() se usa para eliminar espacios en blanco al principio y al final de una cadena.
    • Separación y unión de cadenas: str_split() divide una cadena en partes basadas en un delimitador, y str_c() concatena mĆŗltiples cadenas en una sola.
  4. Contar y medir cadenas:
    • Conteo de caracteres: str_length() devuelve el nĆŗmero de caracteres en una cadena.
    • Conteo de ocurrencias: str_count() cuenta cuĆ”ntas veces un patrón especĆ­fico aparece en una cadena.
datos$FECHA[a2[1]] 
## [1] "04/01/2015 12:00:00 AM,VALLE,CALI (CT),Domingo,19:40,OBRERO E9,URBANA,\"DROGUERIAS, FARMACIAS\",36,MASCULINO,LLAVE MAESTRA,A PIE,A PIE,SUZUKI,2009,viva 115,AZUL,UNION LIBRE,NO REPORTADO,SECUNDARIA,76001000,1"
require(stringr)
 str_match(datos$FECHA[a2[1]], '"(.*?)"')[,2]
## [1] "DROGUERIAS, FARMACIAS"
a3<-vector()
 for(i in 1:length(a2)){
 a3[i]<-str_match(datos$FECHA[a2[i]],'"(.*?)"')[,2]
 }
resum3<-summary(as.factor(a3))
 resum3
##        BARES, CANTINAS Y SIMILARES                   CHICO I, II, III 
##                                 37                                  1 
##                 COLEGIOS, ESCUELAS                   COR, LOS ANGELES 
##                                 33                                  1 
##              DROGUERIAS, FARMACIAS HOTELES, RESIDENCIAS, Y SIMILARES. 
##                                  1                                 14 
##              LA ADIELA I,II,III,IV    VDA. QUILCACE, ANTES DEL PUENTE 
##                                  2                                  2 
##                   VIA, PUERTO ASIS 
##                                  1
a31<-str_replace_all(a3,",","-")
 resum4<-summary(as.factor(a31))
 resum4
##        BARES- CANTINAS Y SIMILARES                   CHICO I- II- III 
##                                 37                                  1 
##                 COLEGIOS- ESCUELAS                   COR- LOS ANGELES 
##                                 33                                  1 
##              DROGUERIAS- FARMACIAS HOTELES- RESIDENCIAS- Y SIMILARES. 
##                                  1                                 14 
##              LA ADIELA I-II-III-IV    VDA. QUILCACE- ANTES DEL PUENTE 
##                                  2                                  2 
##                   VIA- PUERTO ASIS 
##                                  1
b1<-str_replace(datos$FECHA[a2[1]],a3[1],a31[1])
 b1
## [1] "04/01/2015 12:00:00 AM,VALLE,CALI (CT),Domingo,19:40,OBRERO E9,URBANA,\"DROGUERIAS- FARMACIAS\",36,MASCULINO,LLAVE MAESTRA,A PIE,A PIE,SUZUKI,2009,viva 115,AZUL,UNION LIBRE,NO REPORTADO,SECUNDARIA,76001000,1"
 b2<-str_replace(b1,'\"','' )
 b2<-str_replace(b2,'\",',',' )
 b2
## [1] "04/01/2015 12:00:00 AM,VALLE,CALI (CT),Domingo,19:40,OBRERO E9,URBANA,DROGUERIAS- FARMACIAS,36,MASCULINO,LLAVE MAESTRA,A PIE,A PIE,SUZUKI,2009,viva 115,AZUL,UNION LIBRE,NO REPORTADO,SECUNDARIA,76001000,1"
 a32<-vector()
 for(i in 1:length(a2)){
 b1<-str_replace(datos$FECHA[a2[i]],a3[i],a31[i])
 b2<-str_replace(b1,'\"','')
 b2<-str_replace(b2,'\",',',' )
 a32[i]<-b2
 }
require(tidyr)
 #datos21<-separate(data.frame(a32),a32, sep=",",into=colnames(datos))
#datos21 <- separate(data.frame(a32 = a32), col = "a32", sep = ",", into = colnames(datos))
datos21 <- tidyr::separate(data.frame(a32 = a32), col = "a32", sep = ",", into = colnames(datos))
datos[a2,]<-datos21
datos<-droplevels(datos)
table(datos$GENERO)# DESPUƉS DE PROCESAR LOS DATOS
## 
##     FEMENINO    MASCULINO NO REPORTADO 
##         4728        22494            1

5.8 Visualización de la base despues de ser procesada.

kable(datos[70:87,], caption= "Tabla 1: Base de datos, Delito de Hurto de Motocicleta") %>%
  kable_styling(full_width = F) %>%
  column_spec(2, width = "20em") %>%
  scroll_box(width = "900px", height = "450px")
Tabla 1: Base de datos, Delito de Hurto de Motocicleta
FECHA DEPARTAMENTO MUNICIPIO DIA HORA BARRIO ZONA CLASE.SITIO EDAD GENERO ARMA.EMPLEADA MOVIL.AGRESOR MOVIL.VICTIMA MARCA MODELO LINEA COLOR ESTADO.CIVIL PROFESIONES ESCOLARIDAD CODIGO.DANE X2015
70 04/01/2015 12:00:00 AM HUILA HOBO Domingo 12:30 CENTRO URBANA VIAS PUBLICAS 34 MASCULINO LLAVE MAESTRA A PIE A PIE HONDA 1990 LINEA STANDARD AZUL UNION LIBRE NO REPORTADO PRIMARIA 41349000 1
71 04/01/2015 12:00:00 AM MAGDALENA SANTA MARTA (CT) Domingo 13:30 URBANIZACION MARIA CECILIA URBANA VIAS PUBLICAS 23 MASCULINO ARMA DE FUEGO PASAJERO MOTOCICLETA A PIE YAMAHA 2013 FZ16 NEGRO GRAFITO SOLTERO NO REPORTADO SECUNDARIA 47001000 1
72 04/01/2015 12:00:00 AM META FUENTE DE ORO Domingo 8:00 VEREDA PUERTO POVEDA RURAL PLAYA 23 MASCULINO LLAVE MAESTRA A PIE CONDUCTOR MOTOCICLETA BAJAJ 2014 DISCOVER ROJO UNION LIBRE NO REPORTADO PRIMARIA 50287000 1
73 04/01/2015 12:00:00 AM META VILLAVICENCIO (CT) Domingo 1:00 VALLES DE ARAGON URBANA VIAS PUBLICAS 25 MASCULINO LLAVE MAESTRA A PIE A PIE YAMAHA 2015 FZ16 NEGRO SOLTERO NO REPORTADO SECUNDARIA 50001000 1
74 04/01/2015 12:00:00 AM PUTUMAYO MOCOA (CT) Domingo 16:00 OBRERO I URBANA VIAS PUBLICAS 64 MASCULINO CONTUNDENTES A PIE A PIE BAJAJ 2010 DISCOVER AZUL CASADO NO REPORTADO SECUNDARIA 86001000 1
75 04/01/2015 12:00:00 AM RISARALDA PEREIRA (CT) Domingo 15:00 VDA. SAN JOSE RURAL PARQUEADERO 24 MASCULINO LLAVE MAESTRA A PIE A PIE YAMAHA 1997 RX 100 MARRON SOLTERO NO REPORTADO SECUNDARIA 66001000 1
76 04/01/2015 12:00:00 AM SUCRE SINCELEJO (CT) Domingo 11:30 CENTRO URBANA VIAS PUBLICAS 47 MASCULINO LLAVE MAESTRA A PIE CONDUCTOR MOTOCICLETA AUTECO 2014 BOXER NEGRO CASADO NO REPORTADO SECUNDARIA 70001000 1
77 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 7:00 UNION DE VIVIENDA POPULAR E16 URBANA FRENTE A RESIDENCIAS - VIA PUBLICA 24 MASCULINO LLAVE MAESTRA A PIE A PIE AKT 2008 AK 100 NEGRO UNION LIBRE NO REPORTADO SECUNDARIA 76001000 1
78 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 9:00 SANTA ELENA E10 URBANA VIAS PUBLICAS 33 MASCULINO LLAVE MAESTRA A PIE A PIE TVS 2007 SPORT NEGRO UNION LIBRE NO REPORTADO SECUNDARIA 76001000 1
79 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 16:00 VISTAHERMOSA E1 URBANA VIAS PUBLICAS 28 MASCULINO LLAVE MAESTRA A PIE A PIE AUTECO 2011 BOXER BLANCO SOLTERO NO REPORTADO SECUNDARIA 76001000 1
80 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 18:00 LA LIBERTAD E10 URBANA VIAS PUBLICAS 31 FEMENINO ARMA DE FUEGO A PIE A PIE YAMAHA 2015 BWS NEGRO UNION LIBRE NO REPORTADO SECUNDARIA 76001000 1
81 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 19:40 OBRERO E9 URBANA DROGUERIAS- FARMACIAS 36 MASCULINO LLAVE MAESTRA A PIE A PIE SUZUKI 2009 viva 115 AZUL UNION LIBRE NO REPORTADO SECUNDARIA 76001000 1
82 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 20:30 ATANASIO GIRARDOT E8 URBANA VIAS PUBLICAS 34 MASCULINO LLAVE MAESTRA A PIE A PIE HONDA 2013 ECO NEGRO SOLTERO NO REPORTADO SECUNDARIA 76001000 1
83 04/01/2015 12:00:00 AM VALLE CALI (CT) Domingo 22:40 LOS GUAYACANES E5 URBANA VIAS PUBLICAS 21 FEMENINO ARMA DE FUEGO CONDUCTOR MOTOCICLETA CONDUCTOR MOTOCICLETA YAMAHA 2014 CRYPTON 115 AZUL SOLTERO NO REPORTADO SECUNDARIA 76001000 1
84 04/01/2015 12:00:00 AM VALLE TRUJILLO Domingo 0:40 CENTRO URBANA VIAS PUBLICAS 23 MASCULINO LLAVE MAESTRA A PIE A PIE YAMAHA 1996 DT125 BLANCO SOLTERO NO REPORTADO SECUNDARIA 76828000 1
85 05/01/2015 12:00:00 AM ANTIOQUIA ENVIGADO Lunes 22:00 EL DORADO URBANA VIAS PUBLICAS 28 MASCULINO ARMA DE FUEGO PASAJERO MOTOCICLETA CONDUCTOR MOTOCICLETA BMW 2015 RNO REPORTADO1200C ROJO SOLTERO NO REPORTADO SUPERIOR 5266000 1
86 05/01/2015 12:00:00 AM ANTIOQUIA GUARNE Lunes 0:00 SAN FRANCISCO URBANA VIAS PUBLICAS 26 MASCULINO LLAVE MAESTRA A PIE A PIE AUTECO 2014 PULSAR AMARILLO SOLTERO NO REPORTADO SECUNDARIA 5318000 1
87 05/01/2015 12:00:00 AM ANTIOQUIA TURBO Lunes 9:00 CENTRO URBANA VIAS PUBLICAS 33 FEMENINO NO REPORTADO A PIE A PIE BAJAJ 2011 DISCOVER NEGRO CASADO NO REPORTADO SECUNDARIA 5837000 1

5.9 Paquete readr

El paquete readr es una herramienta esencial en R para la lectura de archivos de datos de manera rÔpida y eficiente. Forma parte del tidyverse, una colección de paquetes que comparten una filosofía de diseño común y son desarrollados para trabajar juntos de manera armoniosa.

5.9.1 Principales funcionalidades de readr

  1. Lectura de archivos de texto:
    • readr permite la importación de archivos de texto delimitados como CSV (read_csv()), TSV (read_tsv()), y otros formatos similares de manera eficiente y rĆ”pida. Utiliza una implementación optimizada en C++ que permite un desempeƱo superior en comparación con funciones base de R como read.csv().
  2. Conversión automÔtica de tipos:
    • Una de las caracterĆ­sticas mĆ”s Ćŗtiles de readr es su capacidad para inferir automĆ”ticamente los tipos de datos de cada columna. Esto significa que, al leer un archivo, el paquete detecta si una columna contiene nĆŗmeros, fechas, texto, etc., y realiza la conversión adecuada automĆ”ticamente.
  3. Lectura perezosa (lazy reading):
    • En lugar de cargar todos los datos en la memoria de una sola vez, readr permite leer y procesar los datos de manera perezosa, lo que es Ćŗtil para manejar archivos de gran tamaƱo sin sobrecargar la memoria.
  4. Funciones para otros formatos de archivos:
    • AdemĆ”s de CSV y TSV, readr incluye funciones para leer otros formatos como archivos delimitados por espacios (read_table()), archivos de ancho fijo (read_fwf()), y archivos de datos R (read_rds()).
  5. Facilidad de manejo de errores:
    • El paquete proporciona herramientas para detectar y manejar problemas durante la importación de datos, como valores faltantes o errores de tipo. Esto facilita la limpieza y preparación de datos para su anĆ”lisis posterior.

5.10 Ejemplos de uso

  1. Lectura de un archivo CSV:
suppressWarnings({
  suppressPackageStartupMessages(library(readr))
  suppressPackageStartupMessages(library(dplyr))
  suppressPackageStartupMessages(library(magrittr))
  
  datos2A <- read_csv("Delito_Hurto_Motocicletas.csv",
                      locale = locale(encoding = "UTF-8"),
                      skip_empty_rows = TRUE)
})
## Rows: 27223 Columns: 22
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (17): FECHA, DEPARTAMENTO, MUNICIPIO, DIA, BARRIO, ZONA, CLASE SITIO, G...
## dbl   (4): EDAD, MODELO, CODIGO DANE, 2015
## time  (1): HORA
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Al visualizar la base de datos cargada View(datos2A) se observa que la fila 81 presenta problemas.

Con la ayuda de este paquete y su respectiva función problems se identifica el problema de la base de datos, en un solo paso.

probs1 <- problems(datos2A)
probs1
probs1 <- problems(datos2A)

# Ajustar las filas reportadas restando 1, dado que me presento ese problema.
probs1_adjusted <- probs1
probs1_adjusted$row <- probs1$row - 1

El comando names(probs1) se utiliza para mostrar los nombres de las columnas o las variables en el objeto probs1. En este caso, el resultado que se muestra en la salida indica que probs1 es un objeto (probablemente un data frame o lista) que contiene las siguientes variables o columnas:

names(probs1_adjusted)
## [1] "row"      "col"      "expected" "actual"   "file"

Se revelan las salidas con problemas y se verifican que el problema se presenta en las misma filas identificadas manualmente en los pasos anteriores.

probs1_adjusted$row[1:10]# Mostrar las primeras 10 filas con problemas identificadas por probs1
##  [1]   81  366  728  924 1331 1364 1490 1525 1785 1798
a2[1:10] # Obtenido "manualmente"
##  [1]   81  366  728  924 1331 1364 1490 1525 1785 1798

5.11 Se visualizan las distintas variables para observar cómo se encuentran los datos.

Variable Estado Civil

barplot(sort(table(datos$ESTADO.CIVIL),
 decreasing=TRUE),las=2)

Variable Color

barplot(sort(table(datos$COLOR),
 decreasing=TRUE),las=2)

Se muestran solo los primeros 20 colores.

barplot(sort(table(datos$COLOR),
 decreasing = TRUE)[1:20],las=2)

Variable MOVIL.AGRESOR

barplot(sort(table(datos$MOVIL.AGRESOR),
 decreasing = TRUE),las=2)

Variable HORA

barplot(sort(table(datos$HORA),
 decreasing=TRUE)[1:20],las=2)

Se carga el paquete tidyr para manipular un conjunto de datos creado y llamado datos22. Primero, se divide la columna HORA en dos nuevas columnas, HORA1 y MINS1, separando los valores en base al sĆ­mbolo ā€œ:ā€. Luego, las nuevas columnas se convierten a formato numĆ©rico para permitir operaciones matemĆ”ticas. Finalmente, se crea una nueva columna HORAS, que representa la hora en formato decimal, sumando la parte de las horas con los minutos divididos entre 60. Este proceso facilita el anĆ”lisis de las horas al convertirlas en un formato numĆ©rico continuo.

 suppressPackageStartupMessages(library(tidyr))
                                
 #datos22<-separate(datos,HORA, sep=":",into=c("HORA1","MINS1" ))
 datos22 <- tidyr::separate(datos, col = HORA, sep = ":", into = c("HORA1", "MINS1"))

                  
 datos22$HORA1<-as.numeric(datos22$HORA1)
 datos22$MINS1<-as.numeric(datos22$MINS1)
 datos22$HORAS<-datos22$HORA1+(datos22$MINS1/60)

Variable HORA, segĆŗn nĆŗmero de robos.

 require(ggplot2)
 ggplot(datos22,aes(x=HORAS))+
 geom_density(alpha=0.4,fill="blue")

Variable HORA, segĆŗn nĆŗmero MOVIL AGRESOR, APIE,PASAJERO MOTOCICLETA,CONDUCTORMOTOCICLETA.

datos23<-subset(datos22,subset=(MOVIL.AGRESOR==c("APIE","PASAJERO MOTOCICLETA","CONDUCTOR MOTOCICLETA")))
ggplot(datos23,aes(x=HORAS,fill=MOVIL.AGRESOR))+geom_density(alpha=0.4)

Variable HORA, segĆŗn nĆŗmero ESTADO CIVIL.

datos24<-subset(datos22,subset=(ESTADO.CIVIL==c("SOLTERO","UNIONLIBRE","CASADO")))
 ggplot(datos24,aes(x=HORAS,fill=ESTADO.CIVIL))+geom_density(alpha=0.4)

Variable HORA, segĆŗn nĆŗmero GENERO.

datos25<-subset(datos22,subset=(GENERO!=c("NO REPORTADO")))
ggplot(datos25,aes(x=HORAS,fill=GENERO))+geom_density(alpha=0.4)

5.12 PrƔctica #1:

Seleccione alguna de las funciones en R para leer las bases de datos que se encuentran en la cuenta de GitHub. Los archivos de datos que necesita son:

url_DHM <- "https://raw.githubusercontent.com/Kalbam/Datos/main/Delito_Hurto_Automotores.csv"
url_master <- "https://raw.githubusercontent.com/Kalbam/Datos/main/master.csv"

Utilice las funciones de R para explorar estos archivos y analizar su contenido.

  1. Explore posibles problemas leyendo las bases de datos y regĆ­strelos brevemente. Use las funciones head, tail, summary, barplot, etc.

  2. ¿Qué solución propone para resolver dichos problemas y apliquelas?

6 Actividad #2

En la vida real, los archivos de datos suelen contener problemas como comas internas en los campos, comillas innecesarias, valores faltantes, errores de codificación y nombres de columnas poco claros. Aprender a identificar y resolver estos problemas es fundamental para un anÔlisis de datos confiable.

He subido varios archivos CSV problemƔticos al repositorio del curso, que puedes encontrar aquƭ:
https://github.com/Kalbam/Data_cleaning_practice

  1. Selecciona uno de los archivos del repositorio y cƔrgalo en R utilizando al menos dos funciones diferentes (read.csv(), readr::read_csv(), data.table::fread(), etc.).
  2. Describe los principales problemas de formato que encuentres: comas internas, comillas, valores faltantes, codificación, etc.
  3. Limpia el dataset:
    • Elimina o corrige comillas innecesarias y comas internas.
    • Ajusta los tipos de datos (nĆŗmeros, fechas, factores).
    • Renombra las columnas para que sean claras y consistentes.
  4. Realiza un anƔlisis exploratorio simple:
    • Calcula frecuencias o resĆŗmenes bĆ”sicos de las variables principales.
    • Detecta valores atĆ­picos o inconsistencias.
    • Presenta al menos dos grĆ”ficos usando ggplot2 o funciones base de R.

6.1 Ejemplo PrƔctico #1.

Consideremos nuevamente la base de datos que contiene información acerca de incautaciones de bebidas alcohólicas fraudulentas y de contrabando en cierta ciudad. Puede acceder a la base de datos en el siguiente enlace: BASE_DATOS.xlsx.

url_base_Datos <-"https://raw.github.com/Kalbam/Datos/blob/main/BASE_DATOS.xlsx"

Las variables se describen como:

  • TL: Tipo de licor
  • PI: Precio de incautación. Se refiere al precio de venta en el establecimiento por unidad.
  • GAE: Grados de alcohol en etiqueta.
  • GAQ: Grados de alcohol en prueba quĆ­mica.
  • CE: Cantidad estandarizada. NĆŗmero de unidades estandarizadas a 750 ml.
require(readxl) 
direccion="BASE_DATOS.xlsx"
DATOS<-read_excel(direccion,sheet="datos")

NOTA: La función read_excel importa un objeto tibble, que es en esencia un data.frame, pero que cuenta con algunas ventajas estéticas en las presentaciones e informes.

6.1.1 Caracteristica generales de los datos

Verifiquemos que leĆ­mos bien los datos viendo el encabezado y la cola de los datos:

head(DATOS)
tail(DATOS)

Las dimensiones, los nombres de las columnas y la estructura de la base de datos se obtienen con los códigos:

dim(DATOS) # dimensiones de los datos
## [1] 300   5
colnames(DATOS) # Nombres de las columnas o variables
## [1] "TL"  "PI"  "GAE" "GAQ" "CE"
str(DATOS)
## tibble [300 Ɨ 5] (S3: tbl_df/tbl/data.frame)
##  $ TL : chr [1:300] "Aguardiente" "Aguardiente" "Aguardiente" "Aguardiente" ...
##  $ PI : num [1:300] 21470 26422 19737 30240 28374 ...
##  $ GAE: num [1:300] 29 29 29 29 38 38 29 38 38 38 ...
##  $ GAQ: num [1:300] 25.1 29 25.3 29 33.5 ...
##  $ CE : num [1:300] 251 262 289 266 232 ...

Otra función que funciona igual a str es glimpse, que hace parte del paquete tibble:

require(tibble)
glimpse(DATOS)
## Rows: 300
## Columns: 5
## $ TL  <chr> "Aguardiente", "Aguardiente", "Aguardiente", "Aguardiente", "Tequi…
## $ PI  <dbl> 21470, 26422, 19737, 30240, 28374, 33601, 33207, 33029, 30964, 309…
## $ GAE <dbl> 29, 29, 29, 29, 38, 38, 29, 38, 38, 38, 29, 29, 29, 35, 29, 38, 29…
## $ GAQ <dbl> 25.143, 29.000, 25.288, 29.000, 33.516, 31.198, 29.000, 33.060, NA…
## $ CE  <dbl> 250.6536, 261.7272, 289.1134, 266.3064, 231.7978, 248.8542, 252.57…
 summary(DATOS)
##       TL                  PI              GAE             GAQ       
##  Length:300         Min.   : 16308   Min.   :29.00   Min.   :24.27  
##  Class :character   1st Qu.: 26682   1st Qu.:29.00   1st Qu.:26.04  
##  Mode  :character   Median : 29390   Median :35.00   Median :30.07  
##                     Mean   : 31846   Mean   :33.54   Mean   :30.74  
##                     3rd Qu.: 32751   3rd Qu.:38.00   3rd Qu.:34.10  
##                     Max.   :430091   Max.   :40.00   Max.   :40.00  
##                     NA's   :5                        NA's   :2      
##        CE       
##  Min.   :  0.0  
##  1st Qu.:239.4  
##  Median :250.3  
##  Mean   :249.6  
##  3rd Qu.:261.7  
##  Max.   :296.5  
##  NA's   :3

6.1.2 Considere el resumen individual de las variables numƩricas:

summary(DATOS$PI)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   16308   26682   29390   31846   32751  430091       5
summary(DATOS$GAE)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   29.00   29.00   35.00   33.54   38.00   40.00
summary(DATOS$GAQ)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   24.27   26.04   30.07   30.74   34.10   40.00       2

6.1.3 AnÔlisis Descriptivo Individual Variable categórica

Para hacer una tabla de frecuencias de las variables categóricas es necesario transformar primero la variable en factor:

# Se transforma primero en factor
 TL_FACTOR<- as.factor(DATOS$TL)
 # Se obtiene el resumen
 summary(TL_FACTOR)
##  Aguardiente Aguardientre          Ron          Run      Tequila       Whiski 
##          131            1           60            1           85            1 
##       Whisky         NA's 
##           20            1

Otra forma de obtener una tabla de frecuencias para las variables categóricas consiste en utilizar la función table del paquete base, que se carga automÔticamente cuando abrimos una sesión del R o del R-Studio:

table(DATOS$TL)
## 
##  Aguardiente Aguardientre          Ron          Run      Tequila       Whiski 
##          131            1           60            1           85            1 
##       Whisky 
##           20

Para extraer los nombres de las categorías de una variable categórica usamos la función labels del paquete base:

unique(DATOS$TL)
## [1] "Aguardiente"  "Tequila"      "Ron"          "Whisky"       "Run"         
## [6] NA             "Whiski"       "Aguardientre"

Reemplazar los nombres de las categorías mal codificadas, utilizamos la función str_replace del paquete stringr y lo guardamos ya corregido directamente en la variable TL de DATOS:

require(stringr)


DATOS$TL <- str_replace(DATOS$TL, "Aguardientre", "Aguardiente")

unique(DATOS$TL)
## [1] "Aguardiente" "Tequila"     "Ron"         "Whisky"      "Run"        
## [6] NA            "Whiski"

Reemplazar los nombres de las categorías mal codificadas, utilizamos la función str_replace del paquete stringr y lo guardamos ya corregido directamente en la variable TL de DATOS:

require(stringr)

reemplazos <- c("Run" = "Ron", "Whiski" = "Whisky")

DATOS$TL <- str_replace_all(DATOS$TL, reemplazos)

unique(DATOS$TL)
## [1] "Aguardiente" "Tequila"     "Ron"         "Whisky"      NA

6.1.4 Identificando valores NA:

is.na(DATOS[1:10,]) # Identifica cuƔles valores son NA
##          TL    PI   GAE   GAQ    CE
##  [1,] FALSE FALSE FALSE FALSE FALSE
##  [2,] FALSE FALSE FALSE FALSE FALSE
##  [3,] FALSE FALSE FALSE FALSE FALSE
##  [4,] FALSE FALSE FALSE FALSE FALSE
##  [5,] FALSE FALSE FALSE FALSE FALSE
##  [6,] FALSE FALSE FALSE FALSE FALSE
##  [7,] FALSE FALSE FALSE FALSE FALSE
##  [8,] FALSE FALSE FALSE FALSE FALSE
##  [9,] FALSE FALSE FALSE  TRUE FALSE
## [10,] FALSE FALSE FALSE FALSE FALSE
is.na(DATOS$TL) # Identifica cuƔles valores son NA en TL
##   [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [25] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [37] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [49] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [61] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [73] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [85] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [97] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [109] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
## [121] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [133] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [145] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [157] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [169] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [181] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [193] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [205] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [217] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [229] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [241] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [253] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [265] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [277] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [289] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

6.1.5 Identificando valores NA por columna:

 NAS_TL<-is.na(DATOS$TL) #GuardamosenNAS_TL
 DATOS[NAS_TL,] #FiltramosporlosNASdeTL
NAS_PI<-is.na(DATOS$PI) # Guardamos en NAS_PI
 DATOS[NAS_TL | NAS_PI,] # Filtramos por los NAS de TL y PI

6.1.6 Identificando las filas con al menos un NA:

La función complete.cases del paquete stats nos muestra cuÔles filas tienen datos en TODAS sus columnas:

complete.cases(DATOS)
##   [1]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE
##  [13]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
##  [25]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
##  [37]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
##  [49]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
##  [61]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
##  [73] FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
##  [85]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE
##  [97]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE
## [109]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE
## [121]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE
## [133]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE
## [145]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [157]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [169]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE
## [181]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [193]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [205]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [217]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [229]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [241]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [253]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [265]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [277]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [289]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE

Cuando negamos estos casos completos obtenemos las filas donde hay al menos un valor faltante, es decir,

!complete.cases(DATOS)
##   [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
##  [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [25] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [37] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [49] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [61] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [73]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [85] FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE
##  [97] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE
## [109] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
## [121] FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
## [133] FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE
## [145] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE
## [157] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [169] FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE
## [181] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [193] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [205] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [217] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [229] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [241] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [253] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [265] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [277] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [289] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

Guardamos los valores anteriores en un vector para luego filtrar por las filas donde hay al menos un valor NA:

NAS_ALL <- !complete.cases(DATOS)

DATOS[NAS_ALL, ]  # Filas donde hay al menos un valor NA

6.1.7 GrƔfica que identifica los NA en cada variable:

suppressWarnings(require(Amelia))
## Cargando paquete requerido: Amelia
## Cargando paquete requerido: Rcpp
## ## 
## ## Amelia II: Multiple Imputation
## ## (Version 1.8.3, built: 2024-11-07)
## ## Copyright (C) 2005-2025 James Honaker, Gary King and Matthew Blackwell
## ## Refer to http://gking.harvard.edu/amelia/ for more information
## ##
suppressWarnings(missmap(DATOS))

summary(DATOS)# Analicemos las mediadas de tendencia
##       TL                  PI              GAE             GAQ       
##  Length:300         Min.   : 16308   Min.   :29.00   Min.   :24.27  
##  Class :character   1st Qu.: 26682   1st Qu.:29.00   1st Qu.:26.04  
##  Mode  :character   Median : 29390   Median :35.00   Median :30.07  
##                     Mean   : 31846   Mean   :33.54   Mean   :30.74  
##                     3rd Qu.: 32751   3rd Qu.:38.00   3rd Qu.:34.10  
##                     Max.   :430091   Max.   :40.00   Max.   :40.00  
##                     NA's   :5                        NA's   :2      
##        CE       
##  Min.   :  0.0  
##  1st Qu.:239.4  
##  Median :250.3  
##  Mean   :249.6  
##  3rd Qu.:261.7  
##  Max.   :296.5  
##  NA's   :3

6.1.8 Identificando los valores atĆ­picos:

 par(mfrow=c(1,4))
 boxplot(DATOS$PI,main="PI")
 boxplot(DATOS$GAE,main="GAE")
 boxplot(DATOS$GAQ,main="GAQ")
 boxplot(DATOS$CE,main="CE")

De los grƔficos anteriores podemos ver que:

  • La variable PI tiene algunos valores atĆ­picos muy altos.
  • Las variables GAE y GAQ NO presentan valores atĆ­picos muy altos o muy bajos.
  • La variable CE presenta valores atĆ­picos muy bajos.

Filtramos los datos sin los valores atípicos de la variable PI y de la variable CE, filtrando con la función filter del paquete dplyr con el siguiente código:

require(dplyr)
filter(DATOS, PI > 100000)
filter(DATOS, CE < 200)

Reemplazamos los valores atĆ­picos por valores NA (lo cual es equivalente a eliminarlos):

DATOS$PI[DATOS$PI > 100000] <- NA
DATOS$CE[DATOS$CE < 200] <- NA

Repetimos los grƔficos boxplot:

par(mfrow=c(1,4))
 boxplot(DATOS$PI,main="PI")
 boxplot(DATOS$GAE,main="GAE")
 boxplot(DATOS$GAQ,main="GAQ")
 boxplot(DATOS$CE,main="CE")

6.1.9 Reemplazando los valores NA de la variable NĆŗmerica:

Si se supone que estas variables tienen Distribución normal, se modifican por la Media, de lo contrario seria por la Mediana, como técnica bÔsica de Imputación de datos.

Calculamos la media de las columnas numéricas con la función colMeans del paquete base. Para esto primero excluimos la variable categórica TL que se encuentra en la columna 1:

medias <- colMeans(DATOS[,-1], na.rm = TRUE)
medias
##          PI         GAE         GAQ          CE 
## 29518.52560    33.54000    30.74141   250.49003

El argumento na.rm=TRUE permite calcular las medias de los datos que no son NA.

6.1.10 Verificamos que ya no hay valores NA en las variables numƩricas:

Para esto usamos la función replace_na del paquete tidyr:

require(tidyr)  # Cargamos el paquete

# Creamos una lista con los reemplazos:
reemplazos <- list(PI = medias[1], GAE = medias[2], GAQ = medias[3], CE = medias[4])

# Reemplazamos y guardamos en DATOS:
DATOS <- replace_na(DATOS, reemplazos)
summary(DATOS)
##       TL                  PI             GAE             GAQ       
##  Length:300         Min.   :16308   Min.   :29.00   Min.   :24.27  
##  Class :character   1st Qu.:26710   1st Qu.:29.00   1st Qu.:26.04  
##  Mode  :character   Median :29519   Median :35.00   Median :30.22  
##                     Mean   :29519   Mean   :33.54   Mean   :30.74  
##                     3rd Qu.:32459   3rd Qu.:38.00   3rd Qu.:34.07  
##                     Max.   :42022   Max.   :40.00   Max.   :40.00  
##        CE       
##  Min.   :210.9  
##  1st Qu.:239.7  
##  Median :250.5  
##  Mean   :250.5  
##  3rd Qu.:261.5  
##  Max.   :296.5

6.1.11 Reemplazando los valores NA de la variable categórica:

unique(DATOS$TL)
## [1] "Aguardiente" "Tequila"     "Ron"         "Whisky"      NA
FACTOR_TL<-as.factor(DATOS$TL) #Convertimos en factorTL
 FRECUENCIAS_TL<-summary(FACTOR_TL) #Tabla de frecuencias
 barplot(FRECUENCIAS_TL) #Graficamos las frecuencias

Si decidimos reemplazar por el valor mÔs frecuente, lo cual NO es recomendable, lo hacemos con el código:

reemplazos <- list(TL = "Aguardiente")
DATOS <- replace_na(DATOS, reemplazos)

# Verificamos el cambio:
unique(DATOS$TL)
## [1] "Aguardiente" "Tequila"     "Ron"         "Whisky"
 FACTOR_TL<-as.factor(DATOS$TL) #Convertimos enfactor TL
 FRECUENCIAS_TL<-summary(FACTOR_TL) #Tabla de frecuencias
 barplot(FRECUENCIAS_TL) #Graficamos las frecuencias

6.1.12 Finalmente se realiza el esquema de los Datos y se verifica que no se encuentren NAS

suppressWarnings(missmap(DATOS))

7 Otras librerias para visualizar datos: Operadores pipe %>% y %<>%

Los desarrolladores del R-Studio cada vez crean herramientas que buscan simplificar los códigos que utilizamos en R.

Dos herramientas muy Ćŗtiles son:

  • El operador pipe de continuidad: %>%.
  • Operador pipe de asignación compuesta: %<>%.

Dichos operadores se encuentran en el paquete magrittr.

7.0.1 ¿Cómo podemos usar estos operadores?

La mejor manera de ver el uso de los pipes de continuidad y de asignación compuesta es a través de ejemplos.

Considere que tenemos una variable x que tiene un valor igual a (-3). Queremos aplicar a x:

  • Primero la función valor absoluto;
  • Luego al resultado anterior aplicarle logaritmo natural y;
  • Finalmente al resultado anterior aplicarle raĆ­z cuadrada.
suppressWarnings(require(magrittr)) #Cargamos el paquete

 x<-(-3)
 r_1<-abs(x) #r_1 es el primer resultado.
 r_2<-log(r_1) #r_2 es el segundo resultado.
 r_3<-sqrt(r_2)#r_3es el tercer y Ćŗltimo resultado.
 r_3#Este es el resultado finalque se querĆ­a obtener
## [1] 1.048147

En un solo renglón esto podría ser escrito como:

x<-(-3)
 r_3<-sqrt(log(abs(x))) #Las tres funciones se aplican
 r_3
## [1] 1.048147

Ousando el pipe de continuidad %>% serĆ­a:

x<-(-3)
 r_3<-x %>%abs %>%log %>%sqrt #Las tres funciones se aplican
 r_3
## [1] 1.048147

Si deseamos reemplazar el valor de x por el resultado final, utilizamos el pipe compuesto %<>%, es decir,

x <- (-3)

x %<>% abs %<>% log %<>% sqrt  # Las tres funciones se aplican

# El valor de x ya no es (-3) sino el resultado final deseado:

x
## [1] 1.048147

7.1 Aplicación prÔctica #1 de los operadores.

Anteriormente vimos que cómo obtener un grÔfico de barra con los códigos:

FACTOR_TL <- as.factor(DATOS$TL)  # Convertimos en factor TL
FRECUENCIAS_TL <- summary(FACTOR_TL)  # Tabla de frecuencias
barplot(FRECUENCIAS_TL)  # Graficamos las frecuencias

Los tres renglones anteriores se pueden resumir con los pipes:

DATOS$TL %>% as.factor %>% summary %>% barplot

7.2 Aplicación prÔctica #2 de los operadores %>% y %<>% para los nombres de las variables.

En esta sección, utilizaremos operadores para modificar y manejar los nombres de las variables en la base de datos master.csv. Esto es particularmente útil cuando los nombres de las columnas no son intuitivos o contienen caracteres que pueden complicar su manipulación en R.

Consideremos nuevamente la base de datos master.csv:

  • Si leemos con la función read.csv del paquete utils:

7.3 Manejando los nombres de las variables:

En esta sección, utilizaremos operadores para modificar y manejar los nombres de las variables en la base de datos master.csv. Esto es particularmente útil cuando los nombres de las columnas no son intuitivos o contienen caracteres que pueden complicar su manipulación en R.

Consideremos nuevamente la base de datos master.csv:

direccion <- "https://raw.github.com/Kalbam/Datos/blob/main/master.csv"
#master<-read_csv("master.csv")
master <- readr::read_csv("https://raw.githubusercontent.com/Kalbam/Datos/main/master.csv")
## Rows: 27820 Columns: 12
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (5): country, sex, age, country-year, generation
## dbl (6): year, suicides_no, population, suicides/100k pop, HDI for year, gdp...
## num (1): gdp_for_year ($)
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
master %>% names  # Equivalente a: names(master)
##  [1] "country"            "year"               "sex"               
##  [4] "age"                "suicides_no"        "population"        
##  [7] "suicides/100k pop"  "country-year"       "HDI for year"      
## [10] "gdp_for_year ($)"   "gdp_per_capita ($)" "generation"

7.3.1 Limpiando los nombres de las variables:

Para facilitar la manipulación de las variables en R, es recomendable limpiar los nombres de las columnas, eliminando caracteres especiales y estandarizando el formato. Para esto, utilizaremos la función clean_names del paquete janitor.

suppressWarnings(require(janitor))  # Contiene la función clean_names
## Cargando paquete requerido: janitor
## 
## Adjuntando el paquete: 'janitor'
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
master %<>% clean_names  # Equivalente a: master <- clean_names(master)

7.3.2 Renombrando las variables:

viejos <- master %>% names
viejos
##  [1] "country"           "year"              "sex"              
##  [4] "age"               "suicides_no"       "population"       
##  [7] "suicides_100k_pop" "country_year"      "hdi_for_year"     
## [10] "gdp_for_year"      "gdp_per_capita"    "generation"
require(dplyr)  # Este paquete contiene la función rename
require(magrittr) 

master %<>% rename(
  pais = viejos[1],
  anio = viejos[2],
  sexo = viejos[3],
  edad = viejos[4],
  num_suic = viejos[5],
  poblacion = viejos[6],
  suic_x100k = viejos[7],
  pais_anio = viejos[8],
  idh_anio = viejos[9],
  pib_anio = viejos[10],
  pib_pcap = viejos[11],
  generacion = viejos[12]
)

7.3.3 Vemos el efecto de los cambios de nombres en las variables:

Después de realizar los cambios en los nombres de las variables, podemos verificar el resultado ejecutando el siguiente código:

master %>% names
##  [1] "pais"       "anio"       "sexo"       "edad"       "num_suic"  
##  [6] "poblacion"  "suic_x100k" "pais_anio"  "idh_anio"   "pib_anio"  
## [11] "pib_pcap"   "generacion"
str(master)
## spc_tbl_ [27,820 Ɨ 12] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ pais      : chr [1:27820] "Albania" "Albania" "Albania" "Albania" ...
##  $ anio      : num [1:27820] 1987 1987 1987 1987 1987 ...
##  $ sexo      : chr [1:27820] "male" "male" "female" "male" ...
##  $ edad      : chr [1:27820] "15-24 years" "35-54 years" "15-24 years" "75+ years" ...
##  $ num_suic  : num [1:27820] 21 16 14 1 9 1 6 4 1 0 ...
##  $ poblacion : num [1:27820] 312900 308000 289700 21800 274300 ...
##  $ suic_x100k: num [1:27820] 6.71 5.19 4.83 4.59 3.28 2.81 2.15 1.56 0.73 0 ...
##  $ pais_anio : chr [1:27820] "Albania1987" "Albania1987" "Albania1987" "Albania1987" ...
##  $ idh_anio  : num [1:27820] NA NA NA NA NA NA NA NA NA NA ...
##  $ pib_anio  : num [1:27820] 2.16e+09 2.16e+09 2.16e+09 2.16e+09 2.16e+09 ...
##  $ pib_pcap  : num [1:27820] 796 796 796 796 796 796 796 796 796 796 ...
##  $ generacion: chr [1:27820] "Generation X" "Silent" "Generation X" "G.I. Generation" ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   country = col_character(),
##   ..   year = col_double(),
##   ..   sex = col_character(),
##   ..   age = col_character(),
##   ..   suicides_no = col_double(),
##   ..   population = col_double(),
##   ..   `suicides/100k pop` = col_double(),
##   ..   `country-year` = col_character(),
##   ..   `HDI for year` = col_double(),
##   ..   `gdp_for_year ($)` = col_number(),
##   ..   `gdp_per_capita ($)` = col_double(),
##   ..   generation = col_character()
##   .. )
##  - attr(*, "problems")=<externalptr>

Cuando leemos la base de datos master.csv con la función read.csv tenemos un problema con las comas de la variable PIB por año, lo cual hace que sea reconocida como una variable categórica. Esto lo vemos observando los primeros 5 datos de dicha variable:

master$pib_anio[1:5]
## [1] 2156624900 2156624900 2156624900 2156624900 2156624900

7.4 Eliminando variables innecesarias

En la base de datos master.csv encontramos la variable pais_anio la cual aparentemente no da mucha información.

Para eliminarla podemos utilizar la función select del paquete dplyr:

require(dplyr)

master %<>% select(-pais_anio)  # - elimina

Verificamos que sí quedó eliminada:

 master %>% names
##  [1] "pais"       "anio"       "sexo"       "edad"       "num_suic"  
##  [6] "poblacion"  "suic_x100k" "idh_anio"   "pib_anio"   "pib_pcap"  
## [11] "generacion"

7.5 Seleccionando variables

La función select también es útil para seleccionar un subconjunto de variables deseadas:

master_sub <- master %>% select(anio, pais, num_suic)

head(master_sub)  # Equivale a: master_sub %>% head

7.6 Creando variables

La función mutate del paquete dplyr es útil para crear nuevas variables. Por ejemplo, si deseamos crear una variable nueva que considere el porcentaje de suicidios, denotada por p_suic, hacemos:

master %<>% mutate(p_suic = 100 * (num_suic / poblacion))

master %>% names
##  [1] "pais"       "anio"       "sexo"       "edad"       "num_suic"  
##  [6] "poblacion"  "suic_x100k" "idh_anio"   "pib_anio"   "pib_pcap"  
## [11] "generacion" "p_suic"

Para obtener un resumen estadƭstico bƔsico de la variable que acabamos de crear, p_suic, tenemos al menos dos opciones:

# FORMA 1:
master$p_suic %>% summary
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
## 0.0000000 0.0009187 0.0059909 0.0128161 0.0166177 0.2249719

7.7 Clases de variables

Algunas de las clases de variables que se manejan en R son:

  • as.numeric(): Convierte una variable a tipo numĆ©rico (double).
  • as.logical(): Convierte una variable a tipo lógico.
  • as.integer(): Convierte una variable a tipo entero.
  • as.factor(): Convierte una variable a tipo factor.
  • as.character(): Convierte una variable a tipo carĆ”cter (character).
  • as.ordered(): Convierte una variable a tipo factor asumiendo un orden o jerarquĆ­a entre los niveles.

7.8 Clases de variables relacionadas con fechas

Otro tipo o clase de variables bastante utilizados en distintas bases de datos tienen que ver con fechas. El paquete lubridate tiene distintas opciones, entre las que resaltan:

  • as_date(): Convierte una variable de tipo carĆ”cter o factor a tipo fecha.
  • dmy(): Convierte una variable de tipo carĆ”cter o factor a tipo fecha dĆ­a-mes-aƱo. TambiĆ©n estĆ”n las funciones mdy() (mes-dĆ­a-aƱo), myd() (mes-aƱo-dĆ­a), ymd() (aƱo-mes-dĆ­a), ydm() (aƱo-dĆ­a-mes) o dym() (dĆ­a-aƱo-mes).

NOTA: Luego de aplicar el formato fecha anterior, se pueden aplicar las funciones: day() para extraer el dƭa, month() para extraer el mes y year() para extraer el aƱo.

7.9 Transformando clases de variables

require(tibble)

master %>% glimpse
## Rows: 27,820
## Columns: 12
## $ pais       <chr> "Albania", "Albania", "Albania", "Albania", "Albania", "Alb…
## $ anio       <dbl> 1987, 1987, 1987, 1987, 1987, 1987, 1987, 1987, 1987, 1987,…
## $ sexo       <chr> "male", "male", "female", "male", "male", "female", "female…
## $ edad       <chr> "15-24 years", "35-54 years", "15-24 years", "75+ years", "…
## $ num_suic   <dbl> 21, 16, 14, 1, 9, 1, 6, 4, 1, 0, 0, 0, 2, 17, 1, 14, 4, 8, …
## $ poblacion  <dbl> 312900, 308000, 289700, 21800, 274300, 35600, 278800, 25720…
## $ suic_x100k <dbl> 6.71, 5.19, 4.83, 4.59, 3.28, 2.81, 2.15, 1.56, 0.73, 0.00,…
## $ idh_anio   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ pib_anio   <dbl> 2156624900, 2156624900, 2156624900, 2156624900, 2156624900,…
## $ pib_pcap   <dbl> 796, 796, 796, 796, 796, 796, 796, 796, 796, 796, 796, 796,…
## $ generacion <chr> "Generation X", "Silent", "Generation X", "G.I. Generation"…
## $ p_suic     <dbl> 0.0067114094, 0.0051948052, 0.0048325854, 0.0045871560, 0.0…

Transformemos las variables pais, sexo, edad y generacion en variables de tipo factor. Para esto utilizaremos la función mutate_at del paquete dplyr:

require(dplyr)

master %<>% mutate_at(vars(pais, sexo, edad, generacion), as.factor)

El equivalente serĆ­a:

master$pais <- as.factor(master$pais)
master$sexo <- as.factor(master$sexo)
#master$edad <- as.factor(master$edad)
master$generacion <- as.factor(master$generacion)

Es utilizando el mutate_if:

master %<>% mutate_if(is.character, as.factor)
require(tibble)
 master %>% glimpse
## Rows: 27,820
## Columns: 12
## $ pais       <fct> Albania, Albania, Albania, Albania, Albania, Albania, Alban…
## $ anio       <dbl> 1987, 1987, 1987, 1987, 1987, 1987, 1987, 1987, 1987, 1987,…
## $ sexo       <fct> male, male, female, male, male, female, female, female, mal…
## $ edad       <fct> 15-24 years, 35-54 years, 15-24 years, 75+ years, 25-34 yea…
## $ num_suic   <dbl> 21, 16, 14, 1, 9, 1, 6, 4, 1, 0, 0, 0, 2, 17, 1, 14, 4, 8, …
## $ poblacion  <dbl> 312900, 308000, 289700, 21800, 274300, 35600, 278800, 25720…
## $ suic_x100k <dbl> 6.71, 5.19, 4.83, 4.59, 3.28, 2.81, 2.15, 1.56, 0.73, 0.00,…
## $ idh_anio   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ pib_anio   <dbl> 2156624900, 2156624900, 2156624900, 2156624900, 2156624900,…
## $ pib_pcap   <dbl> 796, 796, 796, 796, 796, 796, 796, 796, 796, 796, 796, 796,…
## $ generacion <fct> Generation X, Silent, Generation X, G.I. Generation, Boomer…
## $ p_suic     <dbl> 0.0067114094, 0.0051948052, 0.0048325854, 0.0045871560, 0.0…

7.10 Subconjuntos de Datos

Ya vimos antes la función filter del paquete dplyr para filtrar bases de datos y extraer subconjuntos de bases de datos. AdemÔs de esta función, existe la función subset del paquete base y que permite seleccionar subconjuntos de datos. Como ejemplo, supongamos que queremos extraer las variables:

master_sub <- subset(master,
                     subset = (anio < 2000 & pais == "Colombia"),
                     select = c(pais, anio, pib_anio, suic_x100k, sexo))

head(master_sub)
 glimpse(master_sub)
## Rows: 180
## Columns: 5
## $ pais       <fct> Colombia, Colombia, Colombia, Colombia, Colombia, Colombia,…
## $ anio       <dbl> 1985, 1985, 1985, 1985, 1985, 1985, 1985, 1985, 1985, 1985,…
## $ pib_anio   <dbl> 34894411352, 34894411352, 34894411352, 34894411352, 3489441…
## $ suic_x100k <dbl> 17.02, 11.13, 8.31, 8.02, 7.67, 3.73, 1.98, 1.72, 1.29, 1.0…
## $ sexo       <fct> male, male, male, male, male, female, female, female, femal…
 summary(master_sub)
##                   pais          anio         pib_anio           suic_x100k    
##  Colombia           :180   Min.   :1985   Min.   :3.489e+10   Min.   : 0.000  
##  Albania            :  0   1st Qu.:1988   1st Qu.:3.921e+10   1st Qu.: 0.980  
##  Antigua and Barbuda:  0   Median :1992   Median :4.928e+10   Median : 2.575  
##  Argentina          :  0   Mean   :1992   Mean   :6.228e+10   Mean   : 4.518  
##  Armenia            :  0   3rd Qu.:1996   3rd Qu.:9.251e+10   3rd Qu.: 7.795  
##  Aruba              :  0   Max.   :1999   Max.   :1.067e+11   Max.   :18.830  
##  (Other)            :  0                                                      
##      sexo   
##  female:90  
##  male  :90  
##             
##             
##             
##             
## 

Para eliminar las categorías o países que ya no aparecen en el subconjunto de datos master_sub y que se heredaron de los datos originales, master, utilizamos la función droplevels del paquete base:

master_sub <- droplevels(master_sub)

Haciendo un resumen del subconjunto de datos, vemos que ya no aparecen las otras categorĆ­as o paĆ­ses de la base de datos original:

summary(master_sub)
##        pais          anio         pib_anio           suic_x100k         sexo   
##  Colombia:180   Min.   :1985   Min.   :3.489e+10   Min.   : 0.000   female:90  
##                 1st Qu.:1988   1st Qu.:3.921e+10   1st Qu.: 0.980   male  :90  
##                 Median :1992   Median :4.928e+10   Median : 2.575              
##                 Mean   :1992   Mean   :6.228e+10   Mean   : 4.518              
##                 3rd Qu.:1996   3rd Qu.:9.251e+10   3rd Qu.: 7.795              
##                 Max.   :1999   Max.   :1.067e+11   Max.   :18.830

7.11 AnƔlisis descriptivos por subgrupos

En muchas situaciones resulta muy Ćŗtil obtener resĆŗmenes estadĆ­sticos por subgrupos de datos que se obtienen de manera ā€œnaturalā€ al considerar variables categóricas como por ejemplo: sexo, aƱo, paĆ­s, etc.

El programa R ofrece mĆŗltiples opciones y herramientas para obtener dichos resĆŗmenes y entre estos destacan las funciones group_by y summarise del paquete dplyr que hace parte de la ā€œfamiliaā€ tidyverse.

Recuerde que la base de datos master_sub es un subconjunto de master correspondiente a Colombia. Un resumen por gƩnero se obtiene como:

require(dplyr)

master_sub %>% 
  group_by(sexo) %>% 
  summarise(
    media = mean(suic_x100k),
    mediana = median(suic_x100k),
    n = n()
  )

Un resumen por aƱo se obtiene como:

master_sub %>% 
  group_by(anio) %>% 
  summarise(
    media = mean(suic_x100k),
    mediana = median(suic_x100k),
    n = n()
  )

7.12 Unión de bases de datos.

En algunos casos es necesario unir varios conjuntos o bases de datos a través de una variable que funciona como un código de concatenación. En R, existe la función merge del paquete base. Para ver cómo funciona, considere los siguientes conjuntos de datos:

dat1 = data.frame(
  Nombre = c('Carlos', 'Juan', 'Sara', 'Pedro', 'Luis', 'Ana'),
  Edad = c(21, 23, 19, 25, 19, 26)
)

dat2 = data.frame(
  Id = c(1029, 2516, 8437, 9289, 7373),
  Nombre = c('Ana', 'Carlos', 'Sara', 'Pedro', 'Luis')
)

dat3 = data.frame(
  Genero = c('F', 'M', 'M', 'M', 'F', 'M'),
  Nombre = c('Ana', 'Carlos', 'Juan', 'Pedro', 'Sara', 'Luis')
)

dat4 = data.frame(
  Nombre = c('Carlos', 'Juan', 'Sara', 'Pedro', 'Luis', 'Ana'),
  Programa = c('Estadistica', 'Matematicas', 'Ing. Sistemas', 'Ing. Civil', 'Economia', 'Ing. Ambiental')
)

Para visualizar los conjuntos de datos usamos la función kable del paquete knitr:

require(knitr)
## Cargando paquete requerido: knitr
kable(dat1)
Nombre Edad
Carlos 21
Juan 23
Sara 19
Pedro 25
Luis 19
Ana 26
 kable(dat3)
Genero Nombre
F Ana
M Carlos
M Juan
M Pedro
F Sara
M Luis

Aplicamos la función merge para unir los conjuntos de datos dat1 y dat2:

datos_1 <- merge(dat1, dat2, by = 'Nombre')

kable(datos_1)
Nombre Edad Id
Ana 26 1029
Carlos 21 2516
Luis 19 7373
Pedro 25 9289
Sara 19 8437

Con la opción sort=FALSE descartamos que el conjunto de datos los organice en orden alfabético por Nombre.

datos_1<-merge(dat1,dat2,by='Nombre', sort=FALSE)
 kable(datos_1)
Nombre Edad Id
Carlos 21 2516
Sara 19 8437
Pedro 25 9289
Luis 19 7373
Ana 26 1029

Para que la unión de las bases de datos considere todos los registros, usamos la opción all=TRUE. En este caso, Juan aparece, pero con un NA en la variable Id.

datos_1<-merge(dat1,dat2,by='Nombre', all=TRUE)
 kable(datos_1)
Nombre Edad Id
Ana 26 1029
Carlos 21 2516
Juan 23 NA
Luis 19 7373
Pedro 25 9289
Sara 19 8437

Unimos los conjuntos dat1 y dat2 con dat3:

 datos_3<-merge(datos_1,dat3,by='Nombre', all=TRUE)
 kable(datos_3)
Nombre Edad Id Genero
Ana 26 1029 F
Carlos 21 2516 M
Juan 23 NA M
Luis 19 7373 M
Pedro 25 9289 M
Sara 19 8437 F

Note que la función merge solo permite unir dos bases de datos a la vez. Unimos los conjuntos dat1, dat2 y dat3 con dat4:

datos_4<-merge(datos_3, dat4, by='Nombre', all=TRUE)
 kable(datos_4)
Nombre Edad Id Genero Programa
Ana 26 1029 F Ing. Ambiental
Carlos 21 2516 M Estadistica
Juan 23 NA M Matematicas
Luis 19 7373 M Economia
Pedro 25 9289 M Ing. Civil
Sara 19 8437 F Ing. Sistemas

Para concatenar todas las bases de datos en una sola línea, podemos utilizar la función Reduce del paquete base.

dat1 = data.frame(
 Nombre=c('Carlos','Juan','Sara','Pedro','Luis','Ana'),
 Edad=c(21,23,19,25,19,26))
 dat2 = data.frame(
 Id=c(1029,2516,8437,9289,7373),
 Nombre=c('Ana','Carlos','Sara','Pedro','Luis'))
 dat3 = data.frame(
 Genero=c('F','M','M','M','F','M'),
 Nombre=c('Ana','Carlos','Juan','Pedro','Sara','Luis'))
 dat4 = data.frame(
 Nombre=c('Carlos','Juan','Sara','Pedro','Luis','Ana'),
 Programa=c('Estadistica','Matematicas','Ing. Sistemas',
 'Ing. Civil','Economia','Ing. Ambiental'))

La función Reduce tiene como primer argumento una función y como segundo argumento una lista con las bases de datos:

datos_4<-Reduce(merge, list(dat1,dat2,dat3,dat4))
 kable(datos_4)
Nombre Edad Id Genero Programa
Ana 26 1029 F Ing. Ambiental
Carlos 21 2516 M Estadistica
Luis 19 7373 M Economia
Pedro 25 9289 M Ing. Civil
Sara 19 8437 F Ing. Sistemas

Note que no se incluye a Juan en la base de datos final, ya que la función merge por defecto incluye solo datos completos.

Para cambiar las opciones del merge, debemos crear una función auxiliar que permita incluir todos los casos:

merge_aux <- function(...) merge (..., all=TRUE)
 datos_4<-Reduce(merge_aux, list(dat1,dat2,dat3,dat4))
 kable(datos_4)
Nombre Edad Id Genero Programa
Ana 26 1029 F Ing. Ambiental
Carlos 21 2516 M Estadistica
Juan 23 NA M Matematicas
Luis 19 7373 M Economia
Pedro 25 9289 M Ing. Civil
Sara 19 8437 F Ing. Sistemas

7.13 Ejemplo PrƔctico #2.

Se consideran los datos de importación de vehículos reportados por Fasecolda, que estÔn divididos en cuatro bases de datos: DB1.csv, DB2.csv, DB3.csv y DB4.csv.

Estos archivos pueden descargarse desde el siguiente enlace de GitHub: Datos de Importación de Vehículos.

BD_1<-read_csv("DB1.csv")
## Rows: 12433 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): Marca
## dbl (2): Codigo, Peso
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
names(BD_1)
## [1] "Codigo" "Marca"  "Peso"
BD_2<-read.csv("DB2.csv",header=TRUE)
names(BD_2)
## [1] "Codigo" "Estado"
BD_3<-read.csv("DB3.csv",header=TRUE)
names(BD_3)
## [1] "Importado"         "AireAcondicionado" "Codigo"           
## [4] "Cilindraje"        "Potencia"
BD_4<-read.csv("DB4.csv",header=TRUE)
names(BD_4)
## [1] "Estado"       "Nacionalidad" "Combustible"  "Puertas"      "Transmision" 
## [6] "Codigo"
merge_aux <- function(...) merge (..., all=T)
Vehiculos <- Reduce(merge_aux,list(BD_1,BD_2,BD_3,BD_4))
Vehiculos %>% names
##  [1] "Codigo"            "Estado"            "Marca"            
##  [4] "Peso"              "Importado"         "AireAcondicionado"
##  [7] "Cilindraje"        "Potencia"          "Nacionalidad"     
## [10] "Combustible"       "Puertas"           "Transmision"
Vehiculos %>% glimpse
## Rows: 12,433
## Columns: 12
## $ Codigo            <dbl> 10001001, 10001002, 10001003, 10001004, 10004001, 10…
## $ Estado            <chr> "Activo", "Activo", "Activo", "Activo", "Activo", "A…
## $ Marca             <chr> "TATA", "TATA", "TATA", "TATA", "TATA", "TATA", "TAT…
## $ Peso              <dbl> 980, 0, 1005, 1070, 0, 2225, 1070, 1521, 1477, 1338,…
## $ Importado         <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
## $ AireAcondicionado <int> 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0…
## $ Cilindraje        <int> 1405, 1400, 1405, 1405, 1984, 2956, 1405, 3800, 3100…
## $ Potencia          <int> 43, 0, 75, 84, 0, 113, 83, 170, 160, 120, 205, 205, …
## $ Nacionalidad      <chr> "IND", "IND", "IND", "IND", "IND", "IND", "IND", "US…
## $ Combustible       <chr> "DSL", "DSL", "", "", "", "", "", "GSL", "GSL", "GSL…
## $ Puertas           <int> 5, 5, 5, 5, 2, 5, 5, 4, 2, 4, 4, 4, 4, 4, 4, 2, 0, 2…
## $ Transmision       <chr> "4X2", "4X2", "", "", "", "4X4", "", "4X2", "4X2", "…
Vehiculos %>% summary
##      Codigo            Estado             Marca                Peso      
##  Min.   :  101001   Length:12433       Length:12433       Min.   :    0  
##  1st Qu.: 3201240   Class :character   Class :character   1st Qu.:  268  
##  Median : 6201016   Mode  :character   Mode  :character   Median : 1355  
##  Mean   : 8406562                                         Mean   : 1778  
##  3rd Qu.: 9401013                                         3rd Qu.: 2005  
##  Max.   :40301001                                         Max.   :41000  
##    Importado      AireAcondicionado   Cilindraje       Potencia    
##  Min.   :0.0000   Min.   :0.0000    Min.   :    0   Min.   :  0.0  
##  1st Qu.:1.0000   1st Qu.:0.0000    1st Qu.: 1339   1st Qu.: 80.0  
##  Median :1.0000   Median :1.0000    Median : 1998   Median :122.0  
##  Mean   :0.8407   Mean   :0.5501    Mean   : 2585   Mean   :134.7  
##  3rd Qu.:1.0000   3rd Qu.:1.0000    3rd Qu.: 3246   3rd Qu.:175.0  
##  Max.   :1.0000   Max.   :1.0000    Max.   :15950   Max.   :662.0  
##  Nacionalidad       Combustible           Puertas      Transmision       
##  Length:12433       Length:12433       Min.   :0.000   Length:12433      
##  Class :character   Class :character   1st Qu.:2.000   Class :character  
##  Mode  :character   Mode  :character   Median :3.000   Mode  :character  
##                                        Mean   :2.952                     
##                                        3rd Qu.:5.000                     
##                                        Max.   :6.000
Vehiculos$Transmision %>%as.factor%>%summary
##       2X1  3X1  3X2  4x2  4X2  4X4  6X2  6x4  6X4  6X6  8X4 
##  700 1402   59   15    4 7603 2147   14    1  463    2   23
Vehiculos$Transmision %>%as.factor %>% summary %>%
 sort(decreasing=TRUE) %>%barplot(las=1)

7.14 Herramientas de Verificación de Datos.

suppressWarnings(require(editrules))
## Cargando paquete requerido: editrules
## Cargando paquete requerido: igraph
## 
## Adjuntando el paquete: 'igraph'
## The following objects are masked from 'package:lubridate':
## 
##     %--%, union
## The following objects are masked from 'package:dplyr':
## 
##     as_data_frame, groups, union
## The following objects are masked from 'package:purrr':
## 
##     compose, simplify
## The following object is masked from 'package:tidyr':
## 
##     crossing
## The following object is masked from 'package:tibble':
## 
##     as_data_frame
## The following objects are masked from 'package:stats':
## 
##     decompose, spectrum
## The following object is masked from 'package:base':
## 
##     union
## 
## Adjuntando el paquete: 'editrules'
## The following objects are masked from 'package:igraph':
## 
##     blocks, normalize
## The following object is masked from 'package:dplyr':
## 
##     contains
## The following object is masked from 'package:purrr':
## 
##     reduce
## The following objects are masked from 'package:tidyr':
## 
##     contains, separate
 reglas <-c("Peso>0",
 "Potencia>0",
 "Cilindraje>0",
 "Transmision%in%c('2X1','3X1','3X2','4x2','4X2','4X4',
 '6X2','6x4','6X4','6X6','8X4')",
 "Combustible%in%c('DSL','ELT','GAS','GSL','HBD')")
 Condiciones<-editset(reglas)
 Condiciones
## 
## Data model:
## dat1 : Combustible %in% c('DSL', 'ELT', 'GAS', 'GSL', 'HBD')
## dat2 : Transmision %in% c('2X1', '3X1', '3X2', '4x2', '4X2', '4X4', '6X2', '6x4', '6X4', '6X6', '8X4') 
## 
## Edit set:
## num1 : 0 < Peso
## num2 : 0 < Potencia
## num3 : 0 < Cilindraje

Para chequear cuÔles elementos de nuestra base de datos cumplen o no con las reglas que impusimos, utilizamos la función violatedEdits del paquete editrules:

Errores <- violatedEdits(c(Condiciones), Vehiculos)

Errores[1:6, ]  # Presentamos las primeras 6 filas
##       edit
## record  num1  num2  num3  dat1  dat2
##      1 FALSE FALSE FALSE FALSE FALSE
##      2  TRUE  TRUE FALSE FALSE FALSE
##      3 FALSE FALSE FALSE  TRUE  TRUE
##      4 FALSE FALSE FALSE  TRUE  TRUE
##      5  TRUE  TRUE FALSE  TRUE  TRUE
##      6 FALSE FALSE FALSE  TRUE FALSE
 plot(Errores) #Graficamoslos%deerrores

Para ubicar los errores, usamos la función localizeErrors del paquete editrules:

Localiza <- localizeErrors(Condiciones, Vehiculos)$adapt

# Lo transformamos en data frame:
Localiza <- data.frame(Localiza)

Visualizamos Localiza:

head(Localiza)  # TRUE significa incumple alguna regla

Para ver cuÔntas filas hay con por lo menos un problema, utilizamos la función apply del paquete base junto con una función auxiliar creada con la función any, también del paquete base:

any_aux <- function(x) any(x == TRUE)  # Función auxiliar

# Verifica fila por fila (a eso se refiere el 1)
verificacion <- apply(Localiza, 1, any_aux)

# Para saber cuƔles filas tienen al menos un problema:
filas <- which(verificacion)

# Para contar cuƔntas filas hay con al menos un problema:
length(filas)
## [1] 2785
 #% de filas con al menos un problema:
 100*(length(filas)/nrow(Vehiculos))
## [1] 22.40006

7.15 Graficos DescrĆ­ptivos

Continuemos analizando entonces la base de datos master.csv y para ello la procesamos:

direccion<-"master.csv"
 master <- read_csv(direccion)
## Rows: 27820 Columns: 12
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (5): country, sex, age, country-year, generation
## dbl (6): year, suicides_no, population, suicides/100k pop, HDI for year, gdp...
## num (1): gdp_for_year ($)
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
 viejos <- master %>% names
 master %<>% rename(pais=viejos[1], anio=viejos[2],
 sexo=viejos[3], edad=viejos[4],
 num_suic=viejos[5], poblacion=viejos[6],
 suic_x100k=viejos[7], pais_anio=viejos[8],
 idh_anio=viejos[9], pib_anio=viejos[10],
 pib_pcap=viejos[11], generacion=viejos[12]
 )
master %<>% select(-pais_anio)
master %<>% mutate(pib_anio_mill=pib_anio/1000000)
master %<>% mutate_if(is.character, as.factor)
master %>% glimpse
## Rows: 27,820
## Columns: 12
## $ pais          <fct> Albania, Albania, Albania, Albania, Albania, Albania, Al…
## $ anio          <dbl> 1987, 1987, 1987, 1987, 1987, 1987, 1987, 1987, 1987, 19…
## $ sexo          <fct> male, male, female, male, male, female, female, female, …
## $ edad          <fct> 15-24 years, 35-54 years, 15-24 years, 75+ years, 25-34 …
## $ num_suic      <dbl> 21, 16, 14, 1, 9, 1, 6, 4, 1, 0, 0, 0, 2, 17, 1, 14, 4, …
## $ poblacion     <dbl> 312900, 308000, 289700, 21800, 274300, 35600, 278800, 25…
## $ suic_x100k    <dbl> 6.71, 5.19, 4.83, 4.59, 3.28, 2.81, 2.15, 1.56, 0.73, 0.…
## $ idh_anio      <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ pib_anio      <dbl> 2156624900, 2156624900, 2156624900, 2156624900, 21566249…
## $ pib_pcap      <dbl> 796, 796, 796, 796, 796, 796, 796, 796, 796, 796, 796, 7…
## $ generacion    <fct> Generation X, Silent, Generation X, G.I. Generation, Boo…
## $ pib_anio_mill <dbl> 2156.625, 2156.625, 2156.625, 2156.625, 2156.625, 2156.6…
master$edad %>%levels
## [1] "15-24 years" "25-34 years" "35-54 years" "5-14 years"  "55-74 years"
## [6] "75+ years"
master$generacion %>%levels
## [1] "Boomers"         "G.I. Generation" "Generation X"    "Generation Z"   
## [5] "Millenials"      "Silent"

Se redifen los niveles

master$edad %<>%factor(levels=c("5-14years","15-24years",
 "25-34years","35-54years","55-74years","75+years"))
 master$generacion %<>%factor(levels=c('G.I.Generation','Silent',
 'Boomers','GenerationX',
 'Millenials','GenerationZ'))
master_col <- subset(master,subset=(pais=="Colombia"))
master_col %<>% droplevels
master_col %>% glimpse
## Rows: 372
## Columns: 12
## $ pais          <fct> Colombia, Colombia, Colombia, Colombia, Colombia, Colomb…
## $ anio          <dbl> 1985, 1985, 1985, 1985, 1985, 1985, 1985, 1985, 1985, 19…
## $ sexo          <fct> male, male, male, male, male, female, female, female, fe…
## $ edad          <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ num_suic      <dbl> 21, 113, 193, 256, 188, 117, 45, 3, 31, 12, 12, 10, 13, …
## $ poblacion     <dbl> 123400, 1015200, 2323700, 3190200, 2451100, 3140700, 227…
## $ suic_x100k    <dbl> 17.02, 11.13, 8.31, 8.02, 7.67, 3.73, 1.98, 1.72, 1.29, …
## $ idh_anio      <dbl> 0.573, 0.573, 0.573, 0.573, 0.573, 0.573, 0.573, 0.573, …
## $ pib_anio      <dbl> 34894411352, 34894411352, 34894411352, 34894411352, 3489…
## $ pib_pcap      <dbl> 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 13…
## $ generacion    <fct> NA, NA, Boomers, NA, Silent, NA, Boomers, NA, Silent, NA…
## $ pib_anio_mill <dbl> 34894.41, 34894.41, 34894.41, 34894.41, 34894.41, 34894.…
master_col %>% summary
##        pais          anio          sexo       edad        num_suic     
##  Colombia:372   Min.   :1985   female:186   NA's:372   Min.   :  0.00  
##                 1st Qu.:1992   male  :186              1st Qu.: 28.75  
##                 Median :2000                           Median : 79.50  
##                 Mean   :2000                           Mean   :142.69  
##                 3rd Qu.:2008                           3rd Qu.:221.25  
##                 Max.   :2015                           Max.   :629.00  
##                                                                        
##    poblacion         suic_x100k        idh_anio         pib_anio        
##  Min.   : 123400   Min.   : 0.000   Min.   :0.5730   Min.   :3.489e+10  
##  1st Qu.:1619744   1st Qu.: 1.087   1st Qu.:0.6290   1st Qu.:4.928e+10  
##  Median :3390041   Median : 2.855   Median :0.6925   Median :9.820e+10  
##  Mean   :2978339   Mean   : 5.402   Mean   :0.6703   Mean   :1.445e+11  
##  3rd Qu.:4092334   3rd Qu.: 9.200   3rd Qu.:0.7150   3rd Qu.:2.338e+11  
##  Max.   :6566323   Max.   :21.910   Max.   :0.7200   Max.   :3.802e+11  
##                                     NA's   :252                         
##     pib_pcap         generacion  pib_anio_mill   
##  Min.   :1299   Silent    : 82   Min.   : 34894  
##  1st Qu.:1664   Boomers   : 68   1st Qu.: 49280  
##  Median :2796   Millenials: 72   Median : 98204  
##  Mean   :3709   NA's      :150   Mean   :144464  
##  3rd Qu.:5643                    3rd Qu.:233822  
##  Max.   :8731                    Max.   :380192  
## 

Se pueden obtener resumenes

resum_col_1<-master_col %>%group_by(anio) %>%
 summarise(pib_anio_mill=mean(pib_anio_mill),
 suic_x100k=mean(suic_x100k))
 resum_col_1 %<>%select(anio,pib_anio_mill,suic_x100k)
 resum_col_1

7.16 Introducción a ggplot2

El paquete ggplot2 es una de las herramientas mÔs poderosas y flexibles en R para la visualización de datos. Desarrollado por Hadley Wickham, ggplot2 se basa en la gramÔtica de grÔficos (Grammar of Graphics), un enfoque que proporciona un marco coherente para construir visualizaciones.

7.16.1 CaracterĆ­sticas principales

  • Sistema de capas: ggplot2 permite construir grĆ”ficos a travĆ©s de capas. Puedes empezar con datos bĆ”sicos y agregar capas adicionales para personalizar la apariencia, como lĆ­neas de tendencia, etiquetas, tĆ­tulos y temas.

  • Altamente personalizable: Cada aspecto de un grĆ”fico en ggplot2 puede ser personalizado. Desde los colores y formas de los puntos hasta los temas y leyendas, el paquete ofrece un control detallado sobre cómo se presentan los datos.

  • Facilidad de uso con dplyr y tidyr: ggplot2 se integra perfectamente con otros paquetes del ecosistema tidyverse, como dplyr y tidyr, lo que facilita la manipulación y preparación de los datos antes de visualizarlos.

library(ggplot2)
ggplot(resum_col_1,aes(pib_anio_mill,suic_x100k))+
 geom_point(col="blue",cex=2)

ggplot(resum_col_1,aes(anio,suic_x100k))+
 geom_line(col="red")+
  labs(title = "Evolución de la Tasa de Suicidios por 100.000 Habitantes en Colombia")

 ggplot(resum_col_1,aes(anio,pib_anio_mill))+
 geom_line(col="black") +
  labs(title = "Evolución de la Tasa de Suicidios por 100.000 Habitantes en Colombia")

7.17 GrƔficos en varios paneles del resumen 1 con ggplot

Para considerar varios paneles, utilizamos la función ggarrange del paquete ggpubr. Antes de usarla, debemos definir los grÔficos que queremos ubicar en dichos paneles:

fig_1 <- ggplot(resum_col_1, aes(pib_anio_mill, suic_x100k)) +
  geom_point(col = "blue", cex = 2)

fig_2 <- ggplot(resum_col_1, aes(anio, pib_anio_mill)) +
  geom_line(col = "black")

fig_3 <- ggplot(resum_col_1, aes(anio, suic_x100k)) +
  geom_line(col = "red")
 require(ggpubr)
## Cargando paquete requerido: ggpubr
 ggarrange(fig_1,fig_2,fig_3,labels= c("A","B","C"),
 ncol=2,nrow=2)

7.17.1 Resumen 2

 resum_col_2<-master_col %>%group_by(anio,sexo) %>%
 summarise(pib_anio_mill=mean(pib_anio_mill),
 suic_x100k=mean(suic_x100k))
## `summarise()` has grouped output by 'anio'. You can override using the
## `.groups` argument.
 resum_col_2 %<>%select(sexo,anio,pib_anio_mill,
 suic_x100k)
 resum_col_2
ggplot(resum_col_2, aes(x = anio, y = suic_x100k, color = sexo)) +
  geom_point(cex = 3) +
  labs(
    title = "Tasa de Suicidios por 100.000 Habitantes en Colombia según Género",
    x = "AƱo",
    y = "Tasa de Suicidios por 100.000 Habitantes",
    color = "GƩnero"
  )

ggplot(resum_col_2, aes(x = anio, y = suic_x100k, color = sexo)) +
  geom_line(cex = 1) +
  labs(
    title = "Evolución de la Tasa de Suicidios por 100.000 Habitantes en Colombia según Género",
    x = "AƱo",
    y = "Tasa de Suicidios por 100.000 Habitantes",
    color = "GƩnero"
  )

### Resumen 3

resum_col_3<-master_col %>%group_by(generacion,sexo) %>%
  summarise(num_suic=sum(num_suic))
## `summarise()` has grouped output by 'generacion'. You can override using the
## `.groups` argument.
 resum_col_3 %<>%select(generacion,sexo,num_suic)
 resum_col_3
ggplot(resum_col_3, aes(x = generacion, y = num_suic, fill = sexo)) +
  geom_bar(stat = "identity") +
  geom_text(aes(label = num_suic), 
            position = position_stack(vjust = 0.5), 
            color = "white") +
  labs(
    title = "Número de Suicidios por Generación y Género en Colombia",
    x = "Generación",
    y = "NĆŗmero de Suicidios",
    fill = "GƩnero"
  )

 ggplot(resum_col_3,aes(x=generacion,y=num_suic,fill=sexo))+
 geom_bar(stat="identity",position="dodge")+
 geom_text(aes(label=num_suic),
 position=position_dodge(width=0.9),vjust=-0.25) +  labs(
    title = "Número de Suicidios por Generación y Género en Colombia",
    x = "Generación",
    y = "NĆŗmero de Suicidios",
    fill = "GƩnero"
  )

fig_1<-ggplot(resum_col_3,aes(x=generacion,y=num_suic,
 fill=sexo))+ geom_bar(stat="identity")
 fig_1+facet_grid(rows =vars(sexo)) + labs(
    title = "Número de Suicidios por Generación y Género en Colombia",
    x = "Generación",
    y = "NĆŗmero de Suicidios",
    fill = "GƩnero"
  )

7.18 GrÔficos descriptivos contrastando variables categóricas.

Un tipo de grÔfico que permite relacionar dos variables categóricas se obtiene con la función PlotXTabs del paquete CGPfunctions:

library(CGPfunctions)

if (exists("master_col") && is.data.frame(master_col)) {
  PlotXTabs(master_col, "sexo", "generacion")
} else {
  print("El objeto 'master_col' no existe o no es un data.frame")
}
require(CGPfunctions)
#PlotXTabs(master_col, sexo, generacion)
#PlotXTabs(master_col, x = "sexo", fill = "generacion")
PlotXTabs(master_col, "sexo", "generacion")

7.18.1 Resumen 5

`

 resum_col_5<-master_col %>%group_by(anio,sexo) %>%
  summarise(suic_x100k=mean(suic_x100k),
 pib_anio=mean(pib_anio),
 pib_anio_mill=mean(pib_anio_mill),
 num_suic=sum(num_suic),
 poblacion=sum(poblacion))
## `summarise()` has grouped output by 'anio'. You can override using the
## `.groups` argument.
 resum_col_5 %<>%select(anio,sexo,suic_x100k,pib_anio,
 pib_anio_mill,num_suic,poblacion)
 resum_col_5
require(GGally)
## Cargando paquete requerido: GGally
## Registered S3 method overwritten by 'GGally':
##   method from   
##   +.gg   ggplot2
 resum_col_5 %>%ggpairs(mapping=aes(col=sexo,alpha=0.4))
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

fig_1<-ggplot(resum_col_5,aes(x=anio,y=poblacion,
 col=sexo,fill=sexo))+
 geom_point()+
 geom_smooth(method ="lm")


 fig_2<-ggplot(resum_col_5,aes(x=anio,y=pib_anio_mill,
 col=sexo,fill=sexo))+
 geom_point()+
 geom_smooth(method ="lm")
require(ggpubr)
 ggarrange(fig_1,fig_2,labels=c("A","B"),ncol=1,nrow=2)
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'

ggplot(resum_col_5, aes(x = anio, y = pib_anio_mill, col = sexo, fill = sexo)) +
  geom_boxplot(color = "blue") +
  labs(
    title = "Distribución del PIB por Año y Sexo en Colombia",
    x = "AƱo",
    y = "PIB (en millones)",
    fill = "GƩnero",
    col = "GƩnero"
  )

# Agrupar los datos por aƱo y sexo, y calcular los valores mƭnimos y mƔximos de cada grupo
min1 <- resum_col_5 %>% group_by(anio, sexo) %>% summarise(min_suic_x100k = min(suic_x100k))
## `summarise()` has grouped output by 'anio'. You can override using the
## `.groups` argument.
BD_min1 <- filter(resum_col_5, suic_x100k %in% min1$min_suic_x100k)

max1 <- resum_col_5 %>% group_by(anio, sexo) %>% summarise(max_suic_x100k = max(suic_x100k))
## `summarise()` has grouped output by 'anio'. You can override using the
## `.groups` argument.
BD_max1 <- filter(resum_col_5, suic_x100k %in% max1$max_suic_x100k)

# Crear una tabla con los valores mƭnimos y mƔximos
Tabla1 <- data.frame(
  Anio = BD_min1$anio,
  Sexo = BD_min1$sexo,
  Minimo_suic_x100k = BD_min1$suic_x100k,
  Maximo_suic_x100k = BD_max1$suic_x100k,
  row.names = NULL
)
# Requerir knitr y mostrar la tabla
require(knitr)
kable(Tabla1[1:9,])
Anio Sexo Minimo_suic_x100k Maximo_suic_x100k
1985 female 1.676667 1.676667
1985 male 8.750000 8.750000
1986 female 1.381667 1.381667
1986 male 6.970000 6.970000
1987 female 1.220000 1.220000
1987 male 6.308333 6.308333
1988 female 1.271667 1.271667
1988 male 7.168333 7.168333
1989 female 1.175000 1.175000

8 AnƔlisis con datos faltantes

  • Los datos faltantes es uno de los temas que se ignoran en la mayorĆ­a de los textos introductorios. Probablemente, parte de la razón por la que esto es asĆ­ es que todavĆ­a abundan muchos mitos sobre el anĆ”lisis con datos faltantes. AdemĆ”s, algunas de las investigaciones sobre tĆ©cnicas de vanguardia es aĆŗn relativamente nueva. Una razón mĆ”s legĆ­tima para su ausencia en los textos introductorios es que la mayorĆ­a de las metodologĆ­as son bastante complicadas, desde el punto de vista matemĆ”tico. Sin embargo, la increĆ­ble ubicuidad de los problemas relacionados con los datos faltantes en el anĆ”lisis de datos de la vida real requiere que se aborde el tema.

  • Hay tratamientos para los datos que faltan, pero no hay tratamientos para los datos malos o incorrectos. El tratamiento estĆ”ndar para el problema de los datos que faltan es reemplazar los datos que faltan por valores no ausentes. Este proceso se denomina imputación. En la mayorĆ­a de los casos, el objetivo de la imputación no es recrear el conjunto de datos faltantes completo, sino permitir que se realicen estimaciones o inferencias.

  • Por ello, la eficacia de las diferentes tĆ©cnicas de imputación no puede evaluarse por su capacidad de recrear los datos con la mayor exactitud posible a partir de un conjunto de datos faltantes simulado, sino que deben juzgarse por su capacidad de apoyar las mismas inferencias estadĆ­sticas que se obtendrĆ­an del anĆ”lisis de los datos completos que se extraen del anĆ”lisis.

  • De este modo, rellenar los datos que faltan es sólo un paso hacia el verdadero objetivo: el anĆ”lisis. El conjunto de datos imputados rara vez se considera el objetivo final de la imputación. En la prĆ”ctica, hay muchas formas diferentes de tratar los datos que faltan, algunas son buenas y otras no tanto. Algunas estĆ”n bien en determinadas circunstancias, pero no en otras. Algunas implican la eliminación de datos faltantes, mientras que otras implican la imputación. Vamos a mencionar brevemente algunos de los mĆ©todos mĆ”s comunes. Sin embargo, el objetivo final de esta sección, es iniciarle en lo que a menudo se describe como el estĆ”ndar de oro de las tĆ©cnicas de imputación: la imputación mĆŗltiple.

8.0.1 Tipos de datos faltantes

El paquete VIM nos permite visualizar los patrones de datos faltantes. Un tƩrmino clave relacionado con los datos faltantes es el mecanismo de datos faltantes, que describe el proceso que determina la probabilidad de que cada punto de datos sea faltante. Existen tres categorƭas principales de mecanismos de datos faltantes:

  1. Faltantes completamente al azar (MCAR - Missing Completely At Random):
    • La falta de datos no estĆ” relacionada con los datos. Por ejemplo, si se borraran filas de una base de datos al azar o si se perdieran formularios de una encuesta debido a un evento aleatorio. Este es el mecanismo mĆ”s fĆ”cil de tratar, pero rara vez es sostenible en la prĆ”ctica.
  2. Faltantes al azar (MAR - Missing At Random):
    • La ausencia de datos estĆ” relacionada con otras variables observadas, pero no con la variable de interĆ©s en sĆ­ misma. Por ejemplo, si los hombres son menos propensos a completar una encuesta sobre depresión debido a factores relacionados con la masculinidad, esto serĆ­a un caso de MAR.
  3. Faltantes no al azar (MNAR - Missing Not At Random):
    • La falta de datos depende del valor faltante en sĆ­. Por ejemplo, si las personas con altos ingresos son mĆ”s propensas a no reportar su salario en una encuesta, se tratarĆ­a de un caso de MNAR. En estos casos, el modelo de datos faltantes debe especificarse cuidadosamente para evitar sesgos.

8.0.2 Visualizar los Datos Faltantes

Para mĆ”s información, consulta el artĆ­culo ā€œExploring Incomplete Data Using Visualization Techniquesā€. El artĆ­culo describe tĆ©cnicas como grĆ”ficos de agregación, diagramas de caja marginales, grĆ”ficos de mosaico, diagramas de dispersión con codificación de valores faltantes y Parallel boxplots para explorar datos incompletos.

  • Tomando el conjunto de datos airquality, un conjunto de datos de mediciones diarias de la calidad del aire en Nueva York de mayo a septiembre de 1973, que tiene valores NA dentro de sus variables. Las filas del conjunto de datos representan 154 dĆ­as consecutivos. Cualquier eliminación de estas filas afectarĆ” a la continuidad del tiempo, lo que puede afectar a cualquier anĆ”lisis de series temporales que se realice. Veamos con mĆ”s detalle el conjunto de datos de la calidad del aire.

  • Iniciamos visualizando los datos faltantes. Eliminaremos algunos puntos de datos del conjunto de datos para este ejemplo. En lo que respecta a las variables categóricas, la sustitución de las mismas no suele ser aconsejable. Algunas prĆ”cticas comunes incluyen la sustitución de las variables categóricas que faltan por la moda de las observadas, sin embargo, es cuestionable si es una buena opción. Aunque en este caso no faltan puntos de datos de las variables categóricas, las eliminamos de nuestro conjunto de datos (podemos volver a aƱadirlas mĆ”s tarde si es necesario) y echamos un vistazo a los datos utilizando summary().

library(VIM)
## Cargando paquete requerido: colorspace
## Cargando paquete requerido: grid
## VIM is ready to use.
## Suggestions and bug-reports can be submitted at: https://github.com/statistikat/VIM/issues
## 
## Adjuntando el paquete: 'VIM'
## The following object is masked from 'package:datasets':
## 
##     sleep
data("airquality")


knitr::kable(head(airquality), 
             caption = "Primeras 6 filas del conjunto de datos 'airquality'", 
             col.names = c("Ozono", "Radiación Solar", "Viento", "Temperatura", "Mes", "Día"), 
             align = 'c')
Primeras 6 filas del conjunto de datos ā€˜airquality’
Ozono Radiación Solar Viento Temperatura Mes Día
41 190 7.4 67 5 1
36 118 8.0 72 5 2
12 149 12.6 74 5 3
18 313 11.5 62 5 4
NA NA 14.3 56 5 5
28 NA 14.9 66 5 6
aggr(airquality, numbers = TRUE, col = c("navyblue", "red"), 
     cex.axis = 0.7, gap = 3, ylab = c("Proportion of missingness", "Missingness pattern"))

library(mice)
## 
## Adjuntando el paquete: 'mice'
## The following object is masked from 'package:stats':
## 
##     filter
## The following objects are masked from 'package:base':
## 
##     cbind, rbind
md.pattern(airquality)

##     Wind Temp Month Day Solar.R Ozone   
## 111    1    1     1   1       1     1  0
## 35     1    1     1   1       1     0  1
## 5      1    1     1   1       0     1  1
## 2      1    1     1   1       0     0  2
##        0    0     0   0       7    37 44
library(naniar)
gg_miss_var(airquality, show_pct = TRUE)

library(Amelia)
missmap(airquality, col = c("red", "blue"), legend = TRUE)

library(VIM)

data("airquality")
marginplot(airquality[, c("Ozone", "Solar.R")], 
           col = c("darkgray", "red", "blue"), 
           cex.numbers = 1.3, pch = 19, 
           cex.lab = 1.5, cex.main = 1.5)

El anterior grÔfico muestra la relación entre las variables Ozone y Solar.R en el conjunto de datos airquality, destacando que la mayoría de los puntos se concentran en valores bajos de Ozone, con una amplia variabilidad en Solar.R. Los boxplots en los ejes indican la distribución de cada variable, revelando la presencia de varios valores atípicos (marcados en rojo) en ambas variables, especialmente en Ozone, donde se observa un grupo significativo de outliers en valores bajos. AdemÔs, se identifican valores faltantes en Solar.R (2) y Ozone (37), lo que podría influir en la interpretación de la relación entre estas dos variables.

8.0.3 TƩcnicas para Tratar estos Datos.

Para abordar problemas de datos faltantes en estadística, existen diversas metodologías que pueden ser aplicadas dependiendo del tipo de datos y del mecanismo de falta. A continuación se mencionan algunas de las principales técnicas:

8.0.3.1 Borrado de la lista

El método mÔs utilizado por los científicos de datos para tratar los datos que faltan es simplemente omitir los casos con datos que faltan, analizando únicamente el resto del conjunto de datos. Este método se conoce como eliminación por lista o anÔlisis de casos completos. En R, la función na.omit() elimina todos los casos con uno o mÔs valores faltantes en un conjunto de datos.

8.0.3.2 Ventajas y Desventajas:

  • Ventaja: La mayor ventaja de este mĆ©todo es su comodidad.
  • Desventaja: Si los datos no son MCAR, la eliminación de casos puede llevar a resultados sesgados y a la disminución de la capacidad para detectar el verdadero efecto de las variables de interĆ©s. AdemĆ”s, puede influir significativamente en las estimaciones como las medias, los coeficientes de regresión y las correlaciones.

Ejemplo en R:

A continuación, se muestra un ejemplo simple utilizando la función na.omit() para eliminar los casos que contienen valores faltantes (NAs):

data("airquality")
airquality_omit <- na.omit(airquality)
head(airquality_omit)
  • Dibujemos los histogramas antes y despuĆ©s de la imputación/eliminación usando ggplot
suppressWarnings({
  library(ggplot2)
  library(tidyverse)
  library(hrbrthemes)
  library(gridExtra)
  library(missForest)
})
## 
## Adjuntando el paquete: 'gridExtra'
## The following object is masked from 'package:dplyr':
## 
##     combine
## 
## Adjuntando el paquete: 'missForest'
## The following object is masked from 'package:VIM':
## 
##     nrmse
ggp1 <- ggplot(data.frame(value=airquality$Ozone), aes(x=value)) +
  geom_histogram(fill="#FD0000", color="#E52521", alpha=0.9) +
  ggtitle("Original data") +
  xlab("Ozone") + ylab("Frequency") +
  theme_ipsum() +
  theme(plot.title = element_text(size=15))

ggp2 <- ggplot(data.frame(value=airquality_omit$Ozone), aes(x=value)) +
  geom_histogram(fill="#43B047", color="#049DCB", alpha=0.9) +
  ggtitle("Listwise Deletion") +
  xlab("Ozone") + ylab("Frequency") +
  theme_ipsum() +
  theme(plot.title = element_text(size=15))

grid.arrange(ggp1, ggp2, ncol = 2)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

8.0.3.3 La mƔs comunes dentro del paquete mice.

  • pmm (Predictive Mean Matching): Es el mĆ©todo mĆ”s utilizado para variables numĆ©ricas. Busca valores observados similares a las predicciones del modelo y los usa para imputar los valores faltantes.

  • logreg (Logistic Regression): Utiliza la regresión logĆ­stica para imputar valores en variables categóricas binarias (sĆ­/no).

  • polyreg (Polynomial Regression): Emplea la regresión polinómica para imputar variables categóricas con mĆŗltiples niveles.

  • norm (Normal Linear Regression): Usa regresión lineal asumiendo una distribución normal de la variable para imputar valores numĆ©ricos.

  • mean (Media): Imputa el valor faltante utilizando la media de la variable. Es un mĆ©todo simple pero no siempre recomendado, ya que puede subestimar la variabilidad de los datos.

  • cart (Classification and Regression Trees): Usa Ć”rboles de decisión para imputar valores faltantes. Este mĆ©todo puede ser Ćŗtil para datos complejos con relaciones no lineales.

8.0.3.4 Imputación por emparejamiento predictivo medio

  • El mĆ©todo de imputación method='pmm' (Predictive Mean Matching) reemplaza los valores faltantes con valores observados que son similares en tĆ©rminos de predicción, manteniendo la coherencia y la distribución original de los datos. Este enfoque es especialmente Ćŗtil para evitar imputaciones irreales y preservar las caracterĆ­sticas estadĆ­sticas del conjunto de datos.

  • Algunos cientĆ­ficos de datos o estadĆ­sticos pueden buscar una solución rĆ”pida sustituyendo los datos que faltan por la media. La media se utiliza a menudo para imputar datos no categóricos. Por ejemplo, en el conjunto de datos de calidad del aire, supongamos que queremos imputar la media de sus valores faltantes. En este caso, utilizamos el paquete R mice. Cambiando el argumento method = mean, se especifica la imputación de la media, el argumento m = 1 cambia las iteraciones a 1, lo que significa no (iteración).

  • El fundamento teórico de utilizar la media para imputar los datos faltantes es que la media es una buena estimación para seleccionar aleatoriamente una observación de una distribución normal. Ahora, intentaremos imputar la media para las variables Ozono y Solar.R del conjunto de datos airquality. En primer lugar, vamos a cargar los paquetes mice y mipackages (instale el paquete de CRAN primero usando install.packages("mice") y install.packages("mi")). Para mĆ”s información sobre el paquete utilizado. Podemos recuperar el conjunto de datos completo utilizando la función complete().

suppressWarnings({
  library(mice)
  library(foreign)
})
  • Los datos faltantes para Ozono y Solar.R pueden ser imputados por la función mice().
imp <- mice(airquality, m=5, maxit=50, method ='pmm', seed=500, printFlag = FALSE)

El parÔmetro m se refiere al número de conjuntos de datos imputados que se generarÔn. Es decir, m representa la cantidad de veces que se imputarÔn los datos para crear múltiples conjuntos de datos imputados. Cada conjunto de datos imputados contendrÔ valores imputados diferentes para los datos faltantes, lo que permite capturar la incertidumbre en el proceso de imputación o falta de certeza sobre los valores reales de los datos faltantes.

El parÔmetro maxit en el paquete mice de R controla cuÔntas veces se repetirÔ el proceso de imputación en cada conjunto de datos para alcanzar una convergencia o un límite mÔximo de iteraciones. Durante cada iteración, el algoritmo intenta mejorar la imputación de los valores faltantes. Si el número de iteraciones alcanza el valor especificado en maxit y el proceso no converge, la imputación se detendrÔ y se generarÔ un mensaje de advertencia. Ajustar adecuadamente el valor de maxit puede ayudar a controlar el tiempo de ejecución y garantizar una convergencia adecuada del algoritmo de imputación.

El valor por defecto es m=5. method='pmm'se refiere al método de imputación. En este caso, utilizamos el método de imputación por emparejamiento predictivo medio (PMM). Se pueden utilizar otros métodos de imputación, escriba methods(mice) para obtener una lista de los métodos de imputación disponibles.

suppressWarnings(methods(mice))
##  [1] mice.impute.2l.bin              mice.impute.2l.lmer            
##  [3] mice.impute.2l.norm             mice.impute.2l.pan             
##  [5] mice.impute.2lonly.mean         mice.impute.2lonly.norm        
##  [7] mice.impute.2lonly.pmm          mice.impute.cart               
##  [9] mice.impute.jomoImpute          mice.impute.lasso.logreg       
## [11] mice.impute.lasso.norm          mice.impute.lasso.select.logreg
## [13] mice.impute.lasso.select.norm   mice.impute.lda                
## [15] mice.impute.logreg              mice.impute.logreg.boot        
## [17] mice.impute.mean                mice.impute.midastouch         
## [19] mice.impute.mnar.logreg         mice.impute.mnar.norm          
## [21] mice.impute.mpmm                mice.impute.norm               
## [23] mice.impute.norm.boot           mice.impute.norm.nob           
## [25] mice.impute.norm.predict        mice.impute.panImpute          
## [27] mice.impute.passive             mice.impute.pmm                
## [29] mice.impute.polr                mice.impute.polyreg            
## [31] mice.impute.quadratic           mice.impute.rf                 
## [33] mice.impute.ri                  mice.impute.sample             
## [35] mice.mids                       mice.theme                     
## see '?methods' for accessing help and source code
imp_df <- complete(imp)
head(imp_df)

La siguiente imagen ilustra el funcionamiento del método de imputación múltiple por ecuaciones encadenadas.

set.seed(123)
data <- data.frame(
  A = c(0.93, 0.24, 0.95, 0.23, 0.90, 0.15, 0.47, 0.89),
  B = c(1.40, 0.46, 1.24, 0.57, 1.28, 0.42, 0.54, 1.23),
  C = c(1.53, 0.76, 1.46, 1.28, 1.28, 1.53, 0.63, 1.45)
)

# Step 2: Introduce missing data in A
data_missing <- data
data_missing[c(2, 5, 7), "A"] <- NA

# Step 3: Original correlation
plot1 <- ggplot(data, aes(x = B, y = A)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE, color = "blue") +
  ggtitle("Original \nR^2 = 0.9345") +
  theme_minimal() +
  theme(plot.title = element_text(size = 10, hjust = 0.5), 
        plot.margin = margin(20, 20, 20, 20))

# Step 4: Impute random data
data_random <- data_missing
data_random[is.na(data_random$A), "A"] <- runif(sum(is.na(data_missing$A)), min = min(data$A), max = max(data$A))

plot2 <- ggplot(data_random, aes(x = B, y = A)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE, color = "blue") +
  ggtitle("Random Imput\nR^2 = 0.4106") +
  theme_minimal() +
  theme(plot.title = element_text(size = 10, hjust = 0.5), 
        plot.margin = margin(20, 20, 20, 20))

# Step 5: Impute Random Forest
data_rf <- suppressWarnings(missForest(data_missing)$ximp)

plot3 <- ggplot(data_rf, aes(x = B, y = A)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE, color = "blue") +
  ggtitle("Rand. F Imput\nR^2 = 0.5311") +
  theme_minimal() +
  theme(plot.title = element_text(size = 10, hjust = 0.5), 
        plot.margin = margin(20, 20, 20, 20))

# Step 6: Impute B using Random Forest and re-calculate correlation
data_rf2 <- suppressWarnings(missForest(data_rf)$ximp)

plot4 <- ggplot(data_rf2, aes(x = B, y = A)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE, color = "blue") +
  ggtitle("Ran. F (A & B Imputed)\nR^2 = 0.8771") +
  theme_minimal() +
  theme(plot.title = element_text(size = 10, hjust = 0.5), 
        plot.margin = margin(20, 20, 20, 20))

# Arrange plots in a grid with increased spacing
grid.arrange(plot1, plot2, plot3, plot4, ncol = 4, widths = c(0.5, 0.5, 0.5, 0.5))
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'

AdemÔs, la siguiente figura ilustra el funcionamiento del método de imputación por emparejamiento predictivo medio usado en el ejemplo para airquality.

  • Observación: El mĆ©todo PMM utiliza aleatoriedad en la imputación para introducir variabilidad en las estimaciones de los valores faltantes. Esto ayuda a evitar la sobreestimación o subestimación sistemĆ”tica de los valores imputados y a capturar la incertidumbre asociada con la imputación de datos faltantes. La introducción de aleatoriedad tambiĆ©n puede ayudar a mejorar la robustez y generalización del modelo de imputación, especialmente en situaciones donde los datos faltantes siguen patrones complejos o no aleatorios.

  • Tracemos un histograma y un diagrama de dispersión del aspecto del conjunto de datos de la calidad del aire tras la imputación mediante el mĆ©todo PMM. Los siguientes son los histogramas asociados:

ggp1 <- ggplot(data.frame(value=airquality$Ozone), aes(x=value)) +
  geom_histogram(fill="#FBD000", color="#E52521", alpha=0.9) +
  ggtitle("Original data") +
  xlab('Ozone') + ylab('Frequency') +
  theme_ipsum() +
  theme(plot.title = element_text(size=15))

ggp2 <- ggplot(data.frame(value=imp_df$Ozone), aes(x=value)) +
  geom_histogram(fill="#43B047", color="#049CD8", alpha=0.9) +
  ggtitle("PMM imputation") +
  xlab('Ozone') + ylab('Frequency') +
  theme_ipsum() +
  theme(plot.title = element_text(size=15))

grid.arrange(ggp1, ggp2, ncol = 2)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

  • Vamos a comparar las distribuciones de los datos originales e imputados utilizando algunos grĆ”ficos Ćŗtiles. En primer lugar, podemos utilizar un grĆ”fico de dispersión y comparar Ozone con todas las demĆ”s variables.
library(lattice)
xyplot(imp, Ozone ~ Wind + Temp + Solar.R, pch = 18, cex = 1)

  • Lo que queremos ver es que la forma de los puntos rojos (imputados) coincida con la de los azules (observados). La coincidencia de la forma nos indica que los valores imputados son realmente valores plausibles. Otro grĆ”fico Ćŗtil es el de la densidad:
densityplot(imp)

  • La densidad de los datos imputados para cada conjunto de datos imputados se muestra en color rojo, mientras que la densidad de los datos observados se muestra en azul. De nuevo, segĆŗn nuestros supuestos anteriores, esperamos que las distribuciones sean similares.
  • Es muy recomendable comprobar visualmente la convergencia. Lo comprobamos cuando llamamos a la función de trazado a la que asignamos la salida de mice, para mostrar grĆ”ficos de seguimiento de la media y desviación estĆ”ndar de todas las variables implicadas en las imputaciones.
plot(imp);

  • Cada lĆ­nea es una de las m conjuntos de datos imputados, a saber m = 5 por defecto. Como se puede ver en el grĆ”fico de trazado anterior sobre imp, no hay tendencias claras y las variables se superponen de una iteración a la siguiente. Dicho de otro modo, la varianza dentro de una cadena (hay m cadenas) deberĆ­a ser aproximadamente igual a la varianza entre las cadenas. Esto indica que se ha logrado la convergencia. Si no se logra la convergencia, se puede aumentar el nĆŗmero de iteraciones que mice emplea especificando explĆ­citamente el parĆ”metro maxit a la función mice.

9 Otras técnicas de imputación

  • La función mice entrega distintos mĆ©todos de imputación los cuales puede estudiar y aplicar, de acuerdo a lo que requieran sus datos. Puede aplicar la imputación por regresión en R con la configuración del mĆ©todo method = "norm.predict" en la función mice. Puede aplicar la imputación de regresión estocĆ”stica en R con la función mice utilizando el mĆ©todo method = "norm.nob".

  • El paquete mice tambiĆ©n incluye un procedimiento de imputación de regresión estocĆ”stica Bayesiana. Puede aplicar este procedimiento de imputación con la función mice y utilizar como mĆ©todo method = "norm". Para estos mĆ©todos debe seleccionar primero dos columnas del conjunto de datos de interĆ©s para ajustar constantes del modelo.

data <- airquality[, c("Solar.R", "Wind")]
imp.regress <- mice(airquality, method="norm.predict", m=1, maxit=1)
## 
##  iter imp variable
##   1   1  Ozone  Solar.R
imp.regress$imp$Wind
  • En la actualidad, hay un nĆŗmero limitado de anĆ”lisis que pueden ser automĆ”ticamente agrupados por mice, el mĆ”s importante es el de lm/glm. Sin embargo, si se recuerda, el modelo lineal generalizado es extremadamente flexible, y puede utilizarse para expresar una amplia gama de anĆ”lisis diferentes. Por extensión, podrĆ­amos utilizar la imputación mĆŗltiple no sólo para regresión lineal, sino para la regresión logĆ­stica, la regresión de Poisson, las pruebas t, el ANOVA, ANCOVA, etc. Cada una de estas temĆ”ticas pueden ser abordadas como proyectos finales en este curso.

9.0.1 Otras Técnicas de Imputación de Datos.

  1. Imputación MĆŗltiple (Multiple Imputation - MI): Esta tĆ©cnica es ampliamente utilizada cuando los datos faltantes se consideran como ā€œMissing At Randomā€ (MAR). MI genera varios conjuntos de datos completos imputando valores plausibles para los datos faltantes. Luego, se analizan cada uno de estos conjuntos y los resultados se combinan para obtener estimaciones finales que tienen en cuenta la incertidumbre asociada a los valores imputados Fuente.

  2. MÔxima Verosimilitud de Información Completa (FIML - Full Information Maximum Likelihood): FIML es un método que permite estimar parÔmetros directamente a partir de un modelo de verosimilitud completa sin necesidad de imputar valores faltantes. Funciona bien bajo las condiciones MAR y es preferido en modelos de ecuaciones estructurales y anÔlisis de regresión Fuente.

  3. Algoritmo de Maximización de la Expectativa (EM - Expectation-Maximization): El algoritmo EM es utilizado para estimar parÔmetros en presencia de datos faltantes asumiendo una estructura de modelo subyacente. Alterna entre estimar las probabilidades de los datos faltantes y maximizar la función de verosimilitud, ofreciendo una solución iterativa a la imputación de datos Fuente.

  4. Métodos de Eliminación: Los métodos de eliminación, como la eliminación lista (listwise deletion) o eliminación por pares (pairwise deletion), son mÔs simples pero pueden introducir sesgos si los datos no son faltantes completamente al azar (MCAR). Estos métodos eliminan las observaciones con valores faltantes, lo que puede reducir la potencia estadística y sesgar los resultados Fuente.

10 Reglas generales para imputación

10.1 Menos del 5% de datos faltantes

  • En general, no es necesario imputar, ya que la cantidad es pequeƱa y eliminar los registros con valores faltantes no afectarĆ” significativamente el anĆ”lisis.

10.2 Entre 5% y 20% de datos faltantes

  • Se recomienda imputación simple, como:
    • Media o mediana (para datos numĆ©ricos).
    • Moda o categorĆ­a mĆ”s frecuente (para datos categóricos).
    • Regresión.

10.3 Entre 20% y 40% de datos faltantes

  • Se recomienda imputación avanzada, como:
    • k-Nearest Neighbors (KNN).
    • Regresión mĆŗltiple.
    • Modelos basados en Ć”rboles de decisión.
    • MICE (Multiple Imputation by Chained Equations).

10.4 MƔs del 40% de datos faltantes

Precaución: - La imputación puede introducir sesgos significativos. - Se recomienda evaluar si la variable sigue siendo útil. - Si la variable es crítica, se puede usar técnicas de modelado predictivo para estimar los valores faltantes.

11 Evaluación de la Imputación

Primero verificas la normalidad de la variable imputada (o del conjunto completo, dependiendo de tu anƔlisis).

Esto lo puedes hacer con:

  • Shapiro–Wilk
  • Kolmogórov–Smirnov (con corrección Lilliefors)
  • Pruebas grĆ”ficas: histograma, Q–Q plot

11.1 SegĆŗn el resultado de la normalidad

  • Si la distribución es normal Usas pruebas paramĆ©tricas como:
    • t de Student (para dos grupos)
    • ANOVA (para mĆ”s de dos grupos)
  • Si NO es normal Usas pruebas no paramĆ©tricas:
    • Mann–Whitney U (para dos grupos independientes)
    • Wilcoxon (para dos muestras relacionadas)
    • Kruskal–Wallis (para mĆ”s de dos grupos)

12 Detección de valores atípicos

  • Un valor atĆ­pico es un valor o una observación que se aleja de otras observaciones, es decir, un punto de datos que difiere significativamente de otros puntos de datos. Enderlein (1987) va incluso mĆ”s allĆ”, ya que el autor considera que los valores atĆ­picos son aquellos que se desvĆ­an tanto de otras observaciones que se podrĆ­a suponer un mecanismo de muestreo subyacente diferente.
  • Una observación debe compararse siempre con otras observaciones realizadas sobre el mismo fenómeno antes de calificarla realmente de atĆ­pica. En efecto, una persona de 200 cm de altura (1,90 m en EE.UU.) se considerarĆ” probablemente un valor atĆ­pico en comparación con la población general, pero esa misma persona podrĆ­a no considerarse un valor atĆ­pico si midiĆ©ramos la altura de los jugadores de baloncesto.
  • En esta sección, presentamos varios enfoques para detectar valores atĆ­picos en R, desde tĆ©cnicas sencillas como la estadĆ­stica descriptiva (que incluye el mĆ­nimo, el mĆ”ximo, el histograma, el boxplot y los percentiles) hasta tĆ©cnicas mĆ”s formales como el filtro de Hampel, el Grubbs, el Dixon y las pruebas de Rosner para detectar valores atĆ­picos. Algunas pruebas estadĆ­sticas exigen la ausencia de valores atĆ­picos para sacar conclusiones sólidas, pero la eliminación de valores atĆ­picos no se recomienda en todos los casos y debe hacerse con precaución.

12.1 EstadĆ­sticas descriptivas para conocer atĆ­picos

  • El primer paso para detectar los valores atĆ­picos en R es comenzar con algunos estadĆ­sticos descriptivos, y en particular con el mĆ­nimo y el mĆ”ximo. En R, esto se puede hacer fĆ”cilmente con la función summary(). El conjunto de datos a utilizar es mpg asociado a consumo de combustible de 1999 a 2008 de 38 modelos populares de coches (ver ggplot2::mpg).
dat <- ggplot2::mpg
head(dat)
summary(dat$hwy)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   12.00   18.00   24.00   23.44   27.00   44.00
min(dat$hwy)
## [1] 12
  • Un claro error de codificación, como un peso de 786 kg (1733 libras) para un humano, ya se detectarĆ” fĆ”cilmente con esta tĆ©cnica tan sencilla.

12.1.1 Histogramas

  • Otra forma bĆ”sica de detectar valores atĆ­picos es dibujar un histograma de los datos. Utilizando la base de R (con el nĆŗmero de bins correspondiente a la raĆ­z cuadrada del nĆŗmero de observaciones para tener mĆ”s bins que la opción por defecto), o usando ggplot2. Considere la columna hwy: highway mileage (miles per gallon).
hist(dat$hwy,
     xlab = "hwy",
     main = "Histograma de hwy",
     breaks = sqrt(nrow(dat)))

ggplot(dat) +
  aes(x = hwy) +
  geom_histogram(bins = 30L, fill = "#0c4c8a") +
  theme_minimal()

  • Con base en este histograma, se evidencia que hay un par de observaciones mĆ”s altas que todas las demĆ”s.

12.1.2 Boxplot

  • AdemĆ”s de los histogramas, los boxplots tambiĆ©n son Ćŗtiles para detectar posibles valores atĆ­picos. Considere nuevamente a manera de ejemplo la columna hwy.
boxplot(dat$hwy,
        ylab = "hwy"
)

ggplot(dat) +
  aes(x = "", y = hwy) +
  geom_boxplot(fill = "#0c4c8a") +
  theme_minimal()

  • Un diagrama de caja ayuda a visualizar una variable cuantitativa mostrando cinco resĆŗmenes de localización comunes (mĆ­nimo, mediana, primer y tercer cuartil y mĆ”ximo) y cualquier observación que se haya clasificado como presunto valor atĆ­pico utilizando el criterio del rango intercuartĆ­lico (IQR).

  • Las observaciones consideradas como posibles valores atĆ­picos segĆŗn el criterio IQR se muestran como puntos en el diagrama de caja. SegĆŗn este criterio, hay 2 valores atĆ­picos potenciales (vĆ©anse los 2 puntos por encima de la lĆ­nea vertical, en la parte superior del boxplot).

  • Recuerde que no por el hecho de que una observación sea considerada como un valor atĆ­pico potencial por el criterio IQR debe eliminarla. Eliminar o mantener un valor atĆ­pico depende de: (i) el contexto de su anĆ”lisis, (ii) si las pruebas que va a realizar en el conjunto de datos son robustas a los valores atĆ­picos o no, y (iii) a quĆ© distancia estĆ” el valor atĆ­pico de otras observaciones.

  • TambiĆ©n es posible extraer los valores de los posibles valores atĆ­picos basĆ”ndose en el criterio IQR gracias a la función boxplot.stats()$out:

boxplot.stats(dat$hwy)$out
## [1] 44 44 41
out <- boxplot.stats(dat$hwy)$out
out_ind <- which(dat$hwy %in% c(out))
out_ind
## [1] 213 222 223
  • Con esta información, ahora puede volver fĆ”cilmente a las filas especĆ­ficas del conjunto de datos para verificarlas, o imprimir todas las variables para estos valores atĆ­picos:
dat[out_ind, ]
boxplot(dat$hwy,
  ylab = "hwy",
  main = "Boxplot of highway miles per gallon"
)
mtext(paste("Outliers: ", paste(out, collapse = ", ")))

12.1.3 Percentiles

  • Este mĆ©todo de detección de valores atĆ­picos se basa en los percentiles. Con el mĆ©todo de los percentiles, todas las observaciones que se encuentren fuera del intervalo formado por los percentiles 2.5 y 97.5 se considerarĆ”n como posibles valores atĆ­picos:

\[ I_{out} = [q_{0.025} ; q_{0.975}]^C \]

  • TambiĆ©n pueden considerarse otros percentiles, como el 1 y el 99, o el 5 y el 95, para construir el intervalo. Los valores de los percentiles inferior y superior (y, por tanto, los lĆ­mites inferior y superior del intervalo) pueden calcularse con la función quantile():
lower_bound <- quantile(dat$hwy, 0.025)
lower_bound
## 2.5% 
##   14
upper_bound <- quantile(dat$hwy, 0.975)
upper_bound
##  97.5% 
## 35.175
  • SegĆŗn este mĆ©todo, todas las observaciones por debajo de 14 y por encima de 35.175 se considerarĆ”n posibles valores atĆ­picos. Los nĆŗmeros de fila de las observaciones fuera del intervalo pueden extraerse entonces con la función which():
outlier_ind <- which(dat$hwy < lower_bound | dat$hwy > upper_bound)
outlier_ind
##  [1]  55  60  66  70 106 107 127 197 213 222 223
dat[outlier_ind, "hwy"]
dat[outlier_ind, ]
  • Hay 11 valores atĆ­picos potenciales segĆŗn el mĆ©todo de los percentiles. Para reducir este nĆŗmero, puede establecer los percentiles en 1 y 99:
lower_bound <- quantile(dat$hwy, 0.01)
upper_bound <- quantile(dat$hwy, 0.99)

outlier_ind <- which(dat$hwy < lower_bound | dat$hwy > upper_bound)

dat[outlier_ind, ]

12.2 Filtro de Hampel

  • Otro mĆ©todo, conocido como filtro de Hampel, consiste en considerar como valores atĆ­picos los valores fuera del intervalo formado por la mediana, mĆ”s o menos 3 desviaciones absolutas de la mediana:

\[ I = \left[\bar{X} - 3 \cdot MAD, \, \bar{X} + 3 \cdot MAD\right], \]

donde MAD es la desviación absoluta de la mediana y se define como la mediana de las desviaciones absolutas de la mediana \(\bar{X}\) de los datos:

\[ MAD = k \times \text{median}\left(|X_i - \bar{X}|\right) \]

  • Para este mĆ©todo, primero establecemos los lĆ­mites del intervalo, gracias a las funciones median() y mad(). En este caso, k representa el factor de escala, por defecto, R lo considera como 1.
lower_bound <- median(dat$hwy) - 3 * mad(dat$hwy, constant = 1)
lower_bound
## [1] 9
upper_bound <- median(dat$hwy) + 3 * mad(dat$hwy, constant = 1)
upper_bound
## [1] 39
  • SegĆŗn este mĆ©todo, todas las observaciones por debajo de 9 y por encima de 39 se considerarĆ”n como posibles valores atĆ­picos. Los nĆŗmeros de fila de las observaciones que estĆ”n fuera del intervalo pueden entonces extraerse con la función which(). SegĆŗn el filtro de Hampel, hay 3 valores atĆ­picos para la variable hwy.
outlier_ind <- which(dat$hwy < lower_bound | dat$hwy > upper_bound)
outlier_ind
## [1] 213 222 223
dat[outlier_ind, ]

12.3 Ventajas y desventajas.

12.3.1 Filtro de Hampel

Ventajas: - Robustez: Es menos sensible a los outliers que la desviación estÔndar, ya que la mediana y la MAD son medidas robustas, lo que significa que no se ven muy afectadas por valores atípicos. - Aplicabilidad: Funciona bien en distribuciones no normales y en presencia de otros outliers.

Desventajas: - No paramétrico: Aunque es robusto, puede no ser tan eficaz si los datos tienen una distribución bien definida (por ejemplo, normal) donde las técnicas paramétricas podrían ser mÔs precisas. - Configuración: Requiere ajustar adecuadamente el umbral (generalmente 3 o 4 veces la MAD), lo cual puede ser arbitrario y depender del contexto.

12.3.2 MƩtodo de Percentiles

Ventajas: - Simplicidad: Es fÔcil de entender y aplicar, ya que simplemente se basa en la posición de los valores en la distribución. - Flexibilidad: Permite detectar outliers de manera flexible al elegir diferentes percentiles según la necesidad (por ejemplo, percentil 1 o 99 para una detección mÔs estricta).

Desventajas: - No considera la variabilidad: Este método no toma en cuenta la dispersión de los datos de la misma manera que el filtro de Hampel. Esto significa que si los datos tienen una distribución muy dispersa, los percentiles podrían no ser tan efectivos para capturar outliers verdaderos. - Dependencia de la distribución: Puede ser menos robusto en distribuciones asimétricas o con colas largas, donde los percentiles pueden no reflejar adecuadamente los valores extremos.

13 Pruebas AnƔliticas para Datos Atƭpicos

13.1 Prueba de Grubbs

  • La prueba de Grubbs permite detectar si el valor mĆ”s alto o mĆ”s bajo de un conjunto de datos es un valor atĆ­pico (ver Grubbs). La prueba de Grubbs detecta un valor atĆ­pico cada vez (valor mĆ”s alto o mĆ”s bajo), por lo que las hipótesis nula y alternativa son las siguientes:

    • \(H_0\): El valor mĆ”s alto/bajo no es un valor atĆ­pico
    • \(H_1\): El valor mĆ”s alto/bajo es un valor atĆ­pico
  • Como para cualquier prueba estadĆ­stica, si el valor p es inferior al umbral de significación elegido (generalmente \(\alpha = 0.05\)), se rechaza la hipótesis nula y se concluye que el valor mĆ”s bajo/mĆ”s alto es un valor atĆ­pico. Por el contrario, si el valor p es mayor o igual que el nivel de significación, no se rechaza la hipótesis nula y concluiremos que, basĆ”ndonos en los datos, no rechazamos la hipótesis de que el valor mĆ”s bajo/mĆ”s alto no es un valor atĆ­pico. Tenga en cuenta que la prueba de Grubbs no es apropiada para un tamaƱo de muestra n \(\leq\) 6.

  • Para realizar la prueba de Grubbs en R, utilizamos la función grubbs.test() del paquete outliers. La función recibe los siguientes argumentos: grubbs.test(x, type = 10, opposite = FALSE, two.sided = FALSE). En este caso, type:

    • 10 = prueba si el valor mĆ”ximo es un valor atĆ­pico.
    • 11 = prueba si tanto el valor mĆ­nimo como el mĆ”ximo son valores atĆ­picos.
    • 20 = prueba si hay dos valores atĆ­picos en una cola.
library(outliers)
test <- grubbs.test(dat$hwy)
test
## 
##  Grubbs test for one outlier
## 
## data:  dat$hwy
## G = 3.45274, U = 0.94862, p-value = 0.05555
## alternative hypothesis: highest value 44 is an outlier
  • El valor \(p\) es de 0.056 aproximadamente. Al nivel de significación del 5%, no rechazamos la hipótesis de que el valor mĆ”s alto 44 no es un valor atĆ­pico. No tenemos evidencia suficiente para decir que 44 es un valor atĆ­pico. Por defecto, la prueba se realiza sobre el valor mĆ”s alto (como se muestra en la salida de R: hipótesis alternativa: el valor mĆ”s alto 44 es un valor atĆ­pico). Si desea realizar la prueba para el valor mĆ”s bajo, simplemente aƱada el argumento opposite = TRUE en la función grubbs.test():
test <- grubbs.test(dat$hwy, opposite = TRUE)
test
## 
##  Grubbs test for one outlier
## 
## data:  dat$hwy
## G = 1.92122, U = 0.98409, p-value = 1
## alternative hypothesis: lowest value 12 is an outlier
  • La salida de R indica que la prueba se realiza ahora sobre el valor mĆ”s bajo (vĆ©ase la hipótesis alternativa: el valor mĆ”s bajo 12 es un valor atĆ­pico). El valor \(p\) es 1. Al nivel de significación del 5%, no rechazamos la hipótesis de que el valor mĆ”s bajo 12 no es un valor atĆ­pico.
  • A modo de ilustración, sustituiremos ahora una observación por un valor mĆ”s extremo y realizaremos la prueba de Grubbs en este nuevo conjunto de datos. Reemplacemos el 34th por un valor de 212.
dat[34, "hwy"] <- 212
test <- grubbs.test(dat$hwy)
test
## 
##  Grubbs test for one outlier
## 
## data:  dat$hwy
## G = 13.72240, U = 0.18836, p-value < 2.2e-16
## alternative hypothesis: highest value 212 is an outlier
  • El valor \(p\) es menor que la significancia. Al nivel de significancia del 5%, concluimos que el valor mĆ”s alto 212 es un valor atĆ­pico.

13.2 Prueba de Dixon

  • Al igual que la prueba de Grubbs, la prueba de Dixon se utiliza para comprobar si un Ćŗnico valor bajo o alto es un valor atĆ­pico. Por lo tanto, si se sospecha que hay mĆ”s de un valor atĆ­pico, la prueba tiene que realizarse en estos valores atĆ­picos sospechosos individualmente. Tenga en cuenta que la prueba de Dixon es mĆ”s Ćŗtil para muestras de pequeƱo tamaƱo (normalmente \(n \leq 25\)).

  • Para realizar la prueba de Dixon en R, utilizamos la función dixon.test() del paquete outliers. Sin embargo, restringimos nuestro conjunto de datos a las 20 primeras observaciones, ya que la prueba de Dixon sólo se puede realizar en muestras de pequeƱo tamaƱo (R arrojarĆ” un error y sólo acepta conjuntos de datos de 3 a 30 observaciones).

subdat <- dat[1:20, ]
test <- dixon.test(subdat$hwy)
test
## 
##  Dixon test for outliers
## 
## data:  subdat$hwy
## Q = 0.57143, p-value = 0.006508
## alternative hypothesis: lowest value 15 is an outlier
  • Los resultados muestran que el valor mĆ”s bajo, 15, es un valor atĆ­pico, con valor \(p\) menor que la significancia del 5%.

Para comprobar el valor mÔs alto, basta con añadir el argumento opuesto = TRUE a la función dixon.test().

test <- dixon.test(subdat$hwy, opposite = TRUE)
test
## 
##  Dixon test for outliers
## 
## data:  subdat$hwy
## Q = 0.25, p-value = 0.8582
## alternative hypothesis: highest value 31 is an outlier
  • Los resultados muestran que el valor mĆ”s alto 31 no es un valor atĆ­pico (valor \(p = 0.8582\)). Es una buena prĆ”ctica comprobar siempre los resultados de la prueba estadĆ­stica de valores atĆ­picos con el diagrama de caja para asegurarse de que hemos comprobado todos los valores atĆ­picos potenciales.
out <- boxplot.stats(subdat$hwy)$out
boxplot(subdat$hwy, ylab = "hwy")
mtext(paste("Outliers: ", paste(out, collapse = ", ")))

  • A partir del boxplot, vemos que tambiĆ©n podrĆ­amos aplicar la prueba de Dixon sobre el valor 20 ademĆ”s del valor 15 realizado anteriormente. Esto puede hacerse encontrando el nĆŗmero de fila del valor mĆ­nimo, excluyendo este nĆŗmero de fila del conjunto de datos y aplicando finalmente la prueba de Dixon a este nuevo conjunto de datos.
remove_ind <- which.min(subdat$hwy)
subsubdat <- subdat[-remove_ind, ]

tail(subsubdat)
test <- dixon.test(subsubdat$hwy)
test
## 
##  Dixon test for outliers
## 
## data:  subsubdat$hwy
## Q = 0.44444, p-value = 0.1297
## alternative hypothesis: lowest value 20 is an outlier

Los resultados muestran que el segundo valor mƔs bajo, 20, no es un valor atƭpico (valor p= 0.13).

13.3 Prueba de Rosner

  • La prueba de Rosner para detectar valores atĆ­picos tiene las siguientes ventajas. Se utiliza para detectar varios valores atĆ­picos a la vez (a diferencia de la prueba de Grubbs y Dixon, que debe realizarse de forma iterativa para detectar mĆŗltiples valores atĆ­picos), y estĆ” diseƱado para evitar el problema del enmascaramiento, en el que un valor atĆ­pico cercano a otro atĆ­pico puede pasar desapercibido. A diferencia de la prueba de Dixon, hay que tener en cuenta que la prueba de Rosner es mĆ”s apropiada cuando el tamaƱo de la muestra es grande (\(n \geq 20\)). Por tanto, volvemos a utilizar el conjunto de datos inicial dat, que incluye 234 observaciones.
  • Para realizar la prueba de Rosner utilizamos la función rosnerTest() del paquete EnvStats. Esta función requiere al menos 2 argumentos: los datos y el nĆŗmero de presuntos valores atĆ­picos k (con k = 3 como nĆŗmero de presuntos valores atĆ­picos por defecto). Para este ejemplo, establecemos que el nĆŗmero de presuntos valores atĆ­picos sea igual a 3, tal y como sugiere el nĆŗmero de posibles valores atĆ­picos esbozado en el boxplot al principio del artĆ­culo.
library(EnvStats)
## 
## Adjuntando el paquete: 'EnvStats'
## The following objects are masked from 'package:stats':
## 
##     predict, predict.lm
test <- rosnerTest(dat$hwy, k = 3)

Los resultados interesantes se ofrecen en la tabla all.stats

test$all.stats
  • BasĆ”ndonos en la prueba de Rosner, vemos que sólo hay un valor atĆ­pico (vĆ©ase la columna de valores atĆ­picos), y que es la observación 34 (vĆ©ase Obs.Num) con un valor de 212 (vĆ©ase Value).

13.4 Tratamiento de los valores atĆ­picos

  • Una vez identificados los valores atĆ­picos y habiendo decidido enmendar la situación segĆŗn la naturaleza del problema, puede considerar uno de los siguientes enfoques.

    • Imputación
      • Este mĆ©todo se ha tratado en detalle en la discusión sobre el tratamiento de los valores faltantes.
    • Capping
      • Para los valores que se encuentran fuera de los lĆ­mites de \(1.5 \cdot IQR\), podrĆ­amos poner un tope sustituyendo las observaciones que se encuentran fuera del lĆ­mite inferior por el valor del 5th percentil y las que se encuentran por encima del lĆ­mite superior, por el valor del 95th percentil. A continuación se muestra un código de ejemplo que logra esto
x <- dat$hwy
qnt <- quantile(x, probs=c(.25, .75), na.rm = T)  #Quartiles
caps <- quantile(x, probs=c(.05, .95), na.rm = T) #5th,95th Percentiles
H <- 1.5 * IQR(x, na.rm = T)
x[x < (qnt[1] - H)] <- caps[1]
x[x > (qnt[2] + H)] <- caps[2]
head(x)
## [1] 29 29 31 30 26 26

13.4.1 Predicción

  • En otro enfoque, los valores atĆ­picos pueden sustituirse por valores faltantes (NA) y luego pueden predecirse considerĆ”ndolos como una variable de respuesta. Ya hemos hablado de cómo predecir los valores faltantes en la sección anterior.

14 Actividades NA

14.1 Actividad PrƔctica

Realice un anÔlisis exploratorio de datos (EDA) utilizando el siguiente conjunto de datos disponible en este enlace. El conjunto de datos incluye varias variables médicas predictoras (independientes) y una variable objetivo (dependiente), que es el resultado de interés. Entre las variables independientes se encuentran el número de embarazos de la paciente, el índice de masa corporal (BMI, calculado como \(\frac{\text{peso en kg}}{(\text{altura en m})^2}\)), el nivel de insulina, la edad, entre otras. En este ejercicio, sustituya todos los valores ausentes o nulos por NAN y realice el anÔlisis correspondiente de los datos faltantes.

df.loc[df["Glucose"] == 0.0, "Glucose"] = np.NAN df.loc[df["BloodPressure"] == 0.0, "BloodPressure"] = np.NAN df.loc[df["SkinThickness"] == 0.0, "SkinThickness"] = np.NAN df.loc[df["Insulin"] == 0.0, "Insulin"] = np.NAN df.loc[df["BMI"] == 0.0, "BMI"] = np.NAN

  • Aplique imputación de datos usando las siguientes opciones para method:

    • method="pmm"
    • method="norm.predict"
    • method="norm.nob"
    • method="norm"
  • Identifique datos atĆ­picos para cada variable en el dataset usando las tĆ©cnicas estudiadas en clase. AdemĆ”s, realice imputación de los datos atĆ­picos con base en lo desarrollado en el Ć­tem anterior.

    • Imputación/Capping/Predicción
    • Test de Rosner, Dixon, Grubbs, Hampel, Percentiles, Boxplots, Histogramas, Descriptivos

14.2 Actividad Evaluativa #3

Realice un anƔlisis exploratorio de datos (EDA) utilizando el siguiente conjunto de datos disponible en este enlace.
El conjunto de datos contiene información sobre pacientes y gastos médicos anuales, con variables como la edad, el sexo, el índice de masa corporal (BMI, calculado como \( \frac{\text{peso en kg}}{(\text{altura en m})^2} \)), número de hijos, hÔbito de fumar, región y costo anual del seguro médico.

  1. Cargue la base de datos desde el enlace anterior directamente en R.
  2. Identifique el tipo de variables (numéricas y categóricas) y realice un resumen estadístico.
  3. Simule valores faltantes (NA) en las siguientes condiciones:
    • bmi > 40
    • charges == 0 (si existen)
  4. Visualice y resuma la cantidad de valores faltantes.
  5. Detecte valores atƭpicos para cada variable numƩrica utilizando al menos tres de las siguientes tƩcnicas:
    • Regla del IQR (boxplot)
    • MĆ©todo de Hampel
    • Test de Grubbs
    • Test de Dixon
    • Test de Rosner
    • Percentiles
  6. Trate los valores atĆ­picos aplicando:
    • Imputación
    • Capping
    • Predicción (si aplica)
  7. Impute los valores faltantes usando el paquete mice u otros mƩtodos:
    • method = "pmm"
    • method = "norm.predict"
    • method = "norm.nob"
    • method = "norm"
  8. Compare los resultados de las imputaciones y comente cuƔl mƩtodo conserva mejor la estructura original de los datos, apoyandose en los graficos y en las pruebas analiticas estudiadas.

15 AnƔlisis Exploratorio de Datos

El anÔlisis exploratorio de datos (Ver video) (EDA por sus siglas en inglés) implica el uso de grÔficos y visualizaciones para explorar y analizar un conjunto de datos. El objetivo es explorar, investigar y aprender, no confirmar hipótesis estadísticas.

15.1 Pasos para un AnƔlisis Exploratorio de Datos (EDA)

15.1.1 Comprender el contexto

15.1.2 Importar datos:

  • Cargar los datos.
  • Comprender la estructura del dataset.

15.1.3 Limpieza, tratamiento y preparación:

  • Revisión de los tipos de datos y conversión si es necesario.

  • Revisión de valores faltantes.

  • Resumen estadĆ­stico inicial.

  • Detección de outliers.

    • Los atĆ­picos deben ser tratados.
    • Si se tratan con imputación, y esta debe ser evaluada utilizando histogramas o pruebas analĆ­ticas de igualdad de distribución, dependiendo del resultado de la normalidad.

15.1.4 AnƔlisis descriptivo de las variables:

  • AnĆ”lisis de la distribución de las variables.
  • Identificación de relaciones entre variables.
  • AnĆ”lisis de variables categóricas.
  • Exploración de interacciones de variables.

15.1.5 Documentación de hallazgos.

15.2 ¿CuÔndo debo utilizarlo?

El anƔlisis exploratorio de datos es una potente herramienta para explorar un conjunto de datos. Incluso cuando su objetivo es efectuar anƔlisis planificados, el EDA puede utilizarse para limpiar datos, para anƔlisis de subgrupos o simplemente para comprender mejor los datos. Un paso inicial importante en cualquier anƔlisis de datos es representar los datos grƔficamente.

No grƔfico: Calcula estadƭsticas descriptivas de las variables

GrƔfico: Calcula estadƭsticas de forma grƔfica

Univariado: Analiza una sola variable a la vez

Multivariado: Analiza dos o mƔs variables

A su vez, cada uno de esas dividisiones puede subdividirse según los tipos de datos con los que trabajemos: categóricos o numéricos.

15.3 Representación de datos según su Naturaleza

tabla <- data.frame(
  "Naturaleza de la variable" = c("Cualitativa", "", "Cuantitativa", ""),
  "Escala de Medidas" = c("Nominal", "Ordinal", "Intervalo", "Razon"),
  "Frecuencias" = c("Si", "Si", "Agrupadas", ""),
  "Medidas de Localizacion" = c("Moda", "Moda", "Media, Mediana y Moda", ""),
  "Medidas de Dispersion" = c("No", "No", "Si", "Si"),
  "Medidas de Distribucion" = c("No", "No", "Si", "Si"),
  "Graficos" = c("Sectores, Barras", "Sectores, Barras (sin orden)", "Histograma, Tallo y hojas, Cajas y Bigotes, Dispersion.", "")
)

# Create the table with kableExtra
library(knitr)
library(kableExtra)

tabla %>%
  kable("html", align = "c", col.names = c(
    "Naturaleza de la variable", 
    "Escala de Medidas", 
    "Frecuencias", 
    "Medidas de Localizacion", 
    "Medidas de Dispersion", 
    "Medidas de Distribucion", 
    "Graficos"
  )) %>%
  kable_styling(full_width = F, position = "center", bootstrap_options = c("striped", "hover", "condensed", "responsive")) %>%
  row_spec(0, bold = TRUE, background = "#D9E2F1", color = "black") %>%
  row_spec(1:2, background = "white", color = "black") %>%
  row_spec(3:4, background = "#E7E7E7", color = "black")
Naturaleza de la variable Escala de Medidas Frecuencias Medidas de Localizacion Medidas de Dispersion Medidas de Distribucion Graficos
Cualitativa Nominal Si Moda No No Sectores, Barras
Ordinal Si Moda No No Sectores, Barras (sin orden)
Cuantitativa Intervalo Agrupadas Media, Mediana y Moda Si Si Histograma, Tallo y hojas, Cajas y Bigotes, Dispersion.
Razon Si Si

15.4 Presentación y anÔlisis de la Información en estudios Descríptivos.

tabla <- data.frame(
  "Tipo de Tabla" = c(
    "De Frecuencia (Variable Cualitativa)",
    "De Frecuencia (Variable Cuantitativa)",
    "De Asociacion (Dos Variables Cualitativas)",
    "De Asociacion (Una Variable Cualitativa y una Cuantitativa Discreta)",
    "De Asociacion (Una Variable Cualitativa y una Cuantitativa Continua)",
    "De Asociacion (Dos Variables Cuantitativas)"
  ),
  "Tipo de Grafico" = c(
    "- Barras simples\n- Pastel",
    "- Histograma",
    "- Barras compuestas\n- Barras superpuestas",
    "- Barras:\n  * Compuestas\n  * Superpuestas",
    "- Poligono de Frecuencia\n- Box plot (diagrama de cajas y bigotes)",
    "- Diagrama de Puntos"
  ),
  stringsAsFactors = FALSE
)

# Creating the table using kableExtra
tabla %>%
  kable("html", escape = FALSE, align = "l", col.names = c("Tipo de Tabla", "Tipo de Grafico")) %>%
  kable_styling(full_width = F, position = "center", bootstrap_options = c("striped", "hover", "condensed", "responsive")) %>%
  row_spec(0, bold = TRUE, background = "#D9E2F1", color = "black") %>%
  row_spec(c(3, 4), background = "#F2F2F2", color = "black") %>%
  row_spec(c(2, 5), background = "white", color = "black") %>%
  row_spec(c(1, 6), background = "#E7E7E7", color = "black") %>%
  column_spec(1, width = "4cm") %>%
  column_spec(2, width = "6cm")
Tipo de Tabla Tipo de Grafico
De Frecuencia (Variable Cualitativa)
  • Barras simples
  • Pastel
De Frecuencia (Variable Cuantitativa)
  • Histograma
De Asociacion (Dos Variables Cualitativas)
  • Barras compuestas
  • Barras superpuestas
  • De Asociacion (Una Variable Cualitativa y una Cuantitativa Discreta)
    • Barras:
    • Compuestas
    • Superpuestas
    De Asociacion (Una Variable Cualitativa y una Cuantitativa Continua)
    • Poligono de Frecuencia
  • Box plot (diagrama de cajas y bigotes)
  • De Asociacion (Dos Variables Cuantitativas)
    • Diagrama de Puntos

    15.5 Situación Problema.

    Exploración de la Intersección entre la Psicología y la Ciencia de Datos: Comportamiento Humano en Entornos Digitales

    En un centro de investigación psicológica enfocado en el comportamiento humano en entornos digitales como redes sociales y plataformas de juegos en línea. Recopilamos datos que incluyen variables demogrÔficas, patrones de uso de redes sociales, datos de juegos en línea y mediciones psicológicas. Utilizamos herramientas de ciencia de datos y anÔlisis estadístico para identificar patrones significativos que ayuden a comprender cómo diferentes factores influyen en el comportamiento en línea y el bienestar psicológico. Este enfoque integrado entre la psicología y la ciencia de datos nos permite desarrollar intervenciones efectivas para mejorar la calidad de vida en línea y promover la salud mental de los usuarios.

    15.6 Construcción de una base de datos

    A continuación se construirÔ la primera base de datos a partir de las variables. Para esto, como se observa en los siguientes comandos, se parte por la construcción de 11 variables de 20 casos cada una:

    #Creación de las variables: todas tienen la misma cantidad de casos.
    Paciente <- c("Mario", "Luis", "Pedro", "Maria", "Sandra", "Erika", "Laura","Luz","Olga")
    Edad <- c(18, 20, 20, 17, 19, 22, 22, 22,31)
    Sexo <- c("Masculino", "Femenino", "Masculino", "Femenino", "Masculino", "Femenino", "Masculino", "Femenino","Femenino")
    Educacion <- c("Universidad", "Secundaria", "Universidad", "Posgrado", "Universidad", "Universidad", "Universidad", "Posgrado","Posgrado")
    Ocupacion <- c("Estudiante", "Profesional", "Estudiante", "Profesional", "Estudiante", "Profesional", "Estudiante", "Profesional","Posgrado")
    Red_Social_Principal <- c("Instagram", "Facebook", "Instagram", "Twitter", "TikTok", "Instagram", "Facebook", "Instagram","TikTok")
    Tiempo_en_Redes_Sociales <- c(2.5, 3.0, 2.0, 2.5, 3.5, 2.2, 2.8, 3.0,2.0)
    Horas_Semanales_de_Juego <- c(15, 20, 12, 10, 18, 15, 20, 15,41)
    Autoestima <- c(8.2, 6.9, 7.8, 7.0, 8.5, 7.3, 8.0, 7.6,9)
    Ansiedad_Social <- c(42, 50, 38, 45, 35, 48, 40, 42,45)
    Satisfaccion_con_la_Vida <- c(7.5, 6.9, 8.0, 7.2, 7.8, 6.5, 7.0, 7.3,8)
    Estres<- c(2,2,1,3,4,2,1,4,4)

    A partir de las variables ya creadas se puede construir una base de datos.

    df=data.frame(Paciente, Edad,Sexo,Edad,Educacion, Ocupacion, Red_Social_Principal,Horas_Semanales_de_Juego,Ansiedad_Social,Satisfaccion_con_la_Vida,Estres)
    df

    16 Representación de datos

    La representación de datos se refiere al proceso de presentar la información de manera visual o tabular para facilitar su comprensión, anÔlisis y comunicación. Esta representación puede tomar diversas formas, incluyendo grÔficos, tablas, diagramas, mapas y resúmenes estadísticos. El objetivo principal de la representación de datos es convertir datos crudos en información comprensible y significativa.

    Aquí hay una descripción de algunas formas comunes de representación de datos:

    GrÔficos: Los grÔficos son representaciones visuales de datos que utilizan diferentes tipos de elementos visuales, como líneas, barras, puntos y Ôreas, para mostrar la relación entre variables o la distribución de datos. Algunos tipos comunes de grÔficos incluyen grÔficos de barras, grÔficos de líneas, grÔficos circulares, histogramas y diagramas de dispersión.

    Tablas: Las tablas son representaciones tabulares de datos que organizan la información en filas y columnas. Las tablas son útiles para mostrar datos detallados o para comparar valores entre diferentes categorías o grupos. Pueden incluir valores numéricos, texto descriptivo y otras características.

    16.1 Representación en GrÔficos.

    Para visualizar los datos en formato dataframe puede usar el comando View() o tambiƩn head() para visualizar las primeras filas en consola.

    head(df)

    16.1.1 Diagrama de barras

    ggplot2 es un sistema para crear grÔficos de forma declarativa, basado en la GramÔtica de los GrÔficos. Se deben proporcionar los datos, indicar a ggplot2 cómo asignar las variables a la estética y qué tipo de grÔficas utilizar. La función geom_bar() se utiliza para producir grÔficos de Ôrea 1d: grÔficos de barras para x categóricas, e histogramas para y continuas

    library(ggplot2)
    ggplot(data=df, aes(x=Paciente, y=Edad)) + geom_bar(stat="identity")+labs(title = "Distribución de Edad por Paciente")

    El diagrama puede ser dibujado en forma horizontal usando la función coord_flip()

    ggplot(data=df, aes(x=Paciente, y=Edad)) + geom_bar(stat="identity")+labs(title = "Distribución de Edad por Paciente")+ coord_flip()

    Podemos cambiar el ancho, así como también el color de las barras y bordes. Nótese que se puede hacer una copia de la grÔfica en una variable, en este ejemplo en p para que luego pueda ser usada para presentar el grafico o realizar mÔs transformaciones

    ggplot(data=df, aes(x=Paciente, y=Edad)) + geom_bar(stat="identity",width=0.5)+labs(title = "Distribución de Edad por Paciente")

    ggplot(data=df, aes(x=Paciente, y=Edad)) + geom_bar(stat="identity",width=0.5,color="blue", fill="green3")+labs(title = "Distribución de Edad por Paciente")

    ggplot(data=df, aes(x=Paciente, y=Edad)) + geom_bar(stat="identity",width=0.8, fill="steelblue")+labs(title = "Distribución de Edad por Paciente")

    #creando tabla de resumen
    Tabla_1 <- df %>%
      dplyr::group_by(Red_Social_Principal) %>%                                  
      dplyr::summarise(Total = n()) %>%                                
      dplyr::mutate(Porcentaje = round(Total/sum(Total)*100, 1)) %>%   
      dplyr::arrange(Red_Social_Principal)
    
    
    "Grafico"
    ## [1] "Grafico"
    G1<-ggplot(Tabla_1, aes(x =Red_Social_Principal, y=Total) ) + 
      geom_bar(width = 0.7,stat="identity",                 
               position = position_dodge(), fill="cyan4") +  
      ylim(c(0,5))+
      #xlim(c(0,300)) +                  
      #ggtitle("Un tĆ­tulo") + 
      labs(x="Red Social", y= "Frecuencias \n (Porcentajes)")   +   
      geom_text(aes(label=paste0(Total," ", "", "(", Porcentaje, "%", ")")),  
                vjust=-0.9, 
                color="black", 
                hjust=0.5,
                # define text position and size
                position = position_dodge(0.9),  
                angle=0, 
                size=4.5) +   
      theme(axis.text.x = element_text(angle = 0, vjust = 1, hjust=1)) +      
      theme_bw(base_size = 16) +
      #coord_flip() +                                                         
      facet_wrap(~"Distribución de Tipo de Red Social")
    G1

    16.1.2 GrƔfico de barras agrupado

    Un grƔfico de barras agrupado muestra un valor numƩrico para un conjunto de entidades divididas en grupos y subgrupos.

    El conjunto de datos para el presente ejemplo proporciona 3 columnas: el valor numĆ©rico (value), y 2 variables categóricas. En el llamada aes(), x es (categ), y el subgrupo (categ) se da al argumento fill. En la función geom_bar(), debe especificarse position=ā€œdodgeā€ para que las barras estĆ©n una al lado de la otra.

    • El siguiente llamado es utilizado para el grĆ”fico de barras agrupado
    head(df)
    ggplot(df, aes(fill = Sexo, y = Edad, x = Red_Social_Principal, label = Edad)) +
      geom_bar(position = "dodge", stat = "identity") +
      labs(title = "Distribución de la Red Social según la Edad y Sexo",
           x = "Red Social Principal",
           y = "Edad",
           fill = "Sexo")  

    ggplot(df, aes(fill = Sexo, x = Red_Social_Principal)) +
      geom_bar(position = "stack") +
      geom_text(stat = 'count', aes(label = ..count..), position = position_stack(vjust = 0.5), size = 3, color = "black") +
      labs(title = "Distribución de la Red Social según el Sexo",
           x = "Red Social Principal",
           y = "Frecuencia") +
      scale_fill_manual(values = c("blue2", "pink2"), name = "Sexo", labels = c("Hombre", "Mujer")) +
      theme(legend.position = "right")  # Ubicación de la leyenda 

    16.1.3 Histograma

    Los histogramas son útiles para representar la distribución de variables continuas como Edad, Tiempo en Redes Sociales y Horas Semanales de Juego. Cada barra del histograma muestra la frecuencia de los datos..

    ggplot(data = df, aes(x = Edad)) +
      geom_histogram(binwidth = 1, fill = "skyblue", color = "black", alpha = 0.8) +
      labs(title = "Histograma de Edades",
           x = "Edad",
           y = "Frecuencia")

    16.1.4 GrƔfico Circular

    ggplot2 no ofrece ningún geom específica para construir diagramas circulares (piecharts). El truco es el siguiente: El marco de datos de entrada tiene 2 columnas: los nombres de los grupos (group here) y su valor (value here), se construye un grÔfico de barras apilado con una sola barra utilizando la función geom_bar(), luego se hace circular con coord_polar()

    library(magrittr)
    library(dplyr)
    
    #Tabla resumen
    Tabla_2 <- df %>% 
      group_by(Sexo) %>% # Variable a ser transformada
      count() %>% 
      ungroup() %>% 
      mutate(Porcentaje = `n` / sum(`n`)) %>% 
      arrange(Porcentaje) %>%
      mutate(etiquetas = scales::percent(Porcentaje))
    
    #Grafico #2
    require(scales)
    ## Cargando paquete requerido: scales
    ## 
    ## Adjuntando el paquete: 'scales'
    ## The following object is masked from 'package:purrr':
    ## 
    ##     discard
    ## The following object is masked from 'package:readr':
    ## 
    ##     col_factor
    ggplot(Tabla_2, aes(x = "", y = Porcentaje, fill = Sexo)) +
      geom_col(color = "black") +
      geom_label(aes(label = etiquetas),
                 position = position_stack(vjust = 0.5),
                 show.legend = FALSE) +
      guides(fill = guide_legend(title = "Distribución de Pacientes según el Sexo")) + scale_color_gradient() +
      coord_polar(theta = "y") + ggtitle ("")

      #theme_void() 

    16.2 Representación en Tablas

    La libreria questionr de R contiene la función freq la cual genera y formatea tablas de frecuencia simples a partir de una variable o una tabla, con porcentajes y opciones de formato. El resultado es un objeto de la clase data.frame.

    library(questionr)
    
    Tabla_Sexo <- questionr::freq(Sexo, cum = TRUE, sort = "dec", total = TRUE)
    knitr::kable(Tabla_Sexo)
    n % val% %cum val%cum
    Femenino 5 55.6 55.6 55.6 55.6
    Masculino 4 44.4 44.4 100.0 100.0
    Total 9 100.0 100.0 100.0 100.0

    La tabla puede ordenarse opcionalmente en frecuencia descendente, y funciona bien con kable. Si deseamos ver la estructura de la tabla generada por freq() utilizamos la función str()

    str(Tabla_Sexo) 
    ## Classes 'freqtab' and 'data.frame':  3 obs. of  5 variables:
    ##  $ n      : num  5 4 9
    ##  $ %      : num  55.6 44.4 100
    ##  $ val%   : num  55.6 44.4 100
    ##  $ %cum   : num  55.6 100 100
    ##  $ val%cum: num  55.6 100 100

    16.2.1 Tabla de frecuencias agrupada

    Para realizar una tabla de frecuencias agrupada utilizaremos en este ejemplo la Regla de Sturges, en la que el nĆŗmero de clases es obtenido por medio de: \(c=1+ln(N)/ln(2)\) donde \(N\) representa el nĆŗmero total de datos. Consideremos el Ejemplo 23 de los apuntes, en el que se representan las edades de un conjunto de estudiantes.

    Ejemplo: Se tienen las siguientes edades de algunos estudiantes

    edades <- c(22, 19, 16, 13, 18, 15, 20, 14, 15, 16,
              15, 16, 20, 13, 15, 18, 15, 13, 18, 15)
    knitr::kable(head(edades))
    x
    22
    19
    16
    13
    18
    15

    Encontremos el nĆŗmero de clases usando la regla de Sturges

    n_sturges = 1 + log(length(edades))/log(2)
    n_sturgesc = ceiling(n_sturges)
    n_sturgesf = floor(n_sturges)
    
    n_clases = 0
    if (n_sturgesc%%2 == 0) {
      n_clases = n_sturgesf
    } else {
      n_clases = n_sturgesc
    }
    R = max(edades) - min(edades)
    w = ceiling(R/n_clases)

    Calculemos ahora nuestra tabla de frecuencias con nĆŗmero de clases n_clases. Primero creamos una lista de fronteras de clases bins y luego agrupamos los datos basados en estas

    bins <- seq(min(edades), max(edades) + w, by = w)
    bins
    ## [1] 13 15 17 19 21 23
    Edades <- cut(edades, bins)
    Freq_table <- transform(table(Edades), Rel_Freq=prop.table(Freq), Cum_Freq=cumsum(Freq))
    knitr::kable(Freq_table)
    Edades Freq Rel_Freq Cum_Freq
    (13,15] 7 0.4117647 7
    (15,17] 3 0.1764706 10
    (17,19] 4 0.2352941 14
    (19,21] 2 0.1176471 16
    (21,23] 1 0.0588235 17
    str(Freq_table)
    ## 'data.frame':    5 obs. of  4 variables:
    ##  $ Edades  : Factor w/ 5 levels "(13,15]","(15,17]",..: 1 2 3 4 5
    ##  $ Freq    : int  7 3 4 2 1
    ##  $ Rel_Freq: num  0.4118 0.1765 0.2353 0.1176 0.0588
    ##  $ Cum_Freq: int  7 10 14 16 17

    Podemos tambiƩn crear un histograma para la tabla de frecuencias agrupada

    df2 <- data.frame(x = Freq_table$Edades, y = Freq_table$Freq)
    knitr::kable(df2)
    x y
    (13,15] 7
    (15,17] 3
    (17,19] 4
    (19,21] 2
    (21,23] 1
    ggplot(data=df2, aes(x=x, y=y)) +
      geom_bar(stat="identity", color="blue", fill="green") +
      xlab("Rango de Edades") +
      ylab("Frecuencia")

    17 AnƔlisis Estadƭstico

    Una función multiuso muy útil en R es summary(X), donde X puede ser uno de cualquier número de objetos, incluyendo conjuntos de datos, variables y modelos lineales, por nombrar algunos. Cuando se utiliza, el comando proporciona datos de resumen relacionados con el objeto individual que se introdujo en él. Así, la función de resumen tiene diferentes resultados dependiendo del tipo de objeto que tome como argumento. AdemÔs de ser ampliamente aplicable, este método es valioso porque a menudo proporciona exactamente lo que se necesita en términos de estadísticas de resumen.

    Usando la función summary() podemos obtener estadísticos de interes y valores de posición:

    summary(df$Horas_Semanales_de_Juego)
    ##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    ##   10.00   15.00   15.00   18.44   20.00   41.00

    Del anterior resultado se puede observar que la hora mínima en el juego fue de 10, el 25% se ubicó en 15 horas indicando que dedicaron 15 o menor o igual a 15, al igual que el 50%, en promedio dedicaron 18,4 horas de juego, el 75% dedicó menos o igual que 20 horas y la hora que mÔs dedicaron fue de 41.

    Por otro lado, se puede notar la función summary() no nos entrega todos los estadísticos de interés, para solucionar esto podemos hacer uso de la librería, pastecs y la función stat.desc(), como se muestra a continuación.

    library(pastecs)
    ## 
    ## Adjuntando el paquete: 'pastecs'
    ## The following object is masked from 'package:magrittr':
    ## 
    ##     extract
    ## The following objects are masked from 'package:dplyr':
    ## 
    ##     first, last
    ## The following object is masked from 'package:tidyr':
    ## 
    ##     extract
    stat.desc(df)

    17.1 Caja y extensión

    Los grĆ”ficos de caja (box plots), tambiĆ©n conocidos como diagramas de cajas y bigotes, son una representación grĆ”fica que permite resumir las caracterĆ­sticas principales de los datos (posición, dispersión, asimetrĆ­a, …) e identificar la presencia de valores atĆ­picos. En esta sección revisaremos cómo hacer box plots en R base y en ggplot2.

    Utilizando boxplot() R base

    boxplot(df$Edad, horizontal=TRUE, col='steelblue')

    Usando geom_boxplot() de la librerĆ­a ggplot2

    library(tidyverse)
    library(hrbrthemes)
    library(viridis)
    ## Cargando paquete requerido: viridisLite
    ## 
    ## Adjuntando el paquete: 'viridis'
    ## The following object is masked from 'package:scales':
    ## 
    ##     viridis_pal
    df %>% 
      ggplot(aes(x = "", y = Edad)) +
      geom_boxplot(color = "black", fill = "yellow2", alpha = 0.5) +
      theme_ipsum() +
      theme(legend.position = "none", plot.title = element_text(size = 11)) +
      ggtitle("Distribución de las Edades") +
      coord_flip()

    ggplot(df, aes(x = Sexo, y = Edad, fill = Sexo)) +
      geom_boxplot() +
      labs(title = "Diagrama de Edades segĆŗn el Sexo",
           x = "Sexo", y = "Edades") +
      scale_fill_manual(values = c("lightblue", "pink")) +
      theme_minimal()

    17.2 Coeficiente de Variación

    El coeficiente de variación (CV) es una medida estadística que se utiliza para evaluar la variabilidad relativa de una muestra o población en relación con su media. Se calcula como la desviación estÔndar de los datos dividida por la media, y se expresa como un porcentaje multiplicado por 100 para facilitar su interpretación.

    El CV es útil cuando se comparan distribuciones de datos con diferentes escalas o unidades, ya que normaliza la variabilidad en relación con la magnitud de los datos. Esto permite realizar comparaciones mÔs significativas entre diferentes conjuntos de datos.

    \[CV = \left( \frac{\text{Desviación EstÔndar}}{\text{Media}} \right) \times 100\]

    Ahora vamos a hallar el coeficiente de variación de la variable Edad.

    media <- mean(df$Edad)
    desviacion <- sd(df$Edad)
    coef_variacion <- (desviacion / media) * 100
    cat("El coeficiente de variación es:", coef_variacion, "%\n")
    ## El coeficiente de variación es: 19.25285 %

    17.3 AsimetrĆ­a

    El coeficiente de asimetría de Pearson es que es una medida estandarizada de la asimetría de una distribución de datos. Se calcula como el tercer momento estandarizado de la distribución, es decir, la diferencia promedio al cubo entre los datos y la media, dividida por la desviación estÔndar al cubo. Si el coeficiente de asimetría de Pearson es cero, la distribución es simétrica. Si es positivo, la cola de la distribución estÔ en el lado derecho, y si es negativo, la cola estÔ en el lado izquierdo. Esto proporciona información sobre la forma y dirección de la asimetría en la distribución de datos.

    \[\text{Coeficiente de AsimetrĆ­a de Pearson} = \frac{E[(X - \mu)^3]}{\sigma^3}\]

    Como el coefiente de asmetría de Pearson es mayo que cero, indica que la edad presenta distribución asimetrica hacia la derecha.

    17.4 Medidas de la curtosis

    La curtosis es una medida estadĆ­stica que describe la forma de la distribución de los datos en relación con una distribución normal estĆ”ndar. La curtosis es una medida de la ā€œpicudezā€ de la distribución, es decir, cuĆ”n puntiaguda o aplanada es en comparación con una distribución normal.

    \[\text{Curtosis} = \frac{1}{n} \sum_{i=1}^{n} \left(\frac{x_i - \bar{x}}{s}\right)^4 - 3\]

    Platicúrtica: Una distribución platicúrtica es aquella que tiene un exceso de curtosis negativo en comparación con la distribución normal estÔndar (cuyo exceso de curtosis es 0). Esto significa que la distribución tiene colas mÔs ligeras y es mÔs aplanada en comparación con la distribución normal. En una distribución platicúrtica, los valores se concentran mÔs cerca de la media y hay menos valores extremos en comparación con una distribución normal.

    MesocĆŗrtica: Una distribución mesocĆŗrtica es aquella que tiene un exceso de curtosis igual a 0, es decir, su forma es similar a la de una distribución normal estĆ”ndar. Esto significa que la distribución tiene una cantidad ā€œnormalā€ de picos y colas, y su forma se asemeja a una campana simĆ©trica.

    Leptocúrtica: Una distribución leptocúrtica es aquella que tiene un exceso de curtosis positivo en comparación con la distribución normal estÔndar. Esto significa que la distribución tiene colas mÔs pesadas y es mÔs puntiaguda en comparación con la distribución normal. En una distribución leptocúrtica, los valores tienden a agruparse mÔs cerca de la media y hay mÔs valores extremos en comparación con una distribución normal.

    curtosis <- kurtosis(df$Edad)
    cat("La curtosis de la muestra es:", curtosis, "\n")
    ## La curtosis de la muestra es: 0.8255815

    18 Explorando Datos Geoespaciales: Visualización de Mapas Interactivos y Estaticosā€

    18.1 Visualización de datos geogrÔficos con ggmap

    • El paquete ggmap permite visualizar datos espaciales y estadĆ­sticas espaciales en formato de mapa utilizando el enfoque por capas de ggplot2. Este paquete tambiĆ©n incluye mapas base que dan contexto a sus visualizaciones, incluyendo Google Maps, Open Street Map, Stamen Maps y CloudMade. AdemĆ”s, se proporcionan funciones para acceder a varios servicios de Google, como Geocoding, Distance Matrix, and Directions.

    • El paquete ggmap se basa en ggplot2, lo que significa que adoptarĆ” un enfoque por capas y constarĆ” de los mismos cinco componentes que se encuentran en ggplot2. Estos incluyen un conjunto de datos por defecto con mapeos estĆ©ticos donde x es longitud, y es latitud, y el sistema de coordenadas se fija en Mercator. Otros componentes incluyen una o mĆ”s capas definidas con un objeto geomĆ©trico y una transformación para cada mapa estĆ©tico, el sistema de coordenadas y la especificación de las facetas. Debido a que ggmap estĆ” construido sobre ggplot2 tiene acceso a toda la gama de ggplot2 ya aprendidas.

    • En esta sección cubriremos los siguientes temas:

      • Creación de un mapa base
      • AƱadir capas operativas
      • AƱadir capas desde un shapefile

    18.2 Creación de un mapa base

    • Hay dos pasos bĆ”sicos para crear un mapa con ggmap. En general, sólo hay que descargar el mapa rasterizado (basemap) y luego trazar los datos operativos en el mapa base. El primer paso se consigue con la función get_map(), que puede utilizarse para crear un mapa base desde Google, Stamen, Open Street Map o CloudMade. AprenderĆ”s cómo hacerlo en este paso. En un próximo paso aprenderĆ”s a aƱadir y estilizar los datos operativos de varias maneras.
    1. Cargue el paquete ggmap yendo al panel de paquetes en RStudio y haciendo clic en la casilla junto al nombre del paquete. TambiƩn puede cargarlo desde la consola escribiendo:
    library(ggmap)
    ## ℹ Google's Terms of Service: <https://mapsplatform.google.com>
    ##   Stadia Maps' Terms of Service: <https://stadiamaps.com/terms-of-service/>
    ##   OpenStreetMap's Tile Usage Policy: <https://operations.osmfoundation.org/policies/tiles/>
    ## ℹ Please cite ggmap if you use it! Use `citation("ggmap")` for details.
    ## 
    ## Adjuntando el paquete: 'ggmap'
    ## 
    ## 
    ## The following object is masked from 'package:magrittr':
    ## 
    ##     inset
    1. Crea una variable llamada myLocation y defínela como California, ubicación que usaremos mÔs adelante:
    myLocation <- "California"
    1. Llama a la función get_map() y pasa la variable de localización junto con un nivel de Zoom de 6. NecesitarÔs primero crear una API key en Google Cloud Platform. Si descargas la versión de desarrollo de ggmap desde GitHub, hay una función que guarda tu clave de la API de Google y la utiliza en las siguientes llamadas a la API a través de ggmap. Para instalarla desde la consola escribe:

    2. Utilice las siguientes instrucciones para descargar el mapa. Cargue primero su API key y luego use la función get_googlemap para obtener el mapa de interés. Para que las siguientes instrucciones funcionen, debe realizar los siguientes pasos en Google Cloud Platform:

    • Debe registrarse en Google Cloud Platform. Desafortunadamente para el registro deberĆ” usar una Tarjeta de crĆ©dito. ObtendrĆ” $300 gratis para usarlos por 90 dĆ­as. Luego de esto se cargarĆ”n a su cuenta $5 mensuales, dependiendo del uso que haga de la API. Recomiendo usar una tarjeta de crĆ©dito prepago como por ejemplo Daviplata o similares.
    • Debe habilitar los siguientes servicios en la Biblioteca:
      • Maps Static API
      • Geocoding API
      • Geolocation API
      • Maps Embed API
    • Para finalizar, genere una API restringida a cada uno de los servicios de localización habilitados anteriormente. Las siguientes imĆ”genes muestran cómo quedarĆ­a la configuración requerida.

    • Para obtener mĆ”s información sobre el uso de la función get_googlemap() ver ggmap. Por favor, no use la API key mykey = "AIzaSyABAXSsDR-f9qby3LK2o6bh0BypFw1Krm4" pues estĆ” serĆ” cambiada por otra, en este caso es usada a manera de ejemplo. Cree su propia API Google Cloud Platform
    mykey = "AIzaSyABAXSsDR-f9qby3LK2o6bh0BypFWlKrm4"
    register_google(key = mykey)
    
    get_googlemap("waco, texas") %>% ggmap()
    ## ℹ <https://maps.googleapis.com/maps/api/staticmap?center=waco,%20texas&zoom=10&size=640x640&scale=2&maptype=terrain&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>
    ## ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=waco,+texas&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>

    1. Los siguientes tipos de mapas también estÔn disponibles para vista satelital en los mapas. Para ver mÔs opciones, visitar la documentación de get_googlemap:
    get_googlemap("Colombia",
                  maptype = "hybrid",
                  scale = 2,
                  zoom = 5) %>% ggmap()
    ## ℹ <https://maps.googleapis.com/maps/api/staticmap?center=Colombia&zoom=5&size=640x640&scale=2&maptype=hybrid&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>
    ## ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Colombia&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>

    get_googlemap("Universidad del Norte", 
                  maptype = "roadmap",
                  scale = 2,
                  zoom = 12) %>% ggmap()
    ## ℹ <https://maps.googleapis.com/maps/api/staticmap?center=Universidad%20del%20Norte&zoom=12&size=640x640&scale=2&maptype=roadmap&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>
    ## ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Universidad+del+Norte&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>

    get_googlemap("Universidad del Norte", 
                  maptype = "terrain",
                  scale = 2,
                  zoom = 16) %>% ggmap()
    ## ℹ <https://maps.googleapis.com/maps/api/staticmap?center=Universidad%20del%20Norte&zoom=16&size=640x640&scale=2&maptype=terrain&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>
    ## ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Universidad+del+Norte&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>

    get_googlemap("Universidad del Norte", 
                  maptype = "hybrid",
                  scale = 2,
                  zoom = 18) %>% ggmap()
    ## ℹ <https://maps.googleapis.com/maps/api/staticmap?center=Universidad%20del%20Norte&zoom=18&size=640x640&scale=2&maptype=hybrid&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>
    ## ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Universidad+del+Norte&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>

    18.3 AƱadir capas de datos operativos

    • ggmap() devuelve un objeto ggplot, lo que significa que actĆŗa como capa base en el ggplot2. Esto permite la gama completa de capacidades de ggplot2, lo que significa que puede trazar puntos en el mapa, aƱadir contornos, mapas de calor 2D y mucho mĆ”s. Examinaremos algunas de estas capacidades en esta sección.
    1. El siguiente código es usado para producir un mapa de California que muestre los incendios forestales entre los años 1980-2016 que quemaron mÔs de 1,000 hectÔreas.
    myLocation <- "California"
    myMap <- get_map(location = myLocation, zoom = 6)
    ## ℹ <https://maps.googleapis.com/maps/api/staticmap?center=California&zoom=6&size=640x640&scale=2&maptype=terrain&language=en-EN&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>
    ## ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=California&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>
    library(readr)
    dfWildfires <- read_csv("StudyArea_SmallFile.csv", col_names = TRUE)
    ## Rows: 7154 Columns: 21
    ## ── Column specification ────────────────────────────────────────────────────────
    ## Delimiter: ","
    ## chr (10): ORGANIZATI, FIRENAME, FIRENUMBER, FIRECODE, CAUSE, SIZECLASS, STAR...
    ## dbl (11): FID, SPECCAUSE, STATCAUSE, FIRETYPE, YEAR_, STATE_FIPS, DLATITUDE,...
    ## 
    ## ℹ Use `spec()` to retrieve the full column specification for this data.
    ## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
    library(dplyr)
    df <- select(dfWildfires, STATE, YEAR_, TOTALACRES, DLATITUDE, DLONGITUDE)
    knitr::kable(head(df))
    STATE YEAR_ TOTALACRES DLATITUDE DLONGITUDE
    Arizona 1988 1500 31.58333 -111.5500
    Arizona 1986 10390 32.50000 -111.5167
    Montana 1986 1400 47.50000 -111.4333
    Arizona 2002 1035 31.70000 -111.4830
    Arizona 2000 5700 31.51600 -111.5170
    Arizona 2000 2750 31.64900 -111.4910
    df <- filter(df, TOTALACRES >= 1000 & STATE == "California")
    ggmap(myMap) + geom_point(data=df, aes(x = DLONGITUDE, y = DLATITUDE))

    1. Ahora vamos a hacer algo un poco mÔs interesante. En primer lugar, utilizaremos la función de dplyr mutate() para agrupar los incendios por década:
    df <- mutate(df,
                 DECADE = ifelse(YEAR_ %in% 1980:1989, "1980-1989",
                          ifelse(YEAR_ %in% 1990:1999, "1990-1999",
                          ifelse(YEAR_ %in% 2000:2009, "2000-2009",
                          ifelse(YEAR_ %in% 2010:2016, "2010-2016", "-99")))))
    1. A continuación, codifica por colores los incendios forestales por DECADE y crea un mapa de símbolos graduados en función del tamaño de cada incendio. La propiedad de color define la columna a utilizar para la agrupación, y la propiedad de tamaño define la columna a utilizar para el tamaño de cada símbolo.
    • Nótese que ademĆ”s es posible hacer dinĆ”mico el mapa usando ggplotly. Para esto utilizamos la función ggplot_build para convertir el plot de ggmap a un objeto ggplot.
    last_plot <- ggmap(myMap) +
      geom_point(data = df, aes(x = DLONGITUDE, y = DLATITUDE, colour = DECADE, size = TOTALACRES))
    library(plotly)
    ## 
    ## Adjuntando el paquete: 'plotly'
    ## The following object is masked from 'package:ggmap':
    ## 
    ##     wind
    ## The following object is masked from 'package:igraph':
    ## 
    ##     groups
    ## The following object is masked from 'package:ggplot2':
    ## 
    ##     last_plot
    ## The following object is masked from 'package:stats':
    ## 
    ##     filter
    ## The following object is masked from 'package:graphics':
    ## 
    ##     layout
    gg <- ggplot_build(last_plot)$plot
    plotly_plot <- ggplotly(gg)
    plotly_plot
    1. Vamos a cambiar la vista del mapa para centrarnos mƔs en el sur de California, y en particular el Ɣrea al norte de Los Ɓngeles.
    myMap <- get_map(location = "Santa Clarita, California", zoom = 10)
    ## ℹ <https://maps.googleapis.com/maps/api/staticmap?center=Santa%20Clarita,%20California&zoom=10&size=640x640&scale=2&maptype=terrain&language=en-EN&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>
    ## ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Santa+Clarita,+California&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>
    ggmap(myMap) +
      geom_point(data = df,
                 aes(x = DLONGITUDE, y = DLATITUDE, colour = DECADE, size = TOTALACRES))

    1. A continuación, añadiremos capas de contorno y de calor. La función geom_density2d() se utiliza para crear los contornos mientras que la función stat_density2d() crea el mapa de calor. Puedes experimentar con los colores usando las propiedades scale_fill_gradient(low, high).
    • AquĆ­ los hemos establecido en verde y rojo respectivamente, pero puede cambiar el esquema de colores segĆŗn su preferencia. En este ejemplo, la sintaxis fill = ..level.. se utiliza para rellenar los contornos en el grĆ”fico basado en los niveles de densidad de los puntos de datos.
    myMap <- get_map(location = "California", zoom = 6)
    ## ℹ <https://maps.googleapis.com/maps/api/staticmap?center=California&zoom=6&size=640x640&scale=2&maptype=terrain&language=en-EN&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>
    ## ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=California&key=xxx-f9qby3LK2o6bh0BypFWlKrm4>
    ggmap(myMap, extent = "device") +
      geom_density2d(data = df, aes(x = DLONGITUDE, y = DLATITUDE), size = 0.3) +
      stat_density2d(data = df,
                     aes(x = DLONGITUDE, y = DLATITUDE, fill = ..level..,
                         alpha = ..level..), 
                     size = 0.01, bins = 16, geom = "polygon") +
      scale_fill_gradient(low = "green", high = "red") +
      scale_alpha(range = c(0.3, 0.9), guide = FALSE)

    1. Si prefiere ver el mapa de calor sin contornos, el código puede simplificarse como sigue:
    ggmap(myMap, extent = "device") +
      stat_density2d(data = df,
                     aes(x = DLONGITUDE, y = DLATITUDE, fill = ..level..,
                         alpha = ..level..),
                     size = 0.01, bins = 16, geom = "polygon") +
      scale_fill_gradient(low = "green", high = "red") +
      scale_alpha(range = c(0, 0.3), guide = FALSE)

    1. Por último, vamos a crear un mapa de facetas que represente los puntos críticos de cada año de la década actual. El conjunto de datos contiene información hasta el año 2016. facet_wrap() es una función útil en ggplot2 para crear múltiples mapas mostrados juntos en un diseño de cuadrícula, cada uno representando un subconjunto de los datos.
    df <- filter(df, YEAR_ %in% c(2010, 2011, 2012, 2013, 2014, 2015, 2016))
    
    gg <- ggmap(myMap, extent = "device") +
      stat_density2d(data = df, aes(x = DLONGITUDE, y = DLATITUDE, fill = ..level.., alpha = ..level..),
                     size = 0.01, bins = 16, geom = "polygon") +
      scale_fill_gradient(low = "green", high = "red") +
      scale_alpha(range = c(0, 0.3), guide = FALSE) +
      facet_wrap(~YEAR_, ncol = 4) +
      theme(plot.title = element_text(size = 20)) +
      theme(plot.margin = margin(1, 1, 1, 1, "cm"))
    print(gg)

    18.4 Conclusión

    • En este capĆ­tulo ha aprendido a utilizar el paquete ggmap para crear atractivas visualizaciones de datos en formato de mapa.
    • Ha aprendido a crear mapas base utilizando Google como fuente de datos, aƱadir capas de datos operativos, crear varios tipos de visualizaciones de mapas utilizando fuentes de datos externas, y cargar archivos de forma.

    18.5 Ejercicio para PrƔcticar.

    • Considere el conjunto de datos violent_crimes.csv de la carpeta Datos, el cual abarca una gran variedad de delitos, y realice visualizaciones geogrĆ”ficas para cada tipo de crimen.
    • Considere cada Ć­tem estudiado en esta sección para realizar las visualizaciones respectivas con este dataset.
    • Verifique si existe algĆŗn patrón dentro de los delitos violentos que pueda explorarse visualmente. Los datos incluyen dentro de sus columnas la longitud y latitud, la categorĆ­a en la que se clasifica el delito, la fecha y hora.

    19 Ejercicio PrÔctico de AnÔlisis Exploratorio de Datos incluyendo Visulización en Mapas.

    Los precios de los combustibles tienen un impacto significativo en la economía y el bienestar social, dado que influyen en el costo de transporte, la inflación, y en la competitividad de diversos sectores productivos. La fluctuación de los precios del combustible afecta tanto a los consumidores individuales como a las empresas, generando un efecto dominó en los precios de bienes y servicios. Es importante entender cómo estos precios varían a lo largo del tiempo y el espacio, y cómo factores económicos y políticos pueden influir en estas variaciones. Este anÔlisis permitirÔ obtener una visión detallada de la estructura de precios de los combustibles en diferentes regiones de Colombia, y cómo estas variaciones pueden estar relacionadas con otras variables macroeconómicas.

    1. Realice una exploración y un anÔlisis descriptivo completo (incluyendo tablas de resumen y grÔficos) de la base de datos Precios_de_Combustibles_MinEnergia.csv que se encuentra en la carpeta DATOS de GitHub. Incluyendo grÔficos que presenten los datos sobre un mapa de Colombia, para visualizar la distribución geogrÔfica de los precios de los combustibles en el país.
    library(readr)
    p_comb <- read_csv("Precios_de_Combustibles_MinEnergia.csv")
    ## Rows: 418853 Columns: 12
    ## ── Column specification ────────────────────────────────────────────────────────
    ## Delimiter: ","
    ## chr (10): mes, CodigoDepartamento, NombreDepartamento, CodigoMunicipio, muni...
    ## dbl  (2): periodo, precio
    ## 
    ## ℹ Use `spec()` to retrieve the full column specification for this data.
    ## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

    La presente base de datos contiene información acerca de los precios de diversos combustibles durante los aƱos 2017, 2018, 2019 y 2020p. TambiĆ©n contiene información acerca de municipios, departamentos, meses del aƱo, entre otras variables.ā€

    head(p_comb) #Primeras filas de la base de datos
    dim(p_comb) #Dimensiones de los datos: 418.853 registros y 12 variables:
    ## [1] 418853     12
    nombres1<- names(p_comb); nombres1 #Nombres de las columnas
    ##  [1] "periodo"            "mes"                "CodigoDepartamento"
    ##  [4] "NombreDepartamento" "CodigoMunicipio"    "municipio"         
    ##  [7] "nombrecomercial"    "bandera"            "direccion"         
    ## [10] "producto"           "precio"             "estado"
    p_comb %<>% clean_names;names(p_comb) # se limpian los nombres para evitar inconvenientes
    ##  [1] "periodo"             "mes"                 "codigo_departamento"
    ##  [4] "nombre_departamento" "codigo_municipio"    "municipio"          
    ##  [7] "nombrecomercial"     "bandera"             "direccion"          
    ## [10] "producto"            "precio"              "estado"
    p_comb %>% glimpse #Tipos de variables
    ## Rows: 418,853
    ## Columns: 12
    ## $ periodo             <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 20…
    ## $ mes                 <chr> "Enero", "Enero", "Enero", "Enero", "Enero", "Ener…
    ## $ codigo_departamento <chr> "13", "9", "9", "9", "3", "9", "11", "9", "13", "8…
    ## $ nombre_departamento <chr> "HUILA", "CESAR", "CESAR", "CESAR", "BOGOTA D.C.",…
    ## $ codigo_municipio    <chr> "645", "439", "436", "435", "182", "437", "519", "…
    ## $ municipio           <chr> "GARZON", "BECERRIL", "AGUACHICA", "VALLEDUPAR", "…
    ## $ nombrecomercial     <chr> "ESTACION DE SERVICIO ZULUAGA", "ESTACION DE SERVI…
    ## $ bandera             <chr> "BIOMAX", "SAVE", "PETROMIL", "TERPEL", "PETROBRAS…
    ## $ direccion           <chr> "CALLE 4 No. 2-15", "Cra 5 No. 6-36", "KILOMETRO 6…
    ## $ producto            <chr> "BIODIESEL EXTRA", "BIODIESEL EXTRA", "GASOLINA CO…
    ## $ precio              <dbl> 8055, 6500, 6100, 6520, 7685, 6550, 7929, 7000, 82…
    ## $ estado              <chr> "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", …
    p_comb %<>% mutate_if(is.character, as.factor);p_comb %>% glimpse #Convertir en factor las variables que son character
    ## Rows: 418,853
    ## Columns: 12
    ## $ periodo             <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 20…
    ## $ mes                 <fct> Enero, Enero, Enero, Enero, Enero, Enero, Enero, E…
    ## $ codigo_departamento <fct> 13, 9, 9, 9, 3, 9, 11, 9, 13, 8, 8, 1, 23, 22, 1, …
    ## $ nombre_departamento <fct> "HUILA", "CESAR", "CESAR", "CESAR", "BOGOTA D.C.",…
    ## $ codigo_municipio    <fct> 645, 439, 436, 435, 182, 437, 519, 440, 659, 420, …
    ## $ municipio           <fct> "GARZON", "BECERRIL", "AGUACHICA", "VALLEDUPAR", "…
    ## $ nombrecomercial     <fct> ESTACION DE SERVICIO ZULUAGA, ESTACION DE SERVICIO…
    ## $ bandera             <fct> BIOMAX, SAVE, PETROMIL, TERPEL, PETROBRAS, SAVE, O…
    ## $ direccion           <fct> "CALLE 4 No. 2-15", "Cra 5 No. 6-36", "KILOMETRO 6…
    ## $ producto            <fct> BIODIESEL EXTRA, BIODIESEL EXTRA, GASOLINA CORRIEN…
    ## $ precio              <dbl> 8055, 6500, 6100, 6520, 7685, 6550, 7929, 7000, 82…
    ## $ estado              <fct> A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A,…

    Al explorar las primeras filas del conjunto de datos p_comb mediante la función head(p_comb), se observó una muestra inicial del contenido. Este conjunto de datos cuenta con 418,853 registros y 12 variables, como se indicó utilizando la función dim(p_comb). Los nombres de las columnas fueron revisados inicialmente con names(p_comb) y luego se procedió a limpiar estos nombres utilizando clean_names, lo cual facilita el manejo y la manipulación de las variables sin inconvenientes en pasos posteriores. Al aplicar glimpse, se examinaron los tipos de variables presentes en el dataset. AdemÔs, se decidió convertir todas las variables de tipo character en factores usando mutate_if(is.character, as.factor), para optimizar el anÔlisis posterior y garantizar que las categorías sean tratadas adecuadamente en los modelos y grÔficos.

    unique(p_comb$mes)
    ##  [1] Enero      Febrero    Marzo      Mayo       Julio      Agosto    
    ##  [7] Octubre    Noviembre  Diciembre  Abril      Junio      Septiembre
    ## 12 Levels: Abril Agosto Diciembre Enero Febrero Julio Junio Marzo ... Septiembre
    "Ordenar meses:"
    ## [1] "Ordenar meses:"
    p_comb$mes %<>% factor(levels=c("Enero","Febrero","Marzo","Abril","Mayo", "Junio", "Julio",
                                               "Agosto", "Septiembre","Octubre", "Noviembre", "Diciembre"))
    unique(p_comb$nombre_departamento)
    ##  [1] HUILA                                                   
    ##  [2] CESAR                                                   
    ##  [3] BOGOTA D.C.                                             
    ##  [4] CUNDINAMARCA                                            
    ##  [5] CAUCA                                                   
    ##  [6] ANTIOQUIA                                               
    ##  [7] TOLIMA                                                  
    ##  [8] SUCRE                                                   
    ##  [9] LA GUAJIRA                                              
    ## [10] VALLE DEL CAUCA                                         
    ## [11] META                                                    
    ## [12] RISARALDA                                               
    ## [13] ATLANTICO                                               
    ## [14] SANTANDER                                               
    ## [15] CALDAS                                                  
    ## [16] NARIƑO                                                  
    ## [17] NORTE DE SANTANDER                                      
    ## [18] PUTUMAYO                                                
    ## [19] MAGDALENA                                               
    ## [20] CAQUETA                                                 
    ## [21] BOYACA                                                  
    ## [22] CASANARE                                                
    ## [23] QUINDIO                                                 
    ## [24] BOLIVAR                                                 
    ## [25] CORDOBA                                                 
    ## [26] CHOCO                                                   
    ## [27] ARAUCA                                                  
    ## [28] GUAVIARE                                                
    ## [29] VICHADA                                                 
    ## [30] AMAZONAS                                                
    ## [31] ARCHIPIELAGO DE SAN ANDRES, SANTA CATALINA Y PROVIDENCIA
    ## [32] VAUPES                                                  
    ## [33] GUAINIA                                                 
    ## [34] NARI?O                                                  
    ## 34 Levels: AMAZONAS ANTIOQUIA ... VICHADA
    "Corregir Ƒ:"
    ## [1] "Corregir Ƒ:"
    p_comb$nombre_departamento %<>% str_replace_all("\\?","Ƒ");unique(p_comb$nombre_departamento)
    ##  [1] "HUILA"                                                   
    ##  [2] "CESAR"                                                   
    ##  [3] "BOGOTA D.C."                                             
    ##  [4] "CUNDINAMARCA"                                            
    ##  [5] "CAUCA"                                                   
    ##  [6] "ANTIOQUIA"                                               
    ##  [7] "TOLIMA"                                                  
    ##  [8] "SUCRE"                                                   
    ##  [9] "LA GUAJIRA"                                              
    ## [10] "VALLE DEL CAUCA"                                         
    ## [11] "META"                                                    
    ## [12] "RISARALDA"                                               
    ## [13] "ATLANTICO"                                               
    ## [14] "SANTANDER"                                               
    ## [15] "CALDAS"                                                  
    ## [16] "NARIƑO"                                                  
    ## [17] "NORTE DE SANTANDER"                                      
    ## [18] "PUTUMAYO"                                                
    ## [19] "MAGDALENA"                                               
    ## [20] "CAQUETA"                                                 
    ## [21] "BOYACA"                                                  
    ## [22] "CASANARE"                                                
    ## [23] "QUINDIO"                                                 
    ## [24] "BOLIVAR"                                                 
    ## [25] "CORDOBA"                                                 
    ## [26] "CHOCO"                                                   
    ## [27] "ARAUCA"                                                  
    ## [28] "GUAVIARE"                                                
    ## [29] "VICHADA"                                                 
    ## [30] "AMAZONAS"                                                
    ## [31] "ARCHIPIELAGO DE SAN ANDRES, SANTA CATALINA Y PROVIDENCIA"
    ## [32] "VAUPES"                                                  
    ## [33] "GUAINIA"
    "Reducir San Andres: Nombre muy largo"
    ## [1] "Reducir San Andres: Nombre muy largo"
    p_comb$nombre_departamento[p_comb$nombre_departamento
                               == "ARCHIPIELAGO DE SAN ANDRES, SANTA CATALINA Y PROVIDENCIA"] <- "SAN ANDRES ISLAS"
    p_comb$municipio %<>% str_replace_all("\\?","Ƒ")
    
    unique(p_comb$producto)
    ##  [1] BIODIESEL EXTRA                GASOLINA CORRIENTE OXIGENADA  
    ##  [3] GASOLINA EXTRA OXIGENADA       GASOLINA CORRIENTE            
    ##  [5] GASOLINA EXTRA                 KEROSENE                      
    ##  [7] ACEM - DIESEL ECOLOGICO        GASOLINA CORRIENTE - IMPORTADO
    ##  [9] BIODIESEL CORRIENTE            ACPM - DIESEL                 
    ## [11] BIOACEM AL 9%                 
    ## 11 Levels: ACEM - DIESEL ECOLOGICO ACPM - DIESEL ... KEROSENE
    unique(p_comb$estado)
    ## [1] A 1
    ## Levels: 1 A

    Se procedió a verificar y corregir las categorías de las variables de tipo factor en el conjunto de datos p_comb. Inicialmente, se revisaron las categorías de la variable mes usando unique(p_comb$mes). Para asegurar un orden lógico en los meses del año, se reorganizaron los niveles de esta variable con la función factor, especificando el orden correcto de los meses de enero a diciembre.

    Luego, al examinar la variable nombre_departamento, se detectó la presencia de un carĆ”cter incorrecto en lugar de la letra ā€œĆ‘ā€. Este error se corrigió utilizando la función str_replace_all, reemplazando el sĆ­mbolo ā€œ?ā€ por ā€œĆ‘ā€. Adicionalmente, se simplificó el nombre del departamento ā€œARCHIPIELAGO DE SAN ANDRES, SANTA CATALINA Y PROVIDENCIAā€ a ā€œSAN ANDRES ISLASā€ para facilitar su manejo en anĆ”lisis posteriores.

    Posteriormente, se revisaron las categorĆ­as de la variable municipio, aplicando una corrección similar para el carĆ”cter ā€œĆ‘ā€ en caso de ser necesario. TambiĆ©n se exploraron las categorĆ­as de las variables producto y estado para asegurar que no hubiera inconsistencias o errores en los datos.

    #Verifiar los NA:
    nas<-!complete.cases(p_comb) #Determinar filas con al menos un NA
    Nas_tabla<-p_comb[nas,] #Tabla registros con NA, 3 datos faltantes en nombre comercial
    "El nombre comercial es una variable categórica que posría ser reemplazada consultando con el dueño de la data"
    ## [1] "El nombre comercial es una variable categórica que posría ser reemplazada consultando con el dueño de la data"
    require(Amelia)
    missmap(p_comb) #GrƔfica que identiica los NA

    Se realizó una verificación de valores faltantes (NA) en el conjunto de datos p_comb. Primero, se identificaron las filas que contenían al menos un valor NA utilizando la función complete.cases, y estas filas se almacenaron en la tabla Nas_tabla. Se observó que había 3 registros con datos faltantes en la variable nombre_comercial. Dado que nombre_comercial es una variable categórica, se sugirió que podría ser reemplazada, en caso necesario, consultando con el dueño de la base de datos para obtener la información correcta.

    Para visualizar de manera mÔs clara la distribución de los valores faltantes en todo el conjunto de datos, se utilizó la función missmap del paquete Amelia, que genera un mapa que identifica visualmente la presencia de NA en el dataset.

    #Observar datos atĆ­picos:
    summary(p_comb)
    ##     periodo            mes         codigo_departamento nombre_departamento
    ##  Min.   :2017   Junio    : 38293   1      : 43434      Length:418853      
    ##  1st Qu.:2017   Julio    : 37827   3      : 34921      Class :character   
    ##  Median :2018   Agosto   : 37742   11     : 34788      Mode  :character   
    ##  Mean   :2018   Mayo     : 37574   24     : 32621                         
    ##  3rd Qu.:2019   Noviembre: 36982   17     : 31249                         
    ##  Max.   :2019   Octubre  : 36890   21     : 17773                         
    ##                 (Other)  :193545   (Other):224067                         
    ##  codigo_municipio  municipio                                 nombrecomercial  
    ##  182    : 34938   Length:418853      ESTACION DE SERVICIO SAN ANTONIO:   480  
    ##  1035   : 12893   Class :character   ESTACION DE SERVICIO EL BOSQUE  :   405  
    ##  34     :  9718   Mode  :character   ESTACION DE SERVICIO EL JARDIN  :   362  
    ##  159    :  7931                      ESTACION DE SERVICIO EL LAGO    :   325  
    ##  183    :  5102                      ESTACION DE SERVICIO EL EDEN    :   321  
    ##  876    :  4481                      (Other)                         :416957  
    ##  (Other):343790                      NA's                            :     3  
    ##      bandera                                   direccion     
    ##  TERPEL  :154766   VEREDA CODEMACO                  :   384  
    ##  BIOMAX  : 63828   CALLE DEL COMERCIO               :   305  
    ##  MOBIL   : 62008   BARRIO EL PINDO DE TUMACO        :   228  
    ##  TEXACO  : 42473   CALLE PRINCIPAL                  :   215  
    ##  PETROMIL: 25365   KM 1 VIA NEIVA                   :   204  
    ##  ZEUSS   : 13860   VIA AL MAR KILOMETRO 92 EN TUBARA:   204  
    ##  (Other) : 56553   (Other)                          :417313  
    ##                          producto          precio      estado    
    ##  BIODIESEL EXTRA             :168764   Min.   :  100   1: 12468  
    ##  GASOLINA CORRIENTE OXIGENADA:165615   1st Qu.: 8000   A:406385  
    ##  GASOLINA EXTRA OXIGENADA    : 55548   Median : 8755             
    ##  BIOACEM AL 9%               : 16413   Mean   : 8853             
    ##  GASOLINA CORRIENTE          :  5332   3rd Qu.: 9450             
    ##  ACPM - DIESEL               :  3218   Max.   :70910             
    ##  (Other)                     :  3963
    boxplot(p_comb$precio) #Existe un dato atĆ­pico en los precios, valor muy alto

    Para identificar la presencia de datos atípicos en el conjunto de datos p_comb, se realizó un resumen estadístico utilizando la función summary(p_comb). Este resumen proporcionó una visión general de las estadísticas descriptivas para cada variable, incluyendo valores mínimos, mÔximos, medianas, y cuartiles.

    Posteriormente, se utilizó un diagrama de caja (boxplot) para la variable precio, lo que permitió visualizar la distribución de los precios y detectar posibles valores atípicos. El grÔfico reveló la presencia de un valor atípico significativamente alto en la variable precio, lo cual sugiere que podría haber un error en los datos o que este valor requiere una revisión mÔs detallada para entender su origen.

    require(dplyr)
    atip<-filter(p_comb, precio>50000);atip #Detección de datos atípico en precios
    p_comb$precio[p_comb$precio>50000]<-NA #AtĆ­pico como NA
    #Reemplazar NA con la mediana de los precios
    p_comb$precio <- replace_na(p_comb$precio,median(p_comb$precio,na.rm = T))
    boxplot(p_comb$precio) #Mejora la distribución de los datos

    Para abordar el dato atípico detectado en la variable precio, se utilizó la función filter del paquete dplyr para identificar los registros donde el precio era superior a 50,000. Estos registros se almacenaron en la variable atip para su revisión. Dado que el valor era considerablemente mayor que el rango típico de precios, se decidió tratarlo como un valor atípico y se reemplazó con NA utilizando p_comb$precio[p_comb$precio>50000]<-NA.

    Para evitar la pérdida de datos y mejorar la calidad del anÔlisis, los valores NA resultantes se reemplazaron por la mediana de los precios, calculada sin incluir los NA, mediante la función replace_na. Finalmente, se generó un nuevo diagrama de caja (boxplot) de la variable precio, observÔndose una mejora en la distribución de los datos tras el tratamiento del valor atípico.

    19.1 EstadĆ­sticas DescrĆ­ptivas

    Para analizar la evolución de los precios de los combustibles a lo largo de los meses y años, se creó una nueva variable periodo en el conjunto de datos p_comb, convirtiéndola en un factor para facilitar su uso en el anÔlisis. Posteriormente, se generó un resumen agrupando los datos por mes y periodo, calculando el precio promedio mensual (p_prom) para cada combinación.

    Este resumen fue visualizado utilizando un grĆ”fico de lĆ­neas mediante ggplot2. En el grĆ”fico, los meses se colocaron en el eje X y el precio promedio en el eje Y. Se utilizó una lĆ­nea discontinua de color azul cadete para representar la tendencia del precio promedio a lo largo de los meses. AdemĆ”s, se aplicó la tĆ©cnica de ā€œfacetadoā€ (facet_grid) para crear subgrĆ”ficos por cada periodo, lo que permite observar cómo varĆ­a el precio promedio a lo largo del tiempo en diferentes aƱos. El grĆ”fico se tituló ā€œPrecio promedio de combustibles mensualā€, y se ajustó la rotación del texto en el eje X para mejorar la legibilidad.

    library(ggplot2)
    
    p_comb_filtered <- p_comb %>%
      filter(precio < 20000)  # Ajusta el umbral segĆŗn sea necesario
    
    ggplot(p_comb_filtered, aes(x = producto, y = precio, fill = producto)) + 
      geom_boxplot(outlier.shape = NA) +  # Elimina la visualización de valores atípicos extremos
      labs(title = "Distribucion de Precios por Tipo de Combustible",
           x = "Producto",
           y = "Precio") +
      theme_minimal() +
      theme(legend.position = "none",
            axis.text.x = element_text(angle = 45, hjust = 1),  # Gira el texto para mejorar la legibilidad
            plot.title = element_text(hjust = 0.5)) +  # Centra el tĆ­tulo
      scale_y_continuous(labels = scales::comma)  # AƱade separadores de miles al eje y

    #Precios a travƩs de los meses y aƱos
    p_comb$periodo<-as.factor(p_comb$periodo)
    
    resum1 <- p_comb %>% group_by(mes,periodo) %>%
      summarise(p_prom=mean(precio))
    ## `summarise()` has grouped output by 'mes'. You can override using the `.groups`
    ## argument.
    ggplot(resum1, aes(x = mes, y = p_prom, group=1)) +
      geom_line(linetype="dashed",size=1.2, col="cadetblue") +
      ggtitle("Precio promedio de combustibles mensual")+
      xlab("AƱo")+
      ylab("precio")+
      facet_grid(cols = vars(periodo))+
      theme(axis.text.x = element_text(angle = 90, hjust = 1))

    La tendencia de los precios de los combustibles a lo largo del tiempo ha sido creciente. Los tres grƔficos revelan un incremento constante.

    Para analizar los precios promedios por departamento a lo largo de los meses, se creó un resumen del conjunto de datos p_comb agrupando por producto, periodo, y mes. En este resumen, se calculó el precio promedio (p_prom2) para cada combinación de estas variables.

    Este anĆ”lisis se visualizó utilizando un grĆ”fico de puntos con ggplot2, donde se representa el mes en el eje X y el precio promedio en el eje Y. Los puntos estĆ”n coloreados segĆŗn el tipo de producto, permitiendo diferenciar entre los distintos combustibles. Se utilizó la tĆ©cnica de ā€œfacetadoā€ (facet_grid) para crear subgrĆ”ficos por cada periodo, lo que permite observar las variaciones de precios a lo largo del tiempo para cada producto. El grĆ”fico se tituló ā€œPrecio promedio a travĆ©s de los meses, por departamentosā€, y se ajustó la rotación del texto en el eje X para mejorar la legibilidad.

    #Precios promedios por departamento
    
    resum2 <- p_comb %>% group_by(producto,periodo,mes) %>%
      summarise(p_prom2=mean(precio))
    ## `summarise()` has grouped output by 'producto', 'periodo'. You can override
    ## using the `.groups` argument.
    ggplot(resum2, aes(x=mes,y=p_prom2, color=producto))+
      geom_point(cex=3)+
      facet_grid(cols = vars(periodo))+
      theme(axis.text.x = element_text(angle = 90, hjust = 1))+
      xlab("mes")+
      ylab("precio promedio")+
      ggtitle("Precio promedio a travƩs de los meses, por departamentos")

    La gasolina extra oxigenada se ha mantenido a lo argo del tiempo con el mayor precio, seguida de la gasolina extra. El ACEM- Diesel económico es el combustible mÔs económico. Sin embargo, todos presentan una tendencia creciente en sus precios a lo largo del tiempo.

    #Distribución, precio del combustible según tipo:
    
    ggplot(resum2, aes(x=p_prom2, fill=producto))+
      geom_density(alpha=0.4)+
      xlab("Precio promedio")+
      ylab("Densidad")+
      ggtitle("Distribución precio promedio combustibles según tipo")

    Las distribuciones reflejan los rangos de precios variados que presenta cada tipo de combustible.

    ggplot(resum2, aes(x=p_prom2, fill=periodo))+
      geom_density(alpha=0.4)+
      xlab("Precio promedio")+
      ylab("Densidad")+
      ggtitle("Distribución precio promedio combustibles según periodo")

    Al mirar la distribución según los años, se puede apreciar nuevamente la tencdencia creciente.

    Analizando el comportamiento de la gasolina corriente

    g_corriente<-subset(p_comb,subset=(producto=="GASOLINA CORRIENTE")) #Filtrar base por gasolina corriente
    
    resum3 <- g_corriente %>% group_by(periodo,mes) %>%
      summarise(p_prom3=mean(precio))
    ## `summarise()` has grouped output by 'periodo'. You can override using the
    ## `.groups` argument.
    #Precio de la gasolina corriente en el tiempo
    
    ggplot(resum3, aes(x = mes, y = p_prom3, color=periodo)) +
      geom_point(stat="identity")+
      ggtitle("Precio promedio mensual gasolina corriente")+
      xlab("AƱo")+
      ylab("precio")+
      theme(axis.text.x = element_text(angle = 90, hjust = 1))

    require(plotly)
    
    plot_ly(resum3, x = ~mes, y = ~p_prom3, type = "scatter", mode = "markers",
            color = ~periodo, colors = "Set1") %>% 
      layout(title = "Precio promedio mensual gasolina corriente",
             xaxis = list(title = "mes"), 
             yaxis = list(title = "Precio promedio"))

    Al observar el comportamiento del precio promedio de la gasolina corriente, se puede observar un crecimiento alto en los meses de abril a mayo del año 2017, y el mes de julio dle mismo año, alcanzó el precio promedio mÔs elevado

    Precio promedio de la casolina corriente segĆŗn departamento

    resum4 <- g_corriente %>% group_by(periodo,nombre_departamento) %>%
      summarise(p_prom4=mean(precio))
    ## `summarise()` has grouped output by 'periodo'. You can override using the
    ## `.groups` argument.
    ggplot(resum4, aes(x=reorder(nombre_departamento,-p_prom4),y=p_prom4))+
      geom_bar(stat="identity", position=position_dodge(),fill="darkolivegreen")+
      theme(axis.text.x = element_text(angle = 90, hjust = 1))+
      ggtitle("Precio promedio gasolina corriente segĆŗn departamento")+
      xlab("Departamentos")+
      ylab("precio promedio")

    La presente grÔfica relaciona el precio promedio de la gasolina corriente según el departamento. Guainía dispone del precio promedio mÔs elevado, mientras Cesar y La Guajira los mÔs bajos. Esta informaciónn se puede apreciar mejor a través de los mapas que se crearÔn a continuación

    19.1.1 Georreferenciación: En el siguiente mapa se visualizarÔ el precio promedio de la GASOLINA CORRIENTE,durante el año 2019

    # Cargar las librerĆ­as necesarias
    library(sf)
    ## Linking to GEOS 3.13.0, GDAL 3.10.1, PROJ 9.5.1; sf_use_s2() is TRUE
    library(dplyr)
    library(leaflet)
    
    # Leer el archivo shapefile
    mapa_col <- st_read("COLOMBIA/COLOMBIA.shp", quiet = FALSE)
    ## Reading layer `COLOMBIA' from data source 
    ##   `C:\Users\Keyla Alba\OneDrive - Universidad del Norte\UNINORTE\2025-I\DATAVIZ\DataViz_R\COLOMBIA\COLOMBIA.shp' 
    ##   using driver `ESRI Shapefile'
    ## Simple feature collection with 33 features and 11 fields
    ## Geometry type: MULTIPOLYGON
    ## Dimension:     XY
    ## Bounding box:  xmin: -81.73575 ymin: -4.227907 xmax: -66.84735 ymax: 13.39453
    ## Geodetic CRS:  WGS 84
    # Corregir geometrƭas no vƔlidas
    mapa_col <- st_make_valid(mapa_col)

    En este anÔlisis, se comienza revisando la información de los departamentos contenida en el objeto mapa_col@data, que representa la estructura de datos asociada a un archivo shapefile (shp). Esta estructura incluye nombres de departamentos, identificadores geogrÔficos, y otras características relevantes para la visualización geoespacial.

    Primero, se extrajeron los nombres de los departamentos tanto desde la base de datos p_comb como desde el archivo shapefile, almacenƔndolos en las variables dpto_1 y dpto_2, respectivamente. Esto permite comparar y trabajar con los nombres en ambos conjuntos de datos.

    Para asegurar la coherencia y facilitar comparaciones posteriores, los nombres de los departamentos en ambas variables se convirtieron a letras minúsculas utilizando la función tolower. Este paso es crucial para evitar problemas de coincidencia debido a diferencias de mayúsculas y minúsculas entre los dos conjuntos de datos.

    # Extraer los nombres de los departamentos desde la base de datos y el shapefile
    dpto_1 <- tolower(p_comb$nombre_departamento)  # Base de datos
    dpto_2 <- tolower(mapa_col$DPTO_CNMBR)        # Archivo shapefile
    
    # Verificar quƩ nombres de la base de datos no estƔn en el shapefile
    M1 <- which(is.na(match(dpto_1, dpto_2)))
    
    
    # Corregir nombres que no coinciden entre la base de datos y el shapefile
    dpto_2 <- str_replace_all(dpto_2, "\\?", "Ʊ")
    dpto_2 <- str_replace_all(dpto_2, "archipielago de san andres", "san andres islas")
    
    # Agregar los nombres corregidos como códigos para unir las bases de datos
    p_comb$codigo <- dpto_1
    mapa_col$codigo <- dpto_2

    Para asegurar la consistencia entre los nombres de los departamentos en la base de datos p_comb y el archivo shapefile mapa_col, se utilizó la función match para comparar ambos conjuntos de nombres. Concretamente, se identificaron aquellos nombres en dpto_1 (extraídos de la base de datos p_comb) que no tenían una correspondencia en dpto_2 (extraídos del archivo shapefile). Los índices de estos nombres no coincidentes se almacenaron en M1, y los nombres correspondientes se listaron utilizando dpto_1[M1].

    Una vez identificados los nombres que no coincidĆ­an, se procedió a realizar las correcciones necesarias en dpto_2 para alinear ambas bases de datos. Esto incluyó reemplazar los caracteres ā€œ?ā€ por ā€œĆ±ā€ y modificar el nombre del departamento ā€œarchipielago de san andresā€ a ā€œsan andres islasā€ utilizando la función str_replace_all. Estas modificaciones aseguran que los nombres de los departamentos en ambas bases de datos sean consistentes y puedan ser utilizados sin problemas en anĆ”lisis posteriores.

    # Unir la base de datos y el archivo shapefile a través del código agregado
    datos_unidos <- merge(st_drop_geometry(mapa_col), p_comb, by = "codigo", all = TRUE, sort = FALSE)

    Después de realizar las correcciones necesarias en los nombres de los departamentos, se verificó que todos los nombres coincidieran entre las dos bases de datos (p_comb y mapa_col) comprobando que no quedaran valores NA en la comparación (dpto_1[M1]). Una vez confirmado que todos los nombres eran consistentes, se procedió a agregar estos nombres como códigos en ambas bases de datos, asignÔndolos a una nueva columna denominada codigo tanto en p_comb como en mapa_col@data.

    Este código sirvió como clave común para unir la base de datos p_comb con el archivo shapefile mapa_col. La unión se realizó utilizando la función merge, que combinó ambas bases de datos en un nuevo objeto llamado datos_unidos. Esta unión se realizó utilizando la columna codigo como clave, asegurando que los datos espaciales del shapefile y la información adicional de la base de datos estuvieran correctamente alineados.

    # Filtrar los datos para el aƱo 2019 y para el producto "GASOLINA CORRIENTE"
    datos_unidos2 <- datos_unidos %>%
      filter(periodo == "2019", producto == "GASOLINA CORRIENTE") %>%
      group_by(codigo, DPTO_NANO_, DPTO_NAREA, DPTO_CSMBL, DPTO_NANO, PAIS_PAIS_, SHAPE_Leng, SHAPE_Area) %>%
      summarize(precio_prom_dpto = mean(precio)) %>%
      ungroup()  # Desagrupar los datos para evitar problemas con leaflet
    ## `summarise()` has grouped output by 'codigo', 'DPTO_NANO_', 'DPTO_NAREA',
    ## 'DPTO_CSMBL', 'DPTO_NANO', 'PAIS_PAIS_', 'SHAPE_Leng'. You can override using
    ## the `.groups` argument.

    Para visualizar el promedio de los precios de la gasolina corriente en Colombia durante el aƱo 2019, se realizó una serie de filtrados y agrupaciones en la base de datos datos_unidos. Primero, se filtró el conjunto de datos para incluir Ćŗnicamente los registros correspondientes al aƱo 2019, utilizando la variable periodo. Luego, se aplicó un segundo filtro para seleccionar Ćŗnicamente los registros relacionados con el producto ā€œGASOLINA CORRIENTEā€.

    Una vez filtrados los datos, se procedió a agrupar la base por varias variables geoespaciales y administrativas, incluyendo codigo, DPTO_NANO_, DPTO_NAREA, DPTO_CSMBL, entre otras. Para cada grupo, se calculó el promedio del precio de la gasolina (precio_prom_dpto) utilizando la función summarize. Este proceso permitió colapsar la base de datos, generando un resumen que refleja el precio promedio de la gasolina corriente por departamento para todo el año 2019, lo que facilita su posterior representación en un mapa.

    # Construcción del mapa
    boxplot(datos_unidos2$precio_prom_dpto)

    max(datos_unidos2$precio_prom_dpto)  # Útil para definir los 'breaks'
    ## [1] 12413.64
    # Crear un factor para asignar colores basado en el precio promedio
    datos_unidos2$precio_prom_dpto <- as.numeric(datos_unidos2$precio_prom_dpto)
    precio_fac <- cut(datos_unidos2$precio_prom_dpto, 
                      breaks = c(0, 8000, 9000, 10000, 11000, 13000),
                      labels = c("a. 0-8.000", "b. 8.000-9.000", "c. 9.000-10.000", "d. 10.000-11.000", "e. 12.000-13.000"))

    Para construir un mapa que represente los precios promedios de la gasolina corriente por departamento en Colombia durante 2019, se inició con un anÔlisis exploratorio de la variable precio_prom_dpto en datos_unidos2. Se generó un diagrama de caja (boxplot) para visualizar la distribución de los precios promedio, lo cual ayudó a identificar la variabilidad y los posibles valores atípicos. AdemÔs, se utilizó la función max para identificar el valor mÔximo, que resultó útil para definir los puntos de corte (breaks) en la categorización de los precios.

    Posteriormente, se creó una variable precio_fac utilizando la función cut para clasificar los precios promedio en cinco categorías, basadas en los puntos de corte establecidos (0, 8000, 9000, 10000, 11000, 13000). Estas categorías facilitarÔn la asignación de colores en el mapa.

    Para cada categoría de precios, se asignaron colores específicos: verde para los precios mÔs bajos, seguido de amarillo, naranja, rojo, marrón, y negro para los precios mÔs altos. Esta codificación por colores permitirÔ una representación visual clara de las variaciones en los precios de la gasolina corriente a lo largo de los departamentos en Colombia.

    # Asignar colores manualmente
    colores <- c("a. 0-8.000" = "green",
                 "b. 8.000-9.000" = "yellow",
                 "c. 9.000-10.000" = "orange",
                 "d. 10.000-11.000" = "red",
                 "e. 12.000-13.000" = "brown")

    Para visualizar los precios promedios de la gasolina corriente por departamento en Colombia durante 2019 en un mapa interactivo, se llevó a cabo una serie de pasos utilizando leaflet. Primero, se ajustaron los nombres de los departamentos (DPTO_CNMBR) a un formato adecuado para su visualización, utilizando la función iconv para convertir la codificación de caracteres de ā€œUTF-8ā€ a ā€œISO_8859-1ā€.

    Luego, se creó una leyenda personalizada utilizando la función colorFactor, asignando colores específicos a intervalos de precios predefinidos (0-8,000, 8,000-9,000, 9,000-10,000, 10,000-11,000, y 12,000-13,000). Estos colores permiten una fÔcil interpretación visual de los datos en el mapa.

    Finalmente, se generó el mapa utilizando leaflet. Se aƱadieron las capas del mapa base (addTiles) y los polĆ­gonos que representan los departamentos (addPolygons), donde se aplicaron los colores definidos previamente en función del precio promedio. AdemĆ”s, se incluyó una leyenda en la esquina superior derecha (addLegend), que facilita la interpretación de los colores en relación con los rangos de precios, con el tĆ­tulo ā€œPrecio promedio de gasolina corriente en 2019ā€. Este mapa interactivo permite a los usuarios explorar visualmente las variaciones de precios por departamento en Colombia.

    # Verificar y asignar colores
    if (length(precio_fac) == nrow(datos_unidos2)) {
      datos_unidos2$color <- colores[precio_fac]
      cat("Colores asignados correctamente.\n")
    } else {
      stop("Error: La longitud de precio_fac no coincide con el nĆŗmero de filas en datos_unidos2.")
    }
    ## Colores asignados correctamente.
    # Fusionar la geometrĆ­a de `mapa_col` con `datos_unidos2`
    datos_unidos_final <- merge(mapa_col, datos_unidos2, by = "codigo")
    # Graficar el mapa con la leyenda
    leaflet(datos_unidos_final) %>% 
      addTiles() %>%
      addPolygons(popup = ~DPTO_CNMBR, color = "black", fillColor = ~color, weight = 1.1) %>%
      addLegend(position = "topright", 
                pal = colorFactor(palette = c("green", "yellow", "orange", "red", "brown"), 
                                  domain = levels(precio_fac)),
                values = levels(precio_fac),
                title = "Precio promedio de gasolina corriente en 2019")

    De esta manera es posible observar mediante rangos e información espacial, aquellos departamentos que presentaron precios mÔs elevados y mÔs bajos en cuanto al precio de la gasolina corriente durante el año 2019

    20 Introducción a PostgreSQL en R

    • Cuando se trata de grandes conjuntos de datos que potencialmente exceden la memoria de su mĆ”quina, es bueno tener otra posibilidad, como su propio servidor con una base de datos SQL/PostgreSQL, donde se puede consultar los datos. Por ejemplo, un conjunto de datos financieros de 5 GB caben en una memoria RAM bĆ”sica, pero los datos consumen muchos recursos. Una solución es utilizar una base de datos basada en SQL, donde puedo consultar los datos en trozos mĆ”s pequeƱos, dejando recursos para el cĆ”lculo.

    • Aunque MySQL es la mĆ”s utilizada, PostgreSQL tiene la ventaja de ser de código abierto y gratuita para todos los usos. Sin embargo, todavĆ­a tenemos que conseguir un servidor. Una forma posible de hacerlo es alquilar un servidor de Amazon, sin embargo, existe la opción de utilizar Render tal como se explicó en la sección de Python.

    21 Conexión con R

    • Ahora es el momento de conectarse a la base de datos con R. Este enfoque utiliza el paquete RPostgreSQL. Los siguientes paquetes y herramientas deben ser instalados para poder hacer uso de la API y realizar la conexión con Ć©xito:

      • RPostgresql
      • DBI
      • Su propia base de datos PostgreSQL
      • Acceso remoto a su base de datos
    • El paquete RPostgreSQL y DBI se puede instalar desde CRAN o Github.

    • Para conectar, necesitamos introducir los siguientes comandos en R, nos conectaremos a la base de datos creada anteriormente en Docker en la sección de Python. RPostgres es una interfaz compatible con DBI para la base de datos Postgres. Es una reescritura desde cero usando C++ y Rcpp.

    • Este paquete actĆŗa tanto como el controlador de la base de datos como la interfaz DBI. El código y la información adicional estĆ”n disponibles en su repositorio GitHub aquĆ­: RPostgres. Debe modificar la información que recibe la función dbConnect() por aquella que obtuvo al crear su base de datos en Docker.

    library(DBI)
    library(RPostgreSQL)
    
    #con <- dbConnect(RPostgres::Postgres(), 
     #               dbname = "undatascience-db", 
      #              host = "localhost", 
       #             port = 5432, 
        #            user = "undatascience-user", 
         #           password = "undatascience-password")
    • Para visualizar la lista de tablas que hemos importado antes en la base de datos utilizamos dbListTables():
    #dbListTables(conn = con)

    21.1 Actividad PrƔctica 1 - R:

    Considere la base de datos indicada en el enlace y con respecto a tasas de suicidios mundiales entre 1985 y 2016. La url es:

    https://www.kaggle.com/russellyates88/suicide-rates-overview-1985-to-2016

    La base de datos Suicide Rates Overview 1985 to 2016 se refiere a lo siguiente:

    Existen varias señales correlacionadas con el aumento de las tasas de suicidios mundial, este conjunto de datos fue creado para encontrar este tipo de señales y contiene 27,820 observaciones que aportan información tanto socioeconómica como demogrÔfica de cada país.

    Las variables se codificaron como:

    • suicides/100k: suicidios por cada 100 mil habitantes (tasas de suicidio).
    • country: paĆ­s.
    • year: aƱo.
    • sex: gĆ©nero (male, female).
    • age: edad (grupo de edad).
    • suicides_no: nĆŗmero de suicidios.
    • population: población.
    • country-year: clave compuesta paĆ­s-aƱo.
    • HDI: Ć­ndice de desarrollo humano (IDH) por aƱo.
    • gdp_for_year ($): producto interno bruto (PIB) por aƱo.
    • gdp_per_capita: producto interno bruto per capita.
    • generation: generación.

    21.1.1 Realice lo siguiente:

    1. Importe la base de datos.
    2. Analice las caracterĆ­sticas de la base de datos. Estas pueden incluir: nĆŗmero de filas, nĆŗmero de columnas, nombres de las variables, tipos de variables, entre otras.
    3. Analice cada una de las variables según su tipo: numéricas y categóricas.
    4. Filtre la base de datos para entender mejor su estructura.
    5. Explore la ayuda de la función table del paquete base y utilícela para explorar la base de datos.
    6. Identifique los valores NA (Not Available) en la base de datos.
    7. Analice la presencia de posibles valores atĆ­picos.
    8. Decida quƩ hacer con los valores NA.

    21.2 Actividad prƔctica #2

    Considere la base de datos master y realice lo siguiente (utilice los operadores pipe de continuidad y compuesto):

    1. Edite y explore reglas para verificar que la base de datos no contenga posibles registros erróneos.

    2. Filtre los datos de Colombia y los de EEUU, generando dos bases de datos, llamadas master_col y master_eu.

    3. Realice un anÔlisis de la evolución de los suicidios por cada 100.000 habitantes, del PIB per cÔpita y del IDH, a lo largo de los años en ambos países.

    4. Realice un anÔlisis de la evolución de los suicidios por cada 100.000 habitantes, del PIB per cÔpita y del IDH, a lo largo de los años en ambos países por género.

    5. Realice un anÔlisis de la evolución de los suicidios por cada 100.000 habitantes, del PIB per cÔpita y del IDH, a lo largo de los años en ambos países por grupo de edad.

    21.3 Actividad PrƔctica Evaluativa #3

    1. Realice un anÔlisis descriptivo completo y actualizado 8 de septiembre de 2025, incluyendo graficos, tablas y georreferenciación de la base de datos Accidentalidad_en_Barranquilla.csv. Deben entregar un script .R con los códigos usados.

    Este anƔlisis puede consistir en:

    1.1 Contextualizar tanto la base de datos como las variables describiendo en quƩ consiste cada una de ellas. La base de datos estƔ en el sitio web:
    https://www.datos.gov.co/Transporte/Accidentalidad-en-Barranquilla/yb9r-2dsi

    1.2. Analizar las caracterĆ­sticas de la base de datos. Estas pueden incluir: nĆŗmero de filas, nĆŗmero de columnas, nombres de las variables, tipos de variables, entre otros.

    1.3. Analizar cada una de las variables según su tipo: numéricas y categóricas.

    1.4. Filtrar la base de datos para entender mejor su estructura. Aplique filtros en al menos cinco oportunidades.

    1.5. Utilice la función table para explorar la base de datos.

    1.6. Identifique los valores NA (Not Available) en la base de datos.

    1.7. Analice la presencia de posibles valores atĆ­picos.

    • Aplique imputación de datos usando las siguientes opciones para method:

      • method="pmm"
      • method="norm.predict"
      • method="norm.nob"
      • method="norm"
    • Identifique datos atĆ­picos para cada variable en el dataset usando las tĆ©cnicas estudiadas en clase. AdemĆ”s, realice imputación de los datos atĆ­picos con base en lo desarrollado en el Ć­tem anterior.

      • Imputación/Capping/Predicción
      • Test de Rosner, Dixon, Grubbs, Hampel, Percentiles, Boxplots, Histogramas, Descriptivos
    1. Realice una exploración y un anÔlisis descriptivo completo (incluyendo tablas de resumen y grÔficos) de la base de datos disponible en este enlace. Incluya grÔficos que presenten los datos sobre el mapa de Colombia, para visualizar la distribución geogrÔfica de los precios de los combustibles en el país (2018-2025). Puede utilizar el año mas reciente y completo, ese año deberia ser 2024 (al segundo trimestre).

    22 Visualización con plotly y shiny

    22.1 Introducción a plotly

    • Cualquier grĆ”fico hecho con el paquete R de plotly es impulsado por la biblioteca JavaScript plotly.js.
      La función plot_ly() proporciona una interfaz ā€˜directa’ a plotly.js con algunas abstracciones adicionales para ayudar a reducir la escritura.
      Estas abstracciones, inspiradas en la gramƔtica de los grƔficos y ggplot2, hacen que sea mucho mƔs rƔpido iterar de un grƔfico a otro,
      facilitando el descubrimiento de caracterĆ­sticas interesantes en los datos (Wilkinson 2005; Wickham 2009).

      Para demostrarlo, usaremos plot_ly() para explorar el conjunto de datos diamonds de ggplot2 y aprenderemos un poco cómo funciona plotly.

    library(plotly)
    library(ggplot2)
    
    data(diamonds, package = "ggplot2")
    knitr::kable(head(diamonds))
    carat cut color clarity depth table price x y z
    0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
    0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
    0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
    0.29 Premium I VS2 62.4 58 334 4.20 4.23 2.63
    0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
    0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
    library(knitr)
    
    # Crear la tabla con kable
    data <- data.frame(
      Variable = c("price", "carat", "cut", "color", "clarity", "x", "y", "z", "depth", "table"),
      Description = c(
        "price in US dollars",
        "weight of the diamond",
        "quality of the cut",
        "diamond color",
        "measurement of how clear the diamond is",
        "length in mm",
        "width in mm",
        "depth in mm",
        "total depth percentage",
        "width of top of diamond relative to widest point"
      ),
      Values = c(
        "$326-$18,823",
        "0.2-5.01",
        "Fair, Good, Very Good, Premium, Ideal",
        "J (worst) to D (best)",
        "I1 (worst), SI2, SI1, VS2, VS1, VVS2, VVS1, IF (best)",
        "0-10.74",
        "0-58.9",
        "0-31.8",
        "43-79",
        "43-95"
      )
    )
    
    # Mostrar la tabla en formato HTML
    kable(data, format = "html", align = "l")
    Variable Description Values
    price price in US dollars $326-$18,823
    carat weight of the diamond 0.2-5.01
    cut quality of the cut Fair, Good, Very Good, Premium, Ideal
    color diamond color J (worst) to D (best)
    clarity measurement of how clear the diamond is I1 (worst), SI2, SI1, VS2, VS1, VVS2, VVS1, IF (best)
    x length in mm 0-10.74
    y width in mm 0-58.9
    z depth in mm 0-31.8
    depth total depth percentage 43-79
    table width of top of diamond relative to widest point 43-95
    • Si asignamos nombres de variables (p.Ā ej., cut, clarity, etc.) a propiedades visuales (p.Ā ej., x, y, color, etc.) dentro de plot_ly(), como se ve en la figura,
      se intenta encontrar una representación geométrica sensata de esa información por nosotros.
      En breve veremos cómo especificar estas representaciones geométricas (así como otras codificaciones visuales)
      para crear diferentes tipos de grƔficos.
    plot_ly(diamonds, x = ~cut)
    ## No trace type specified:
    ##   Based on info supplied, a 'histogram' trace seems appropriate.
    ##   Read more about this trace type -> https://plotly.com/r/reference/#histogram
    plot_ly(diamonds, x = ~cut, y = ~clarity)
    ## No trace type specified:
    ##   Based on info supplied, a 'histogram2d' trace seems appropriate.
    ##   Read more about this trace type -> https://plotly.com/r/reference/#histogram2d
    plot_ly(diamonds, x = ~cut, color = ~clarity, colors = "Accent")
    ## No trace type specified:
    ##   Based on info supplied, a 'histogram' trace seems appropriate.
    ##   Read more about this trace type -> https://plotly.com/r/reference/#histogram

    23 Introducción a ggplotly

    • La función ggplotly() del paquete plotly tiene la capacidad de traducir ggplot2 a plotly.
      Esta funcionalidad puede ser realmente útil para añadir rÔpidamente interactividad a su flujo de trabajo ggplot2 existente.
      AdemƔs, incluso si conoce bien plot_ly() y ggplotly(), puede seguir siendo deseable para crear visualizaciones
      que no son necesariamente fÔciles de lograr sin ella. Para demostrarlo, vamos a explorar la relación entre price
      y otras variables del conocido conjunto de datos diamonds.

    • El binning hexagonal (es decir, geom_hex()) es una forma Ćŗtil de visualizar una densidad 2D,
      como la relación entre price y carat, como se muestra en la siguiente figura, donde podemos ver que existe una fuerte
      relación lineal positiva entre el logaritmo de los quilates y el precio.
      También muestra que, para muchos, el quilate sólo se redondea a un número determinado, indicado por las bandas azul claro.
      Hacer este grÔfico interactivo facilita la decodificación de los colores hexagonales en los recuentos que representan.

    library(ggplot2)
    library(plotly)
    library(hexbin)
    
    p <- ggplot(diamonds, aes(x = log(carat), y = log(price))) + 
      geom_hex(bins = 100)
    
    ggplotly(p)
    • Es bueno utilizar ggplotly() sobre plot_ly() para aprovechar la interfaz consistente y expresiva de ggplot2
      para explorar resĆŗmenes estadĆ­sticos entre grupos.
      Por ejemplo, al incluir una variable de color discreta (e.g., cut) con geom_freqpoly(),
      se obtiene un polĆ­gono de frecuencia para cada nivel de esa variable.
      Esta capacidad de generar rÔpidamente codificaciones visuales de resúmenes estadísticos
      a través de un número arbitrario de grupos funciona bÔsicamente para cualquier geom
      (e.g., geom_boxplot(), geom_histogram(), geom_density(), etc.) y es una caracterĆ­stica clave de ggplot2.

    24 Barras e histogramas

    • Las funciones add_bars() y add_histogram() envuelven los tipos de trazado de barras e histogramas de plotly.js.
      La principal diferencia entre ellas es que las trazas de barras requieren alturas de barras (tanto x como y),
      mientras que las trazas de histograma requieren sólo una variable, y plotly.js maneja el binning en el navegador.
      Y quizÔs de forma confusa, ambas funciones pueden utilizarse para visualizar la distribución de una variable numérica o discreta.
      Así que, esencialmente, la única diferencia entre ellas es dónde se produce el binning.

    • La siguiente figura compara el algoritmo de agrupación por defecto en plotly.js con algunos algoritmos diferentes
      disponibles en R a través de la función hist().
      Aunque plotly.js tiene la capacidad de personalizar los bins del histograma a travƩs de xbins/ybins,
      R tiene diversas facilidades para estimar el número óptimo de bins en un histograma que podemos aprovechar fÔcilmente.
      La función hist() por sí sola nos permite referenciar 3 algoritmos famosos por su nombre (Sturges 1926; Freedman-Diaconsis 1981
      y Scott 1979), pero tambiƩn hay paquetes (por ejemplo, el paquete histogram) que amplƭan esta interfaz
      para incorporar mƔs metodologƭa (Mildenberger, Rozenholc y Zasada. 2009).
      La función price_hist() que aparece a continuación envuelve la función hist() para obtener los resultados del binning,
      y mapear esos bins a una versión graficada del histograma usando add_bars().

    p1 <- plot_ly(diamonds, x = ~price) %>%
      add_histogram(name = "plotly.js")
    
    price_hist <- function(method = "FD") {
      h <- hist(diamonds$price, breaks = method, plot = FALSE)
      plot_ly(x = h$mids, y = h$counts) %>% add_bars(name = method)
    }
    
    subplot(
      p1, price_hist(), price_hist("Sturges"),  price_hist("Scott"),
      nrows = 4, shareX = TRUE
    )
    • La siguiente figura muestra dos formas de crear un grĆ”fico de barras bĆ”sico.
      Aunque los resultados visuales son los mismos, vale la pena señalar la diferencia en la implementación.
      La función add_histogram() envía todos los valores observados al navegador y deja que plotly.js realice el binning.

    Se necesita mƔs esfuerzo humano para realizar el binning en R, pero hacerlo tiene la ventaja de enviar menos datos,
    y requiere menos trabajo de cƔlculo del navegador web.

    En este caso, sólo tenemos unos 50,000 registros, por lo que no hay mucha diferencia en los tiempos de carga o el tamaño de la pÔgina.
    Sin embargo, con 1 millón de registros, el tiempo de carga de la pÔgina es mÔs del doble y el tamaño de la pÔgina casi se duplica.

    library(dplyr)
    p1 <- plot_ly(diamonds, x = ~cut) %>%
      add_histogram()
    
    p2 <- diamonds %>%
      count(cut) %>%
      plot_ly(x = ~cut, y = ~n) %>% 
      add_bars()
    
    subplot(p1, p2) %>% hide_legend()
    • A menudo es Ćŗtil ver cómo cambia la distribución numĆ©rica con respecto a una variable discreta.
      Cuando se utilizan barras para visualizar múltiples distribuciones numéricas,
      se recomienda trazar cada distribución en su propio eje utilizando una pantalla de múltiplos pequeños,
      en lugar de intentar superponerlas en un solo eje.

    Obsérvese cómo la función one_plot() define lo que debe mostrarse en cada panel,
    y luego se emplea una estrategia de dividir-aplicar-recombinar (es decir, split(), lapply(), subplot())
    para generar la visualización del enrejado.

    one_plot <- function(d) {
      plot_ly(d, x = ~price) %>%
        add_annotations(
          ~unique(clarity), x = 0.5, y = 1, 
          xref = "paper", yref = "paper", showarrow = FALSE
        )
    }
    
    diamonds %>%
      split(.$clarity) %>%
      lapply(one_plot) %>% 
      subplot(nrows = 2, shareX = TRUE, titleX = FALSE) %>%
      hide_legend()
    ## No trace type specified:
    ##   Based on info supplied, a 'histogram' trace seems appropriate.
    ##   Read more about this trace type -> https://plotly.com/r/reference/#histogram
    ## No trace type specified:
    ##   Based on info supplied, a 'histogram' trace seems appropriate.
    ##   Read more about this trace type -> https://plotly.com/r/reference/#histogram
    ## No trace type specified:
    ##   Based on info supplied, a 'histogram' trace seems appropriate.
    ##   Read more about this trace type -> https://plotly.com/r/reference/#histogram
    ## No trace type specified:
    ##   Based on info supplied, a 'histogram' trace seems appropriate.
    ##   Read more about this trace type -> https://plotly.com/r/reference/#histogram
    ## No trace type specified:
    ##   Based on info supplied, a 'histogram' trace seems appropriate.
    ##   Read more about this trace type -> https://plotly.com/r/reference/#histogram
    ## No trace type specified:
    ##   Based on info supplied, a 'histogram' trace seems appropriate.
    ##   Read more about this trace type -> https://plotly.com/r/reference/#histogram
    ## No trace type specified:
    ##   Based on info supplied, a 'histogram' trace seems appropriate.
    ##   Read more about this trace type -> https://plotly.com/r/reference/#histogram
    ## No trace type specified:
    ##   Based on info supplied, a 'histogram' trace seems appropriate.
    ##   Read more about this trace type -> https://plotly.com/r/reference/#histogram

    La visualización de las distribuciones discretas múltiples es difícil.
    Esta sutil complejidad se debe a que tanto los recuentos como las proporciones
    son importantes para comprender las distribuciones discretas multivariadas.

    La figura siguiente presenta los recuentos de diamantes,
    divididos por su talla y su claridad, mediante un grƔfico de barras agrupadas.

    plot_ly(diamonds, x = ~cut, color = ~clarity) %>%
      add_histogram()

    25 Shiny

    • Hay varios marcos diferentes para crear aplicaciones web a travĆ©s de R, pero centraremos nuestra atención en la vinculación de grĆ”ficos plotly con shiny, un paquete de R para crear aplicaciones web reactivas completamente en R. El modelo de programación reactiva de Shiny permite a los programadores de R aprovechar sus conocimientos existentes de R y crear aplicaciones web basadas en datos sin ninguna experiencia previa en programación web. Shiny en sĆ­ mismo es en gran medida agnóstico al motor utilizado para renderizar las vistas de datos (es decir, puede incorporar cualquier tipo de salida de R), pero el propio shiny tambiĆ©n agrega algĆŗn soporte especial para interactuar con grĆ”ficos e imĆ”genes estĆ”ticas de R (Chang 2017).

    • Al vincular los grĆ”ficos en una aplicación web, hay compensaciones a considerar cuando se utilizan grĆ”ficos estĆ”ticos de R en lugar de grĆ”ficos basados en la web. Resulta que esas compensaciones se complementan muy bien con las fortalezas y debilidades relativas de la vinculación de vistas con plotly, lo que hace que su combinación sea un potente conjunto de herramientas para vincular vistas en la web desde R. El propio Shiny proporciona una forma de acceder a eventos con grĆ”ficos estĆ”ticos hechos con cualquiera de los siguientes paquetes de R: graphics, ggplot2 y lattice. Estos paquetes son muy maduros, con todas las funciones bien probadas, y soportan una gama increĆ­blemente amplia de grĆ”ficos, pero como deben ser regenerados en el servidor, son fundamentalmente limitados desde una perspectiva de grĆ”ficos interactivos.

    • Comparativamente, plotly no tiene la misma gama e historia, pero proporciona mĆ”s opciones y control sobre la interactividad. MĆ”s concretamente, debido a que plotly estĆ” intrĆ­nsecamente basado en la web, permite un mayor control sobre cómo se actualizan los grĆ”ficos en respuesta a la entrada del usuario (por ejemplo, cambiar el color de unos pocos puntos en lugar de redibujar toda la imagen). Esta sección aborda el cómo utilizar grĆ”ficos plotly dentro de shiny, cómo conseguir que esos grĆ”ficos se comuniquen con otros tipos de vistas de datos, y cómo hacerlo todo de forma eficiente.

    26 Su primera aplicación Shiny

    • El patrón mĆ”s comĆŗn de plotly+shiny utiliza una entrada de shiny para controlar una salida de plotly. Mostraremos un ejemplo sencillo de uso de la función selectizeInput() de shiny para crear un desplegable que controla un grĆ”fico de plotly. Este ejemplo, asĆ­ como cualquier otra aplicación shiny, tiene dos partes principales:

      • La interfaz de usuario, ui, define cómo se muestran los widgets de entrada y salida en la pĆ”gina. La función fluidPage() ofrece una forma agradable y rĆ”pida de obtener un diseƱo responsivo basado en una cuadrĆ­cula, pero tambiĆ©n vale la pena seƱalar que la UI es completamente personalizable, y paquetes como shinydashboard facilitan el aprovechamiento de marcos de diseƱo mĆ”s sofisticados (Chang y Borges Ribeiro).

    26.1 Función del servidor

    La función del servidor, server, define un mapeo de los valores de entrada a los widgets de salida. MÔs concretamente, el servidor shiny es una R function() entre los valores ingresados por el cliente y las salidas generadas en el servidor web.

    26.2 Widgets de entrada

    Cada widget de entrada, incluido el selectizeInput(), estÔ vinculado a un valor de entrada al que se puede acceder en el servidor dentro de una expresión reactiva. Las expresiones reactivas de Shiny construyen un grÔfico de dependencia entre las salidas (también conocidas como puntos finales reactivos) y las entradas (también conocidas como fuentes reactivas). La verdadera potencia de las expresiones reactivas reside en su capacidad para encadenar y almacenar en caché los cÔlculos, pero nos centraremos primero en la generación de salidas. Para generar una salida, tienes que elegir una función adecuada para renderizar el resultado de una expresión reactiva.

    26.3 Ejemplo usando plotly

    Consideraremos el siguiente dataset txhousing para este ejemplo. La siguiente rutina utiliza la función renderPlotly() para renderizar una expresión reactiva que genera un grÔfico plotly. Esta expresión depende del valor de entrada input$cities (es decir, el valor de entrada ligado al widget de entrada con un inputId de "cities") y almacena la salida como output$p. Esto indica a shiny que inserte el grÔfico reactivo en el contenedor plotlyOutput(outputId = "p") definido en la interfaz de usuario.

    str(txhousing)
    ## tibble [8,602 Ɨ 9] (S3: tbl_df/tbl/data.frame)
    ##  $ city     : chr [1:8602] "Abilene" "Abilene" "Abilene" "Abilene" ...
    ##  $ year     : int [1:8602] 2000 2000 2000 2000 2000 2000 2000 2000 2000 2000 ...
    ##  $ month    : int [1:8602] 1 2 3 4 5 6 7 8 9 10 ...
    ##  $ sales    : num [1:8602] 72 98 130 98 141 156 152 131 104 101 ...
    ##  $ volume   : num [1:8602] 5380000 6505000 9285000 9730000 10590000 ...
    ##  $ median   : num [1:8602] 71400 58700 58100 68600 67300 66900 73500 75000 64500 59300 ...
    ##  $ listings : num [1:8602] 701 746 784 785 794 780 742 765 771 764 ...
    ##  $ inventory: num [1:8602] 6.3 6.6 6.8 6.9 6.8 6.6 6.2 6.4 6.5 6.6 ...
    ##  $ date     : num [1:8602] 2000 2000 2000 2000 2000 ...
    library(shiny)
    library(plotly)
    
    ui <- fluidPage(
      selectizeInput(
        inputId = "cities", 
        label = "Select a city", 
        choices = unique(txhousing$city), 
        selected = "Abilene",
        multiple = TRUE
      ),
      plotlyOutput(outputId = "p")
    )
    
    server <- function(input, output) {
      output$p <- renderPlotly({
        plot_ly(txhousing, x = ~date, y = ~median) %>%
          filter(city %in% input$cities) %>%
          group_by(city) %>%
          add_lines()
      })
    }
    
    shinyApp(ui, server)
    Shiny applications not supported in static R Markdown documents

    26.4 Otros Widgets de salida

    Si, en lugar de un grÔfico plotly, una expresión reactiva genera un grÔfico R estÔtico, simplemente utilice renderPlot() (en lugar de renderPlotly()) para renderizarlo y plotOutput() (en lugar de plotlyOutput()) para posicionarlo. Otros widgets de salida shiny utilizan esta convención de nombres:

    • renderDataTable()/datatableOutput(),
    • renderPrint()/verbatimTextOutput(),
    • renderText()/textOutput(),
    • renderImage() etc.

    Los paquetes que se basan en el estÔndar htmlwidgets (por ejemplo, plotly y leaflet) también siguen esta convención (renderPlotly()/plotlyOutput() y renderLeaflet()/leafletOutput()).

    26.5 Widgets de entrada adicionales

    Shiny también viene con otros widgets de entrada útiles, que pueden estilizarse fÔcilmente con CSS o SASS, e incluso integrarse con widgets personalizados:

    • selectInput()/selectizeInput() para menĆŗs desplegables.
    • numericInput() para ingresar un solo nĆŗmero.
    • sliderInput() para seleccionar un rango numĆ©rico.
    • textInput() para ingresar texto.
    • dateInput() para seleccionar una fecha.
    library(shiny)
    
    cities <- unique(txhousing$city)
    
    ui <- fluidPage(
      selectizeInput(
        inputId = "cities", 
        label = NULL,
        choices = c("Please choose a city" = "", cities), 
        multiple = TRUE
      ),
      plotlyOutput(outputId = "p")
    )
    
    server <- function(input, output, session) {
      output$p <- renderPlotly({
        req(input$cities)
        if (identical(input$cities, "")) return(NULL)
        p <- ggplot(data = filter(txhousing, city %in% input$cities)) + 
          geom_line(aes(date, median, group = city))
        height <- session$clientData$output_p_height
        width <- session$clientData$output_p_width
        ggplotly(p, height = height, width = width)
      })
    }
    
    shinyApp(ui, server)
    Shiny applications not supported in static R Markdown documents
    • dateRangeInput() para seleccionar un rango de fechas.
    • fileInput() para subir archivos.
    • checkboxInput()/checkboxGroupInput()/radioButtons() para elegir opciones.

    Nuestro enfoque ahora serÔ enlazar múltiples grÔficos en shiny mediante la manipulación directa, centrÔndonos en el uso de plotly y grÔficos estÔticos de R.

    26.6 Ocultación y rediseño al cambiar de tamaño

    La función renderPlotly() renderiza objetos compatibles con plotly_build(), incluyendo plot_ly(), ggplotly() y grÔficos de ggplot2. También puede renderizar NULL como un div HTML vacío, útil cuando no tiene sentido mostrar un grÔfico inmediatamente. Por ejemplo, puedes mostrar un marcador de posición mientras se seleccionan ciudades con selectizeInput(), y luego renderizar un grÔfico interactivo con ggplotly().

    AdemÔs, la salida de plotly puede depender del tamaño del contenedor que la alberga. Por defecto, el grÔfico cambia de tamaño únicamente desde el lado del cliente, pero es posible re-ejecutar la expresión reactiva al cambiar la ventana del navegador, mejorando el comportamiento del grÔfico con ggplotly(). Esto debe usarse con precaución con grandes conjuntos de datos o largos tiempos de cÔlculo. La función req() garantiza que los datos estén disponibles antes de realizar cÔlculos o acciones.

    26.7 Eventos de alcance

    plotly permite acceder a eventos para informar a otras vistas sobre interacciones. La función event_data() utiliza un argumento fuente (ID) para determinar qué vista actúa como fuente del evento. Cuando este ID coincide con el grÔfico de plot_ly()/ggplotly(), event_data() se asigna a esa vista específica.

    Por ejemplo, al hacer clic en una celda de un mapa de calor de correlación, se genera un grÔfico de dispersión para analizar variables en detalle. Los eventos vinculados a un evento plotly_click contienen categorías relevantes como x, y y z. Es crucial que el argumento fuente de event_data() coincida con la fuente de plot_ly() para evitar errores.

    knitr::kable(head(mtcars))
    mpg cyl disp hp drat wt qsec vs am gear carb
    Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
    Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
    Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
    Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
    Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
    Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

    Este dataset de ejemplo (mtcars) puede usarse para ilustrar cómo ajustar modelos lineales con la función lm tras interactuar con grÔficos reactivos.

    library(shiny)
    
    # cache computation of the correlation matrix
    correlation <- round(cor(mtcars), 3)
    
    ui <- fluidPage(
      plotlyOutput("heat"),
      plotlyOutput("scatterplot")
    )
    
    server <- function(input, output, session) {
      
      output$heat <- renderPlotly({
        plot_ly(source = "heat_plot") %>%
          add_heatmap(
            x = names(mtcars), 
            y = names(mtcars), 
            z = correlation
          )
      })
      
      output$scatterplot <- renderPlotly({
        # if there is no click data, render nothing!
        clickData <- event_data("plotly_click", source = "heat_plot")
        if (is.null(clickData)) return(NULL)
        
        # Obtain the clicked x/y variables and fit linear model
        vars <- c(clickData[["x"]], clickData[["y"]])
        d <- setNames(mtcars[vars], c("x", "y"))
        yhat <- fitted(lm(y ~ x, data = d))
        
        # scatterplot with fitted line
        plot_ly(d, x = ~x) %>%
          add_markers(y = ~y) %>%
          add_lines(y = ~yhat) %>%
          layout(
            xaxis = list(title = clickData[["x"]]), 
            yaxis = list(title = clickData[["y"]]), 
            showlegend = FALSE
          )
      })
      
    }
    
    shinyApp(ui, server)
    Shiny applications not supported in static R Markdown documents

    26.8 Drill-down

    Una aplicación de tipo drill-down permite al usuario explorar datos en niveles mÔs específicos. Por ejemplo, un grÔfico circular puede mostrar ventas por categoría (Furniture, Office Supplies, Technology). Al hacer clic sobre una porción del grÔfico, se puede profundizar en las subcategorías de la categoría seleccionada.

    La implementación requiere mantener el estado actual de la categorĆ­a seleccionada utilizando un reactiveVal() y actualizar este valor al hacer clic en una categorĆ­a o al pulsar un botón como ā€œBackā€. La función reactive() se utiliza para envolver expresiones normales creando asĆ­ una expresión reactiva cuyo resultado cambia dinĆ”micamente con el tiempo.

    Este ejemplo utiliza el dataset sales para ilustrar cómo implementar drill-down en aplicaciones Shiny con grÔficos interactivos.

    Un desglose bÔsico como este es útil por sí mismo, pero se vuelve aún mÔs potente cuando se combina con múltiples vistas relacionadas de los datos. Por ejemplo, se podría ampliar la aplicación para mostrar las ventas a lo largo del tiempo tanto por categoría como por subcategoría. Manteniendo el estado con reactiveVal(), las diferentes vistas pueden responder automÔticamente según la categoría seleccionada, mostrando ventas generales cuando no hay categoría seleccionada y detalles específicos cuando se elige una categoría particular.

    library(shiny)
    library(dplyr)
    library(readr)
    library(plotly)
    library(purrr)
    
    sales <- read_csv("https://plotly-r.com/data-raw/sales.csv")
    ## Rows: 9994 Columns: 5
    ## ── Column specification ────────────────────────────────────────────────────────
    ## Delimiter: ","
    ## chr  (3): id, category, sub_category
    ## dbl  (1): sales
    ## dttm (1): order_date
    ## 
    ## ℹ Use `spec()` to retrieve the full column specification for this data.
    ## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
    categories <- unique(sales$category)
    
    ui <- fluidPage(
      plotlyOutput("bar"),
      uiOutput("back"),
      plotlyOutput("time")
    )
    
    server <- function(input, output, session) {
      
      current_category <- reactiveVal()
      
      # report sales by category, unless a category is chosen
      sales_data <- reactive({
        if (!length(current_category())) {
          return(count(sales, category, wt = sales))
        }
        sales %>%
          filter(category %in% current_category()) %>%
          count(sub_category, wt = sales)
      })
      
      # the pie chart
      output$bar <- renderPlotly({
        d <- setNames(sales_data(), c("x", "y"))
        
        plot_ly(d) %>%
          add_bars(x = ~x, y = ~y, color = ~x) %>%
          layout(title = current_category() %||% "Total Sales")
      })
      
      # same as sales_data
      sales_data_time <- reactive({
        if (!length(current_category())) {
          return(count(sales, category, order_date, wt = sales))
        }
        sales %>%
          filter(category %in% current_category()) %>%
          count(sub_category, order_date, wt = sales)
      })
      
      output$time <- renderPlotly({
        d <- setNames(sales_data_time(), c("color", "x", "y"))
        plot_ly(d) %>%
          add_lines(x = ~x, y = ~y, color = ~color)
      })
      
      # update the current category when appropriate
      observe({
        cd <- event_data("plotly_click")$x
        if (isTRUE(cd %in% categories)) current_category(cd)
      })
      
      # populate back button if category is chosen
      output$back <- renderUI({
        if (length(current_category())) 
          actionButton("clear", "Back", icon("chevron-left"))
      })
      
      # clear the chosen category on back button press
      observeEvent(input$clear, current_category(NULL))
    }
    
    shinyApp(ui, server)
    Shiny applications not supported in static R Markdown documents

    26.9 Despliegue de aplicación Shiny

    La plataforma shinyapps.io es ampliamente utilizada para desplegar aplicaciones Shiny. Primero, debe crear una cuenta en shinyapps.io, la cual ofrece un plan gratuito limitado a 5 aplicaciones activas y hasta 25 horas activas mensuales. Si se requiere mayor uso, serĆ” necesario cambiar a un plan pago.

    1. Abra RStudio y cree una nueva aplicación Shiny.

    1. Asignele un nombre (sin espacio), elige dónde guardarlo y haz clic en el botón Create

    1. Luego de esto copie en la app por defecto el código por ejemplo de la anterior app creada para el drill-down. De la misma manera que cuando se abre un nuevo documento R Markdown, se crea el código para una aplicación Shiny bÔsica. Ejecute la aplicación haciendo clic en el botón Run App para ver el resultado.

    1. Se abrirÔ la app. Para publicarla, debemos hacer click en el botón Publish

    Luego de esto hacemos click en ShinyApps.io para conectarnos a nuestra cuenta, creada anteriormente

    1. Luego de esto necesitaremos un token para poder conectar nuestra app al servidor de ShinyApps.io. Con este fin, seguimos las instrucciones que aparecen en la ventana; es decir, vamos a nuestra cuenta, hacemos clic en nuestro nombre de usuario, luego presionamos el botón Tokens, después hacemos clic en Show para visualizar el token y finalmente clic en Show secret/Copy to clipboard. Una vez realizado esto, pegamos nuestro token en la ventana de publicación de nuestra app.

    6. Luego le va a aparecer la siguiente ventana con la opción de publicar su aplicación. Haga click en Publish

    7. Después de varios segundos (dependiendo del peso de su aplicación), la aplicación Shiny debería aparecer en su navegador de Internet. Para volver a desplegar la aplicación puede hacerlo desde su aplicación haciendo click en el botón resaltado en la imagen y deberÔ visualizar su despliegue en la parte inferior. Ahora puede realizar cambio y usar sólo ese botón cuando desde hacer el despliegue de su aplicación.

    1. Puede revisar en su cuenta que se ha creado satisfactoriamente su aplicación. Puede visualizar las conexiones y demÔs información en los botones que se resaltan en rojo. Revise que información entrega cada uno.