Medidas de Centralidad de Dispersión y de Forma

Soy Jeiner Velandia, licenciado en Psicología y Pedagogía, con formación en maestría en TI aplicadas a la educación. Actualmente curso un doctorado en Gestión de la Innovación Tecnológica y una especialización en Métodos Estadísticos para el Análisis de Datos.

En este documento presento los 6 pasos de la actividad: instalación de paquetes, selección y limpieza de datos, creación de data frames, cálculo de medidas de localización, dispersión y forma, y finalmente la generación de gráficos para el análisis.

1. Instalación Paquetes

Preámbulo. Ajusto el sistema con options(digits = 4) para que todos los números en consola se muestren con máximo cuatro decimales. Esto facilita la lectura de resultados y evita valores demasiado largos que pueden ser confusos.

Paso 1. Instalo y cargo librerías esenciales: nycflights13 trae la base de datos de vuelos, dplyr sirve para manipular tablas, ggplot2 para graficar, e1071 y DescTools para calcular asimetría y curtosis, y readr para importar o exportar CSV. Se usan install.packages() y luego library() para tenerlas listas.

#Ajusto el sistema para que todos los datos se vean máximo 4 décimales
options(digits = 4)

#========================================== PASO 1 ===========================
# Instalo los paquetes 

install.packages("nycflights13") # Base de datos de vuelos de NY en 2013
install.packages("dplyr")       # Pauete de manipulacion de datos 
install.packages("ggplot2")    # Liberia para usar gráficos 
install.packages("e1071")      # skewness y kurtosis (Fisher-Pearson)
install.packages("DescTools")  # Skew(), Kurt(), BowleySkew()
install.packages("readr")     #importar/exportar CSV


## Cargo todas las librerias instaladas``

library(nycflights13)
library(dplyr)
library(ggplot2)
library(e1071)
library(DescTools)
library(readr)

2. Base de datos NY2013

Paso 2. Cargo la base flights con data(). Primero exploro su estructura con str() y glimpse() para conocer columnas y tipos de datos. Después selecciono solo cuatro variables relevantes (distance, air_time, dep_delay, arr_delay) con dplyr::select(). Reviso las primeras y últimas filas con head(), tail(), abro el conjunto con View(), obtengo estadísticas básicas con summary() y cuento valores faltantes con colSums(is.na()).

#========================================== PASO 2 ===========================
# Cargar datos y seleccionar variables

