0. Librerias

library(knitr)
library(kableExtra)
library(e1071)

1. Leer Datos

variables <- read.csv("C:/Users/WAN/Downloads/GlobalWeatherRepository.csv")

2. Extraer y Depuración de la Variable

Humedad <- na.omit(variables$humidity)

# Total de datos

n_total <- length(Humedad)

3. Frecuencias

3.1 Max y Min

valor_min <- min(Humedad)
valor_max <- max(Humedad)

rango <- valor_max - valor_min

3.2. Regla de Sturges

K_sturges <- floor(1 + 3.322 * log10(n_total))

cat("Número de clases:", K_sturges, "\n")
## Número de clases: 18
A_sturges <- rango / K_sturges

cat("Amplitud Sturges:", A_sturges, "\n")
## Amplitud Sturges: 5.444444

3.3. Intervalos

Li1 <- seq(max(0, valor_min), min(100, valor_max) - A_sturges, by = A_sturges)
Ls1 <- pmin(100, Li1 + A_sturges)

3.4. Bucle para las columnas de la tabla

ni1 <- numeric(length(Li1))

for(i in 1:length(Li1)){
  
  if(i == length(Li1)){
    
    ni1[i] <- sum(
      Humedad >= Li1[i] &
        Humedad <= Ls1[i]
    )
    
  } else {
    
    ni1[i] <- sum(
      Humedad >= Li1[i] &
        Humedad < Ls1[i]
    )
  }
}

# Frecuencia relativa
hi1 <- (ni1 / sum(ni1)) * 100

# Acumuladas
Ni_asc1 <- cumsum(ni1)
Hi_asc1 <- cumsum(hi1)

Ni_dsc1 <- rev(cumsum(rev(ni1)))
Hi_dsc1 <- rev(cumsum(rev(hi1)))

# Marca de clase
MC1 <- (Li1 + Ls1)/2

3.5. Tabla con amplitud ajustada

Li2 <- seq(0, 90, by = 10)
Ls2 <- Li2 + 10

# Frecuencias ajustadas
ni2 <- numeric(length(Li2))
for(i in 1:length(Li2)){
  if(i == length(Li2)){
    ni2[i] <- sum(Humedad >= Li2[i] & Humedad <= Ls2[i])
  } else {
    ni2[i] <- sum(Humedad >= Li2[i] & Humedad < Ls2[i])
  }
}
hi2 <- (ni2 / sum(ni2)) * 100
Ni_asc2 <- cumsum(ni2)
Hi_asc2 <- cumsum(hi2)
Ni_dsc2 <- rev(cumsum(rev(ni2)))
Hi_dsc2 <- rev(cumsum(rev(hi2)))
MC2 <- (Li2 + Ls2)/2

4. Tabla de Distribución de Frecuencias

4.1. Tabla según Sturges

# Convertir a carácteres para poder mezclar números y texto

Tabla_Sturges <- data.frame(
  
  Lim_inf = round(Li1,2),
  
  Lim_sup = round(Ls1,2),
  
  MC = round(MC1,2),
  
  ni = ni1,
  
  hi = round(hi1,2),
  
  Ni_asc = Ni_asc1,
  
  Hi_asc = round(Hi_asc1,2),
  
  Ni_dsc = Ni_dsc1,
  
  Hi_dsc = round(Hi_dsc1,2)
  
)

Tabla_Sturges2 <- Tabla_Sturges
Tabla_Sturges2[] <- lapply(Tabla_Sturges2, as.character)

# Fila de totales

fila_total <- data.frame(
  Lim_inf = "TOTAL",
  Lim_sup = "",
  MC = "",
  ni = as.character(sum(ni1)),
  hi = as.character(round(sum(hi1), 2)),
  Ni_asc = "",
  Hi_asc = "",
  Ni_dsc = "",
  Hi_dsc = ""
)

# Agregar fila

Tabla_Sturges2 <- rbind(Tabla_Sturges2, fila_total)