data("flights")  # TRAIGO LA DATA DE FLIGT
str(flights)  # QUIERO VER LAS COLUMNAS Y EL TIPO DE DATOS QUE HAY 
## tibble [336,776 × 19] (S3: tbl_df/tbl/data.frame)
##  $ year          : int [1:336776] 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 ...
##  $ month         : int [1:336776] 1 1 1 1 1 1 1 1 1 1 ...
##  $ day           : int [1:336776] 1 1 1 1 1 1 1 1 1 1 ...
##  $ dep_time      : int [1:336776] 517 533 542 544 554 554 555 557 557 558 ...
##  $ sched_dep_time: int [1:336776] 515 529 540 545 600 558 600 600 600 600 ...
##  $ dep_delay     : num [1:336776] 2 4 2 -1 -6 -4 -5 -3 -3 -2 ...
##  $ arr_time      : int [1:336776] 830 850 923 1004 812 740 913 709 838 753 ...
##  $ sched_arr_time: int [1:336776] 819 830 850 1022 837 728 854 723 846 745 ...
##  $ arr_delay     : num [1:336776] 11 20 33 -18 -25 12 19 -14 -8 8 ...
##  $ carrier       : chr [1:336776] "UA" "UA" "AA" "B6" ...
##  $ flight        : int [1:336776] 1545 1714 1141 725 461 1696 507 5708 79 301 ...
##  $ tailnum       : chr [1:336776] "N14228" "N24211" "N619AA" "N804JB" ...
##  $ origin        : chr [1:336776] "EWR" "LGA" "JFK" "JFK" ...
##  $ dest          : chr [1:336776] "IAH" "IAH" "MIA" "BQN" ...
##  $ air_time      : num [1:336776] 227 227 160 183 116 150 158 53 140 138 ...
##  $ distance      : num [1:336776] 1400 1416 1089 1576 762 ...
##  $ hour          : num [1:336776] 5 5 5 5 6 5 6 6 6 6 ...
##  $ minute        : num [1:336776] 15 29 40 45 0 58 0 0 0 0 ...
##  $ time_hour     : POSIXct[1:336776], format: "2013-01-01 05:00:00" "2013-01-01 05:00:00" ...
dplyr::glimpse(flights) #USO UNA LIBERIA DPLYR para con glimpse= vista rápida y compacta de un dataframe
## Rows: 336,776
## Columns: 19
## $ year           <int> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2…
## $ month          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
## $ day            <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
## $ dep_time       <int> 517, 533, 542, 544, 554, 554, 555, 557, 557, 558, 558, …
## $ sched_dep_time <int> 515, 529, 540, 545, 600, 558, 600, 600, 600, 600, 600, …
## $ dep_delay      <dbl> 2, 4, 2, -1, -6, -4, -5, -3, -3, -2, -2, -2, -2, -2, -1…
## $ arr_time       <int> 830, 850, 923, 1004, 812, 740, 913, 709, 838, 753, 849,…
## $ sched_arr_time <int> 819, 830, 850, 1022, 837, 728, 854, 723, 846, 745, 851,…
## $ arr_delay      <dbl> 11, 20, 33, -18, -25, 12, 19, -14, -8, 8, -2, -3, 7, -1…
## $ carrier        <chr> "UA", "UA", "AA", "B6", "DL", "UA", "B6", "EV", "B6", "…
## $ flight         <int> 1545, 1714, 1141, 725, 461, 1696, 507, 5708, 79, 301, 4…
## $ tailnum        <chr> "N14228", "N24211", "N619AA", "N804JB", "N668DN", "N394…
## $ origin         <chr> "EWR", "LGA", "JFK", "JFK", "LGA", "EWR", "EWR", "LGA",…
## $ dest           <chr> "IAH", "IAH", "MIA", "BQN", "ATL", "ORD", "FLL", "IAD",…
## $ air_time       <dbl> 227, 227, 160, 183, 116, 150, 158, 53, 140, 138, 149, 1…
## $ distance       <dbl> 1400, 1416, 1089, 1576, 762, 719, 1065, 229, 944, 733, …
## $ hour           <dbl> 5, 5, 5, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6…
## $ minute         <dbl> 15, 29, 40, 45, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0…
## $ time_hour      <dttm> 2013-01-01 05:00:00, 2013-01-01 05:00:00, 2013-01-01 0…
df <- dplyr::select(flights, distance, air_time, dep_delay, arr_delay) # para la actividad trabajaremos 4 variables
#View(flights)
##### Visualizo el DF en crudo 
head(df,20) # Muestra las primeras 20  Filas 
## # A tibble: 20 × 4
##    distance air_time dep_delay arr_delay
##       <dbl>    <dbl>     <dbl>     <dbl>
##  1     1400      227         2        11
##  2     1416      227         4        20
##  3     1089      160         2        33
##  4     1576      183        -1       -18
##  5      762      116        -6       -25
##  6      719      150        -4        12
##  7     1065      158        -5        19
##  8      229       53        -3       -14
##  9      944      140        -3        -8
## 10      733      138        -2         8
## 11     1028      149        -2        -2
## 12     1005      158        -2        -3
## 13     2475      345        -2         7
## 14     2565      361        -2       -14
## 15     1389      257        -1        31
## 16      187       44         0        -4
## 17     2227      337        -1        -8
## 18     1076      152         0        -7
## 19      762      134         0        12
## 20     1023      147         1        -6
tail(df,20) # Muesta las ultimas 20 filas 
## # A tibble: 20 × 4
##    distance air_time dep_delay arr_delay
##       <dbl>    <dbl>     <dbl>     <dbl>
##  1      209       39        -9       -16
##  2      301       50       194       194
##  3      378       61        -2         8
##  4      764       97        27         7
##  5      872      120        72        57
##  6      273       48       -14       -21
##  7     2565      318        80        42
##  8      944      123       154       130
##  9      266       43        -8        -8
## 10      209       41        -5       -17
## 11      301       52       -10       -20
## 12      264       47        -5       -16
## 13      187       33        12         1
## 14     1617      196       -10       -25
## 15      764       NA        NA        NA
## 16      213       NA        NA        NA
## 17      198       NA        NA        NA
## 18      764       NA        NA        NA
## 19      419       NA        NA        NA
## 20      431       NA        NA        NA
#View(df) # Abre en otra pestaña el DF 
summary(df) # Aplica los estadisticos básicos // (mínimo, cuartiles, media, máximo y los  NA).
##     distance       air_time      dep_delay        arr_delay      
##  Min.   :  17   Min.   : 20    Min.   : -43.0   Min.   : -86.00  
##  1st Qu.: 502   1st Qu.: 82    1st Qu.:  -5.0   1st Qu.: -17.00  
##  Median : 872   Median :129    Median :  -2.0   Median :  -5.00  
##  Mean   :1040   Mean   :151    Mean   :  12.6   Mean   :   6.89  
##  3rd Qu.:1389   3rd Qu.:192    3rd Qu.:  11.0   3rd Qu.:  14.00  
##  Max.   :4983   Max.   :695    Max.   :1301.0   Max.   :1272.00  
##                 NA's   :9430   NA's   :8255     NA's   :9430
colSums(is.na(df)) # cuenta exactamente cuántos NA hay por variable.
##  distance  air_time dep_delay arr_delay 
##         0      9430      8255      9430