kable(
  Tabla_Sturges2,
  align = "c",
  caption = "Tabla N1: Distribución de frecuencias de la Humedad de los registros meteorológicos mundiales mediante la regla de Sturges, período 2024–2026 "
) |>
  
  kableExtra::kable_styling(
    full_width = TRUE,
    position = "center",
    bootstrap_options = c(
      "striped",
      "hover",
      "condensed",
      "responsive"
    )
  ) |>
  
  kableExtra::row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#2C3E50"
  ) |>
  
  kableExtra::row_spec(
    nrow(Tabla_Sturges2),
    bold = TRUE,
    background = "#EAEDED"
  )|>
  footnote(
    general = "Elaborado por Grupo 2. 
    Fuente: Global Weather Repository.",
    general_title = "Nota: ",
    footnote_as_chunk = TRUE,
    title_format = c("italic","bold")
  )
Tabla N1: Distribución de frecuencias de la Humedad de los registros meteorológicos mundiales mediante la regla de Sturges, período 2024–2026
Lim_inf Lim_sup MC ni hi Ni_asc Hi_asc Ni_dsc Hi_dsc
2 7.44 4.72 574 0.41 574 0.41 141703 100
7.44 12.89 10.17 1919 1.35 2493 1.76 141129 99.59
12.89 18.33 15.61 3642 2.57 6135 4.33 139210 98.24
18.33 23.78 21.06 3509 2.48 9644 6.81 135568 95.67
23.78 29.22 26.5 4431 3.13 14075 9.93 132059 93.19
29.22 34.67 31.94 4166 2.94 18241 12.87 127628 90.07
34.67 40.11 37.39 5564 3.93 23805 16.8 123462 87.13
40.11 45.56 42.83 5362 3.78 29167 20.58 117898 83.2
45.56 51 48.28 5786 4.08 34953 24.67 112536 79.42
51 56.44 53.72 8120 5.73 43073 30.4 106750 75.33
56.44 61.89 59.17 7364 5.2 50437 35.59 98630 69.6
61.89 67.33 64.61 10654 7.52 61091 43.11 91266 64.41
67.33 72.78 70.06 10281 7.26 71372 50.37 80612 56.89
72.78 78.22 75.5 15021 10.6 86393 60.97 70331 49.63
78.22 83.67 80.94 14306 10.1 100699 71.06 55310 39.03
83.67 89.11 86.39 17508 12.36 118207 83.42 41004 28.94
89.11 94.56 91.83 13951 9.85 132158 93.26 23496 16.58
94.56 100 97.28 9545 6.74 141703 100 9545 6.74
TOTAL 141703 100
Nota: Elaborado por Grupo 2.
Fuente: Global Weather Repository.

4.2. Tabla ajustada

# Crear el dataframe con los datos numéricos correctos
TDF_Humedad <- data.frame(
  Lim_inf = round(Li2, 2),
  Lim_sup = round(Ls2, 2),
  MC      = round(MC2, 2),
  ni      = ni2,
  hi      = round(hi2, 2),
  Ni_asc  = Ni_asc2,
  Hi_asc  = round(Hi_asc2, 2),
  Ni_dsc  = Ni_dsc2,
  Hi_dsc  = round(Hi_dsc2, 2)
)

# Convertir explícitamente a carácter para permitir la fila TOTAL sin advertencias
TDF_Humedad[] <- lapply(TDF_Humedad, as.character)

# Crear la fila de totales con la misma estructura exacta de columnas
TDF_Total <- data.frame(
  Lim_inf = "TOTAL",
  Lim_sup = "",
  MC      = "",
  ni      = as.character(sum(ni2)),
  hi      = as.character(round(sum(hi2), 2)),
  Ni_asc  = "",
  Hi_asc  = "",
  Ni_dsc  = "",
  Hi_dsc  = "",
  stringsAsFactors = FALSE
)

# Unir la tabla con la fila de totales
TDF_Humedad <- rbind(TDF_Humedad, TDF_Total)

# Renombrar las columnas de forma limpia para la presentación final
colnames(TDF_Humedad) <- c(
  "Lim. Inf.", "Lim. Sup.", "MC", 
  "ni", "hi (%)", 
  "Ni Asc", "Hi Asc (%)", 
  "Ni Dsc", "Hi Dsc (%)"
)

# Renderizar la tabla con kableExtra
kable(
  TDF_Humedad, 
  align = "c", 
  caption = "Tabla N°2: Distribución de frecuencias de la Humedad en intervalos de 10%"
) |>
  kable_styling(
    full_width = TRUE, 
    position = "center", 
    bootstrap_options = c("striped", "hover", "condensed", "responsive")
  ) |>
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50") |>
  row_spec(nrow(TDF_Humedad), bold = TRUE, background = "#EAEDED") |>
  footnote(
    general = "Elaborado por Grupo 2. Fuente: Global Weather Repository.",
    general_title = "Nota: ", 
    footnote_as_chunk = TRUE,
    title_format = c("italic", "bold")
  )
Tabla N°2: Distribución de frecuencias de la Humedad en intervalos de 10%
Lim. Inf. Lim. Sup. MC ni hi (%) Ni Asc Hi Asc (%) Ni Dsc Hi Dsc (%)
0 10 5 1178 0.83 1178 0.83 141703 100
10 20 15 5653 3.99 6831 4.82 140525 99.17
20 30 25 7244 5.11 14075 9.93 134872 95.18
30 40 35 8782 6.2 22857 16.13 127628 90.07
40 50 45 10873 7.67 33730 23.8 118846 83.87
50 60 55 13664 9.64 47394 33.45 107973 76.2
60 70 65 17239 12.17 64633 45.61 94309 66.55
70 80 75 27253 19.23 91886 64.84 77070 54.39
80 90 85 26321 18.57 118207 83.42 49817 35.16
90 100 95 23496 16.58 141703 100 23496 16.58
TOTAL 141703 100
Nota: Elaborado por Grupo 2. Fuente: Global Weather Repository.

5. Gráficos de Distribución de Frecuencias

5.1. Histograma Original (ni)

# Dibujar el histograma usando exactamente tus límites de intervalos de 10 en 10
hist(Humedad,
     breaks = c(Li2, max(Ls2)), # Forzar a usar exactamente los cortes 0, 10, 20... 100
     freq = TRUE,
     main = "Gráfico N°1: Histograma de frecuencias absolutas de la Humedad\nRegistros meteorológicos mundiales, período 2024–2026",
     xlab = "Humedad (%)",
     ylab = "Frecuencia absoluta (ni)",
     col = "lightgreen",
     border = "black",
     xaxt = "n") # Ocultamos el eje X automático para personalizarlo

# Crear marcas del eje X exactamente en cada punto de corte de tus intervalos
axis(1, 
     at = c(Li2, max(Ls2)), 
     labels = c(Li2, max(Ls2)), 
     las = 2,           # Rotar las etiquetas verticalmente para que no se solapen
     cex.axis = 0.8)    # Reducir ligeramente el tamaño del texto para mejor ajuste

# Añadir cuadrícula de fondo para facilitar la lectura de las barras
grid(nx = NA, ny = NULL, lty = 2, col = "gray") 

5.2. Histograma con relación al todo (ni)

# Dibujar el histograma escalado al total de la muestra
hist(Humedad,
     breaks = c(Li2, max(Ls2)), # Mismos intervalos de 10 en 10
     freq = TRUE,
     ylim = c(0, n_total),      # El eje Y llega hasta el gran total de datos (el "todo")
     main = "Gráfico N°2: Histograma de Humedad en Relación al Todo\n(Escala completa de la muestra)",
     xlab = "Humedad (%)",
     ylab = "Frecuencia absoluta (ni)",
     col = "lightgreen",
     border = "black",
     xaxt = "n") # Ocultamos el eje X automático para personalizarlo

# Eje X personalizado y rotado para evitar solapamientos
axis(1, 
     at = c(Li2, max(Ls2)), 
     labels = c(Li2, max(Ls2)), 
     las = 2, 
     cex.axis = 0.8)

# Cuadrícula de fondo referencial
grid(nx = NA, ny = NULL, lty = 2, col = "gray")