3. Limpieza base de datos

Paso 3. Elimino filas con datos faltantes usando dplyr::filter() junto con !is.na() para cada variable. Esto asegura que los cálculos no se vean afectados por huecos en la información. Luego verifico que no haya NA con colSums(is.na()) y reviso el nuevo dataframe limpio con knitr::kable().

#========================================== PASO 3 ===========================
# Limpiamos la base de los NA 

df <- dplyr::filter(df,
                    !is.na(distance),
                    !is.na(air_time),
                    !is.na(dep_delay),
                    !is.na(arr_delay))
# Vuelvo a psar el Colsum debe ser 0 

knitr::kable(head(df,10)) # En mark Down no funciona el VIEW() 
distance air_time dep_delay arr_delay
1400 227 2 11
1416 227 4 20
1089 160 2 33
1576 183 -1 -18
762 116 -6 -25
719 150 -4 12
1065 158 -5 19
229 53 -3 -14
944 140 -3 -8
733 138 -2 8
knitr::kable(tail(df,10)) #Uso este comando con Head y tail para ver 10 y 10 
distance air_time dep_delay arr_delay
872 120 72 57
273 48 -14 -21
2565 318 80 42
944 123 154 130
266 43 -8 -8
209 41 -5 -17
301 52 -10 -20
264 47 -5 -16
187 33 12 1
1617 196 -10 -25
colSums(is.na(df)) # Compruebo que en DF no hay NA con un conteo por variable
##  distance  air_time dep_delay arr_delay 
##         0         0         0         0

4. Limites a variables con valores negativos

Paso 4. Creo la función limites_iqr() que define un rango aceptable de datos usando cuartiles (quantile()) e intervalo intercuartílico (IQR()). Aplico la función a distance y air_time, obtengo límites inferior y superior y filtro valores atípicos con dplyr::filter(). El resultado se guarda en df_clean. Finalmente muestro cuántas filas quedan con nrow() y reviso el nuevo dataset con knitr::kable().

#========================================== PASO 4 ===========================

# Voy a establecer los limittes para que no hayan datos "extraños"
# Creo una función limites_iqr

limites_iqr <- function(x) {
  q1  <- quantile(x, 0.25, na.rm = TRUE)
  q3  <- quantile(x, 0.75, na.rm = TRUE)
  iqr <- IQR(x, na.rm = TRUE)
  setNames(c(q1 - 1.5 * iqr, q3 + 1.5 * iqr), c("inf", "sup"))
}

##### Aplico la funcion Limites iqr a la variable distancia y tiempo de vuelo 
### Guardo en un objeto los limites para los dos
lims_dist <- limites_iqr(df$distance)
lims_air  <- limites_iqr(df$air_time)

print(lims_dist)
##  inf  sup 
## -811 2709
print(lims_air)
## inf sup 
## -83 357
#### Vamos a filtrar sacando los datos que no están los limites inf y sup 

df_clean <- dplyr::filter( # Cree un NUEVO DF llamado DF_CLEAN!!!!!!!
  df,
  distance >= lims_dist["inf"], distance <= lims_dist["sup"], # Cuando la distancia se mayor o igual al inferior y menor o igual al superior
  air_time  >= lims_air["inf"],  air_time  <= lims_air["sup"]
)