5.3. Histograma original (hi)

# Crear el gráfico de barras sin espacio entre ellas (simulando un histograma)
bp <- barplot(hi2,
              names.arg = paste(Li2, Ls2, sep = "-"), # Etiquetas de los intervalos
              ylim = c(0, max(hi2) * 1.1),             # Margen superior del 10%
              main = "Gráfico N°3: Histograma de frecuencias relativas de la Humedad\nRegistros meteorológicos mundiales, período 2024–2026",
              xlab = "Intervalos de Humedad (%)",
              ylab = "Frecuencia Relativa (%)",
              col = "skyblue",
              border = "black",
              las = 2,                                 # Rotar etiquetas del eje X
              cex.names = 0.8,                         # Tamaño de letra del eje X
              space = 0)                               # Sin espacio para que actúe como histograma

# Añadir una cuadrícula de fondo horizontal limpia
grid(nx = NA, ny = NULL, lty = 2, col = "gray")

5.4. Histograma con relacion a todo (hi)

# Crear el gráfico de barras relativas escalado al 100% total
barplot(hi2,
        names.arg = paste(Li2, Ls2, sep = "-"),
        ylim = c(0, 100),                          # El eje Y llega hasta el 100% (el "todo")
        main = "Gráfico N°4: Histograma Global de Frecuencia Relativa\n(Escala completa de 0 a 100%)",
        xlab = "Intervalos de Humedad (%)",
        ylab = "Frecuencia Relativa (%)",
        col = "lightgreen",
        border = "black",
        las = 2,                                   # Rotar etiquetas del eje X verticalmente
        cex.names = 0.8,                           # Ajustar tamaño del texto de los intervalos
        space = 0)                                 # Sin espacio entre barras para simular histograma

# Añadir cuadrícula de fondo horizontal referencial
grid(nx = NA, ny = NULL, lty = 2, col = "gray")

## 5.5. Polígono de frecuencias (ni)

#  Dibujar el histograma de frecuencias absolutas base
hist(Humedad,
     breaks = c(Li2, max(Ls2)), 
     freq = TRUE,
     main = "Gráfico N°5: Histograma y Polígono de Frecuencias Absolutas",
     xlab = "Humedad (%)",
     ylab = "Frecuencia (ni)",
     col = "lightblue",
     border = "black",
     xaxt = "n")

#  Eje X personalizado con marcas rotadas
axis(1, at = c(Li2, max(Ls2)), labels = c(Li2, max(Ls2)), las = 2, cex.axis = 0.8)

#  Superponer el polígono exactamente sobre las marcas de clase (MC2)
lines(MC2, ni2, type = "o", lwd = 2, pch = 16, col = "red")

#  Cuadrícula de fondo horizontal
grid(nx = NA, ny = NULL, lty = 2, col = "gray")

5.6. Polígono de frecuencias (hi)

#  Dibujar el histograma de frecuencias relativas base y guardar posiciones en 'bp'
bp <- barplot(hi2,
              names.arg = paste(Li2, Ls2, sep = "-"),
              ylim = c(0, max(hi2) * 1.2),
              main = "Gráfico N°6: Frecuencia Relativa (%) y Polígono",
              xlab = "Intervalos de Humedad (%)",
              ylab = "Frecuencia Relativa (%)",
              col = "lightgreen",
              border = "black",
              las = 2,
              cex.names = 0.8,
              space = 0) # Sin espacio para simular histograma

# Superponer el polígono usando las posiciones exactas de las barras ('bp')
lines(bp, hi2, type = "o", pch = 16, lwd = 2, col = "red")

#  Cuadrícula de fondo horizontal
grid(nx = NA, ny = NULL, lty = 2, col = "gray")

5.7. Ojiva ascendente y descendente (ni)

#  Preparar datos metodológicos para que las curvas inicien/terminen en cero
eje_x_asc <- c(Li2[1], Ls2)
ni_asc_graf <- c(0, Ni_asc2)

eje_x_dsc <- c(Li2, max(Ls2))
ni_dsc_graf <- c(Ni_dsc2, 0)