knitr::kable(head(df_clean,5)) # En mark Down no funciona el VIEW() 
distance air_time dep_delay arr_delay
1400 227 2 11
1416 227 4 20
1089 160 2 33
1576 183 -1 -18
762 116 -6 -25
knitr::kable(tail(df_clean,5)) #Uso este comando con Head y tail para ver 5 y 5
distance air_time dep_delay arr_delay
209 41 -5 -17
301 52 -10 -20
264 47 -5 -16
187 33 12 1
1617 196 -10 -25
cat("Filas finales:", nrow(df_clean), "\n")# Muestro las filas finales de DF clean
## Filas finales: 321898

5. Función: Centralidad, disperción y forma

Paso 5. Construyo la función analizar() que genera una tabla con estadísticas de cada variable. Incluye medidas de localización (mean(), median(), moda con table()), dispersión (var(), sd(), rango, IQR(), coeficiente de variación) y forma (skewness(), kurtosis()). Agrupo resultados para todas las variables con rbind() en un objeto llamado estadisticas y lo visualizo con knitr::kable().

#===================. PASO 5 ========================
#Creare una función dentro de la función creo un DF nuevo  donde están los todos los estadisticos Localización, Dispersión , Forma 

analizar <- function(x, nom) {
  data.frame(
    Variable = nom,
    # Centralidad
    Media = mean(x, na.rm = TRUE),
    Mediana = median(x, na.rm = TRUE),
    Moda = as.numeric(names(which.max(table(x)))),
    Q1 = quantile(x, 0.25, na.rm = TRUE),
    Q2 = quantile(x, 0.50, na.rm = TRUE),
    Q3 = quantile(x, 0.75, na.rm = TRUE),
    
    #DISPERSIÓN
    Varianza = var(x, na.rm = TRUE),
    Desv_Est = sd(x, na.rm = TRUE),
    Rango = max(x, na.rm = TRUE) - min(x, na.rm = TRUE),
    RIQ = IQR(x, na.rm = TRUE),
    CV = sd(x, na.rm = TRUE) / mean(x, na.rm = TRUE) * 100,
    
    #FORMA 
    Asimetria = e1071::skewness(x, type=2, na.rm = TRUE),
    Curtosis = e1071::kurtosis(x, type=2, na.rm = TRUE),
    row.names = NULL
  )
}

###### VOY A CREAR  un objeto llamdo esttadisticas que va a construir el datta frame final 

estadisticas <- rbind( # RBIND me crea las filas por variable y las guara para graficar 
  analizar(df_clean$distance, "DISTANCE"),
  analizar(df_clean$air_time, "AIR_TIME"),
  analizar(df_clean$dep_delay, "DEP_DELAY"),
  analizar(df_clean$arr_delay, "ARR_DELAY")
)

#View(estadisticas) # Acá ya veo la tabla con los datos guardados de la función 
knitr::kable(estadisticas)
Variable Media Mediana Moda Q1 Q2 Q3 Varianza Desv_Est Rango RIQ CV Asimetria Curtosis
DISTANCE 1017.794 872 2475 502 872 1389 483267 695.17 2506 887 68.30 0.9361 -0.0790
AIR_TIME 146.464 128 42 81 128 186 7735 87.95 337 105 60.05 0.9087 -0.0879
DEP_DELAY 12.596 -2 -5 -5 -2 11 1609 40.11 1180 16 318.42 4.7381 41.4081
ARR_DELAY 6.688 -5 -13 -17 -5 13 1988 44.59 1213 30 666.76 3.6786 27.6714

6. Visualización

Paso 6. Para distance y air_time utilizo geom_histogram() con intervalos definidos por binwidth. Resalto la media de cada distribución con geom_vline(linetype=“dashed”, color=“red”). Ajusto escalas con scale_x_continuous() y scale_y_continuous(), y aplico theme_minimal() para un diseño limpio.

En el caso de los retrasos de salida (dep_delay) y llegada (arr_delay), empleo geom_histogram() combinado con case_when() para clasificar los vuelos en categorías: Adelantado, A Tiempo, Retraso Leve y Retraso Fuerte. Personalizo colores con scale_fill_manual() y limito el foco de los datos con coord_cartesian(xlim=…) para concentrarme en los valores más frecuentes. También agrego convenciones con leyendas claras.

Para un análisis más avanzado de los retrasos en salida, combino geom_histogram(aes(y=..density..)) con geom_density() para representar la forma de la distribución y resaltar la media con geom_vline(). Calculo asimetría y curtosis usando skewness() y kurtosis() de la librería moments, defino la forma de la curva con condicionales ifelse(), y muestro el resultado dentro del gráfico con annotate(“text”, …).