#  Graficar Ojiva Ascendente base
plot(eje_x_asc, ni_asc_graf, 
     type = "o", pch = 16, lwd = 2, col = "blue",
     ylim = c(0, n_total),
     main = "Gráfico N°7: Ojivas de Frecuencia Absoluta Acumulada",
     xlab = "Humedad (%)", 
     ylab = "Frecuencia Acumulada (Ni)",
     xaxt = "n")

#  Superponer Ojiva Descendente
lines(eje_x_dsc, ni_dsc_graf, type = "o", pch = 17, lwd = 2, col = "red")

#  Personalizar Eje X rotado
axis(1, at = c(Li2, max(Ls2)), labels = c(Li2, max(Ls2)), las = 2, cex.axis = 0.8)

#  Leyenda y cuadrícula
legend("right", 
       legend = c("Ojiva Ascendente (<= Ls)", "Ojiva Descendente (>= Li)"), 
       col = c("blue", "red"), pch = c(16, 17), lwd = 2, bty = "n", cex = 0.9)
grid(nx = NA, ny = NULL, lty = 2, col = "gray")

5.8. Ojiva ascendente y descendente (hi)

#  Preparar datos porcentuales metodológicos para iniciar/terminar en cero
hi_asc_graf <- c(0, Hi_asc2)
hi_dsc_graf <- c(Hi_dsc2, 0)

#  Graficar Ojiva Ascendente en porcentaje
plot(eje_x_asc, hi_asc_graf, 
     type = "o", pch = 16, lwd = 2, col = "blue",
     ylim = c(0, 100),
     main = "Gráfico N°8: Ojivas de Frecuencia Relativa Acumulada",
     xlab = "Humedad (%)", 
     ylab = "Frecuencia Acumulada (%)",
     xaxt = "n")

#  Superponer Ojiva Descendente en porcentaje
lines(eje_x_dsc, hi_dsc_graf, type = "o", pch = 17, lwd = 2, col = "red")

#  Personalizar Eje X rotado
axis(1, at = c(Li2, max(Ls2)), labels = c(Li2, max(Ls2)), las = 2, cex.axis = 0.8)

#  Leyenda y cuadrícula
legend("right", 
       legend = c("Ascendente (<= Ls)", "Descendente (>= Li)"), 
       col = c("blue", "red"), pch = c(16, 17), lwd = 2, bty = "n", cex = 0.9)
grid(nx = NA, ny = NULL, lty = 2, col = "gray")

5.9. Boxplot

#  Calcular Cuartiles y Rango Intercuartílico (RIC)
Q1 <- quantile(Humedad, 0.25)
Q3 <- quantile(Humedad, 0.75)
RIC <- Q3 - Q1

#  Límites teóricos estándar de Tukey (Acotados al dominio 0-100)
Lim_inf_Tukey <- max(0, Q1 - 1.5 * RIC)
Lim_sup_Tukey <- min(100, Q3 + 1.5 * RIC)

#  Dibujar Boxplot base (ocultamos los atípicos automáticos para graficarlos personalizados)
bp <- boxplot(Humedad,
              horizontal = TRUE,
              outline = FALSE,
              ylim = c(0, 100), # Forzar el dominio real en el eje horizontal
              main = "Gráfico N°9: Boxplot de Humedad con Límites de Tukey",
              xlab = "Humedad (%)",
              col = "lightblue")

#  Identificar y dibujar valores atípicos reales según los límites calculados
atipicos <- Humedad[Humedad < (Q1 - 1.5 * RIC) | Humedad > (Q3 + 1.5 * RIC)]

if(length(atipicos) > 0) {
  points(atipicos, rep(1, length(atipicos)), col = "red", pch = 19, cex = 0.8)
}

#  Dibujar líneas de límites de Tukey sobre el gráfico
abline(v = Lim_inf_Tukey, col = "blue", lwd = 2, lty = 2)
abline(v = Lim_sup_Tukey, col = "blue", lwd = 2, lty = 2)