Finalmente, para explorar la relación entre retraso en despegue y retraso en aterrizaje, utilizo geom_hex() con scale_fill_viridis_c(), generando un gráfico tipo panal de abeja que permite visualizar zonas de alta concentración sin sobrecargar la visualización.

#===================. PASO 6  ========================
# Grafico: distancia y air time (Histograma y box plot)
# Graficos : Retraso  (Histograma)
#gradicos: Simetria y Curtosis (Graficos avanzados)


#######======= 1. Histograma Distance================

ggplot(df_clean, aes(x = distance)) + ### SELECCIONO la variable 
  geom_histogram(binwidth = 50 , fill = "steelblue", color = "black") +  # SELECCIONO el tipo de grafico 
  geom_vline(aes(xintercept = mean(distance, na.rm = TRUE)), # CREO la LINEA Vertical con la media. 
             color = "red", linetype = "dashed", linewidth = 1) +
  labs(title = "Distribución de distancias de vuelos (Distance)",
       x = "Distancia (millas)", 
       y = "Número de vuelos") +
  
  ####### Eje y 
  scale_y_continuous(breaks = seq(0, 50000, by = 5000)) +
  ######## EJE X 
  scale_x_continuous(breaks = seq(0, 5000, by = 500))+
  theme_minimal()

#####======= 2. Histograma Air time ====================

ggplot(df_clean, aes(x = air_time)) + # Le digo que vamos a graficar air_time
  geom_histogram(binwidth = 10, fill = "steelblue", color = "black") + # agrupo lo vuelos en tiempoos de 10 min / relleno y color de línea
    geom_vline(aes(xintercept = mean(air_time, na.rm = TRUE)), #  geom_vline(...): linea roja es la media del tiempo de vuelo.
             color = "red" , linetype = "dashed", linewidth = 1) + 
  labs(title = "Distribución del tiempo de vuelo (air_time)", # Etiquetas 
       x = "Tiempo de vuelo (minutos)",
       y = "Número de vuelos") +
  ####### Eje y 
  scale_y_continuous(breaks = seq(0, 50000, by = 5000)) +
  ######## EJE X 
  scale_x_continuous(breaks = seq(0, 500, by = 50))+
  
  theme_minimal() # Fondo blanco lineas 

#####=====  Retrasos / despegar 
#para este histograma se deben realizar un grafico mas elaborado
#1. se debe usar un Case_When_ es un condicional de datos que permite etiquetar 
#2. se hace un zoom sobre el X - 
#3. Creamos convenciones que salen al lado derecho

############## HISTOGRAMAS CONDICIONAL COLOR Y CATEGORIA=======

ggplot(df_clean, aes(x = dep_delay)) + 
  
  # Histograma con las categorías de color para poder visualizar el histograma 
  geom_histogram( 
    aes(fill = case_when(
      dep_delay < 0   ~ "Adelantado",
      dep_delay == 0  ~ "A Tiempo",
      dep_delay <=120 ~ "Retraso Leve",
      TRUE            ~ "Retraso Fuerte" # TRUE captura todos los casos restantes
    )),
    binwidth = 5, 
    color = "white",
    na.rm = TRUE # Omite los valores NA para evitar errores
  ) +
######## Importante ZOOM
  coord_cartesian(xlim = c(-43, 100)) +
######## Convensiones 
  scale_fill_manual(
    name = "Tipo de Puntualidad",
    values = c(
      "Adelantado"   = "seagreen",
      "A Tiempo"     = "steelblue",
      "Retraso Leve" = "orange",
      "Retraso Fuerte" = "firebrick"
    )
  ) +

  ########
labs(
  title = "Retrasos vuelos Vuelos ",
  subtitle = " Rango: -43 a 100 minutos",
  x = "Retraso en la Salida (Minutos)",
  y = "Cantidad de Vuelos"
) +
  theme_minimal()