#  Leyenda y cuadrícula horizontal limpia
legend("topright",
       legend = c(paste("Atípicos (N =", length(atipicos), ")"), "Límites de Tukey"),
       col = c("red", "blue"),
       pch = c(19, NA),
       lty = c(NA, 2),
       lwd = c(NA, 2),
       bty = "n",
       cex = 0.9)
grid(nx = NULL, ny = NA, lty = 2, col = "gray")

6. Indicadores Estadísticos

6.1. Tendencia central

media <- mean(Humedad)
mediana <- median(Humedad)

# Calcular la moda estadística cruda usando la función de densidad o el valor más repetido
# (Evitamos extraerla de la tabla de texto para prevenir errores de tipo de dato)
frecuencias_valores <- table(Humedad)
moda <- as.numeric(names(frecuencias_valores)[frecuencias_valores == max(frecuencias_valores)])

# En caso de que existan múltiples modas, tomamos la primera para la tabla descriptiva
if(length(moda) > 1) { moda <- moda[1] }

6.2. Dispersión

varianza <- var(Humedad)
desv_est <- sd(Humedad)
cv <- (desv_est / media) * 100

6.3. Forma

asimetria <- skewness(Humedad)
curtosis <- kurtosis(Humedad)

6.4. Valores atípicos

# Formatear el rango empírico real observado
rango_texto <- paste0("[", round(min(Humedad), 2), "%; ", round(max(Humedad), 2), "%]")

# Formatear el intervalo de confianza de Tukey (acotado al dominio de la variable)
intervalo_atipicos <- paste0("[", round(Lim_inf_Tukey, 2), "%; ", round(Lim_sup_Tukey, 2), "%]")
n_atipicos <- length(atipicos)

6.5. Tabla de indicadores

# Crear el DataFrame estructurado con datos limpios
tabla_indicadores <- data.frame(
  Variable = "Humedad",
  Rango = rango_texto,
  Media = round(media, 2),
  Mediana = round(mediana, 2),
  Moda = round(moda, 2),
  Varianza = round(varianza, 2),
  Desv_Est = round(desv_est, 2),
  CV_Porc = paste0(round(cv, 2), "%"),
  Asimetria = round(asimetria, 2),
  Curtosis = round(curtosis, 2),
  Limite_Aceptable = intervalo_atipicos,
  N_Atipicos = n_atipicos
)

# Renombrar columnas para presentación final académica
colnames(tabla_indicadores) <- c(
  "Variable", "Rango Real", "Media", "Mediana", "Moda", 
  "Varianza", "Desv. Est.", "CV (%)", "Asimetría", "Curtosis", 
  "Umbral de Tukey", "N° Atípicos"
)

# Renderizar la tabla con kableExtra
kable(tabla_indicadores,
      align = "c",
      caption = "Tabla N°3: Indicadores estadísticos de la variable Humedad") |>
  kable_styling(full_width = TRUE,
                position = "center",
                bootstrap_options = c("striped", "hover", "condensed", "responsive")) |>
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50")
Tabla N°3: Indicadores estadísticos de la variable Humedad
Variable Rango Real Media Mediana Moda Varianza Desv. Est. CV (%) Asimetría Curtosis Umbral de Tukey N° Atípicos
Humedad [2%; 100%] 66.75 72 94 566.56 23.8 35.66% -0.65 -0.49 [0%; 100%] 0

7. Conclusiones

El análisis descriptivo de la humedad revela un promedio de 66.75% y una mediana de 72%, con un moda del 94%, lo que muestra una tendencia hacia climas muy húmedos apoyada por una asimetría negativa de -0.65 y una curtosis de -0.49. Los registros se distribuyen ampliamente con una desviación estándar de 23.8 y un coeficiente de variación del 35.66%, confirmando que no existen valores anómalos. Esta condición general es buena para la salud y el ambiente porque el promedio se mantiene en la zona de confort ideal (40%-70%) que protege las vías respiratorias y beneficia los cultivos; sin embargo, sus extremos son malos, ya que caer al 2% reseca la piel y propicia incendios forestales, mientras que alcanzar el 94%-100% favorece la proliferación dañina de moho, hongos y mosquitos.