##############====================================== RETRASOS ATERRIZAR 
#para este histograma se deben realizar un grafico mas elaborado
#1. se debe usar un Case_When_ es un condicional de datos que permite etiquetar 
#2. se hace un zoom sobre el X - 
#3. Creamos convenciones que salen al lado derecho
#4. Creamos  un Scale en X / Y para ver numeros mas pequeños en los labels
ggplot(df_clean, aes(x = arr_delay)) + 
  
  # Histograma con las categorías de color para poder visualizar el histograma 
  geom_histogram( 
    aes(fill = case_when(
      arr_delay < 0   ~ "Adelantado",
      arr_delay == 0  ~ "A Tiempo",
      arr_delay <= 60 ~ "Retraso Leve",
      TRUE            ~ "Retraso Fuerte" # TRUE captura todos los casos restantes
    )),
    binwidth = 10, 
    color = "white",
    na.rm = TRUE # Omite los valores NA para evitar errores
  ) +
  ######## Importante ZOOM delimito el foco de los datos 
  coord_cartesian(xlim = c(-90, 200)) +
  ####### Eje y 
  scale_y_continuous(breaks = seq(0, 70000, by = 10000)) +
 ######## EJE X 
  scale_x_continuous(breaks = seq(-100, 200, by = 50))+
  
   
  ######## Convensiones 
  scale_fill_manual(
    name = "Tipo de Puntualidad",
    values = c(
      "Adelantado"   = "seagreen",
      "A Tiempo"     = "steelblue",
      "Retraso Leve" = "orange",
      "Retraso Fuerte" = "firebrick"
    )
  ) +
  
  ########
labs(
  title = "Retrasos llegadas vuelos ",
  subtitle = " Rango: -90 a 200 minutos",
  x = "Retraso en la llegada  (Minutos)",
  y = "Cantidad de Vuelos"
) +
  theme_minimal()

#####================================
#####================================ AVANZADOS PRO ==============

###### Densidad, Asimetría y curtois

##########=============================RETRASOS SALIDAS=====================================

# Calcular curtosis y asimetría
kurt_val <- kurtosis(df_clean$dep_delay, na.rm = TRUE) # Creo un objeto donde guardo la operacion Kurtosis
skew_val <- skewness(df_clean$dep_delay, na.rm = TRUE) # Creo un objeto donde guardo la asimetría 

# Definir el tipo de distribución según la curtosis creo ubn condicional con IF 
tipo_curtosis <- ifelse(kurt_val > 3, "Leptocúrtica (colas pesadas)",
                        ifelse(kurt_val < 3, "Platicúrtica (aplanada)", 
                               "Mesocúrtica (normal)"))

# Definir el tipo de asimetría , condicional 
tipo_asimetria <- ifelse(skew_val > 0, "Positiva (cola a la derecha)",
                         ifelse(skew_val < 0, "Negativa (cola a la izquierda)",
                                "Simétrica"))

# Texto que se va a mostrar en la anotación
texto <- paste0("Asimetría: ", tipo_asimetria, 
                "\nCurtosis: ", tipo_curtosis, 
                "\n(skew = ", round(skew_val,2), #llamo el objeto guardado y el digo que muestre 2 decimales
                ", kurt = ", round(kurt_val,2), ")")


# --- Gráfico ---
ggplot(df_clean, aes(x = dep_delay)) +
  geom_histogram(aes(y = ..density..), bins = 100, fill = "orange", color = "black") + #histograma
  geom_density(color = "magenta", linewidth = 1) +  #liena de densidad
  geom_vline(aes(xintercept = mean(dep_delay, na.rm = TRUE)), # Linea de la Media 
             color = "red", linetype = "dashed", linewidth = 1) +
  
  ######## Importante ZOOM delimito el foco de los datos 
  coord_cartesian(xlim = c(-50, 100)) + # Zoom en el eje X lo ajusto de acuerdo a los valores de los datos
  ######## EJE X 
  scale_x_continuous(breaks = seq(-50, 200, by = 10))+ # como se ve el eje X en el grafico
  
  labs(title = "Distribución de retraso en despegue (dep_delay)",
       x = "Retraso en despegue (min)", y = "Densidad") +
  
  annotate("text", x = 60, y = 0.06,   # El texto en el gráifco 
           label = texto, color = "black", size = 3, fontface = "bold") +
  
  theme_minimal()

#########==========

###### Panal Exaginal 
  
  library(ggplot2)

ggplot(df_clean, aes(x = dep_delay, y = arr_delay)) +
  geom_hex(bins = 100) +
  scale_fill_viridis_c() +
  labs(title = "Relación entre retraso en despegue y aterrizaje",
       x = "Retraso en despegue (min)",
       y = "Retraso en aterrizaje (min)",
       fill = "Cantidad") +
  theme_minimal()