0. Librerías

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

1. Leer Datos

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

2. Extracción y Depuración de la Variable

Latitud <- na.omit(variables$latitude)

# Total de datos
n_total <- length(Latitud)

3. Frecuencias

3.1 Max y Min

valor_min <- min(Latitud)
valor_max <- max(Latitud)
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.858333

3.3. Intervalos

# Se garantiza acotar matemáticamente dentro del dominio terrestre real (-90 a 90)
Li1 <- seq(max(-90, valor_min), min(90, valor_max) - A_sturges, by = A_sturges)
Ls1 <- pmin(90, 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(Latitud >= Li1[i] & Latitud <= Ls1[i])
  } else {
    ni1[i] <- sum(Latitud >= Li1[i] & Latitud < Ls1[i])
  }
}

# Frecuencias adicionales
hi1 <- (ni1 / sum(ni1)) * 100
Ni_asc1 <- cumsum(ni1)
Hi_asc1 <- cumsum(hi1)
Ni_dsc1 <- rev(cumsum(rev(ni1)))
Hi_dsc1 <- rev(cumsum(rev(hi1)))
MC1 <- (Li1 + Ls1)/2

3.5. Tabla con amplitud ajustada

amplitud <- 15

# Intervalos limpios basados en múltiplos de 15 y acotados al plano cartográfico
Li2 <- seq(floor(max(-90, valor_min)/15)*15, ceiling(min(90, valor_max)/15)*15 - 15, by = 15)
Ls2 <- Li2 + 15

ni2 <- numeric(length(Li2))

for(i in 1:length(Li2)){
  if(i == length(Li2)){
    ni2[i] <- sum(Latitud >= Li2[i] & Latitud <= Ls2[i])
  } else {
    ni2[i] <- sum(Latitud >= Li2[i] & Latitud < 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 Regla de Sturges

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_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 = ""
)

Tabla_Sturges2 <- rbind(Tabla_Sturges2, fila_total)

kable(
  Tabla_Sturges2,
  align = "c",
  caption = "Tabla N°1: Distribución de frecuencias de la Latitud de los registros meteorológicos mundiales mediante la regla de Sturges"
) |>
  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: Los intervalos obtenidos aplicando la regla de Sturges son poco prácticos para su respectiva explicación por lo que se decidió agruparlos para facilitar su comprensión.",
    footnote_as_chunk = TRUE,
    title_format = c("italic", "bold")
  )
Tabla N°1: Distribución de frecuencias de la Latitud de los registros meteorológicos mundiales mediante la regla de Sturges
Lim_inf Lim_sup MC ni hi Ni_asc Hi_asc Ni_dsc Hi_dsc
-41.3 -35.44 -38.37 728 0.51 728 0.51 141703 100
-35.44 -29.58 -32.51 2905 2.05 3633 2.56 140975 99.49
-29.58 -23.72 -26.65 4095 2.89 7728 5.45 138070 97.44
-23.72 -17.87 -20.8 5087 3.59 12815 9.04 133975 94.55
-17.87 -12.01 -14.94 4356 3.07 17171 12.12 128888 90.96
-12.01 -6.15 -9.08 7269 5.13 24440 17.25 124532 87.88
-6.15 -0.29 -3.22 5814 4.1 30254 21.35 117263 82.75
-0.29 5.57 2.64 7611 5.37 37865 26.72 111449 78.65
5.57 11.43 8.5 13043 9.2 50908 35.93 103838 73.28
11.43 17.28 14.35 20327 14.34 71235 50.27 90795 64.07
17.28 23.14 20.21 6943 4.9 78178 55.17 70468 49.73
23.14 29 26.07 7269 5.13 85447 60.3 63525 44.83
29 34.86 31.93 7276 5.13 92723 65.43 56256 39.7
34.86 40.72 37.79 13773 9.72 106496 75.15 48980 34.57
40.72 46.58 43.65 14029 9.9 120525 85.05 35207 24.85
46.58 52.43 49.5 12440 8.78 132965 93.83 21178 14.95
52.43 58.29 55.36 5091 3.59 138056 97.43 8738 6.17
58.29 64.15 61.22 3647 2.57 141703 100 3647 2.57
TOTAL 141703 100
Nota: Los intervalos obtenidos aplicando la regla de Sturges son poco prácticos para su respectiva explicación por lo que se decidió agruparlos para facilitar su comprensión. Elaborado por Grupo 2. Fuente: Global Weather Repository.

4.2. Tabla ajustada a amplitud simplificada

TDF_Latitud <- 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)
)

TDF_Latitud[] <- lapply(TDF_Latitud, as.character)

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
)

TDF_Latitud <- rbind(TDF_Latitud, TDF_Total)
colnames(TDF_Latitud) <- c("Lim. Inf.", "Lim. Sup.", "MC", "ni", "hi (%)", "Ni Asc", "Hi Asc (%)", "Ni Dsc", "Hi Dsc (%)")

kable(
  TDF_Latitud,
  align = "c",
  caption = "Tabla N°2: Distribución de frecuencias agrupadas de la Latitud con amplitud de clase de 15°"
) |>
  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_Latitud), 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 agrupadas de la Latitud con amplitud de clase de 15°
Lim. Inf. Lim. Sup. MC ni hi (%) Ni Asc Hi Asc (%) Ni Dsc Hi Dsc (%)
-45 -30 -37.5 3633 2.56 3633 2.56 141703 100
-30 -15 -22.5 11364 8.02 14997 10.58 138070 97.44
-15 0 -7.5 15981 11.28 30978 21.86 126706 89.42
0 15 7.5 35174 24.82 66152 46.68 110725 78.14
15 30 22.5 20022 14.13 86174 60.81 75551 53.32
30 45 37.5 32172 22.7 118346 83.52 55529 39.19
45 60 52.5 21903 15.46 140249 98.97 23357 16.48
60 75 67.5 1454 1.03 141703 100 1454 1.03
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)

hist(
  Latitud,
  breaks = c(Li2, max(Ls2)), # Corregido para usar exactamente tus límites de 15 en 15
  main = "Gráfico N°1: Distribución de frecuencias absolutas de la Latitud\nRegistros meteorológicos mundiales, período 2024–2026",
  xlab = "Latitud (°)",
  ylab = "Frecuencia absoluta (ni)",
  col = "skyblue",
  border = "black",
  xaxt = "n"
)
axis(1, at = c(Li2, max(Ls2)), labels = c(Li2, max(Ls2)), las = 2, cex.axis = 0.8)
grid(nx = NA, ny = NULL, lty = 2, col = "gray")

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

hist(
  Latitud,
  breaks = c(Li2, max(Ls2)),
  ylim = c(0, n_total),
  main = "Gráfico N°2: Histograma de Latitud en Relación al Todo\n(Escala completa de la muestra)",
  xlab = "Latitud (°)",
  ylab = "Frecuencia absoluta (ni)",
  col = "lightgreen",
  border = "black",
  xaxt = "n"
)
axis(1, at = c(Li2, max(Ls2)), labels = c(Li2, max(Ls2)), las = 2, cex.axis = 0.8)
grid(nx = NA, ny = NULL, lty = 2, col = "gray")

5.3. Histograma original (hi)

bp <- barplot(
  hi2,
  names.arg = paste(Li2, Ls2, sep = " a "),
  ylim = c(0, max(hi2) * 1.1),
  main = "Gráfico N°3: Distribución de frecuencias relativas de la Latitud",
  xlab = "Intervalos de Latitud (°)",
  ylab = "Frecuencia Relativa (%)",
  col = "skyblue",
  border = "black",
  las = 2,
  cex.names = 0.8,
  space = 0
)
grid(nx = NA, ny = NULL, lty = 2, col = "gray")

5.4. Histograma con relación a todo (hi)

barplot(
  hi2,
  names.arg = paste(Li2, Ls2, sep = " a "),
  ylim = c(0, 100),
  main = "Gráfico N°4: Histograma Global de Frecuencia Relativa\n(Escala completa de 0 a 100%)",
  xlab = "Intervalos de Latitud (°)",
  ylab = "Frecuencia Relativa (%)",
  col = "lightgreen",
  border = "black",
  las = 2,
  cex.names = 0.8,
  space = 0
)
grid(nx = NA, ny = NULL, lty = 2, col = "gray")

5.5. Polígono de frecuencias (hi)

# Reparación y cierre de la lógica inconclusa del polígono usando barplot para estabilidad
bp <- barplot(
  hi2,
  names.arg = paste(Li2, Ls2, sep = "-"),
  col = "lightblue",
  border = "black",
  ylim = c(0, max(hi2) * 1.2),
  main = "Gráfico N°5: Frecuencia Relativa (%) y Polígono de la Latitud",
  xlab = "Intervalos de Latitud (°)",
  ylab = "Frecuencia relativa (%)",
  las = 2,
  cex.names = 0.8,
  space = 0
)

lines(
  bp,
  hi2,
  type = "o",
  col = "red",
  lwd = 2,
  pch = 16
)
grid(nx = NA, ny = NULL, lty = 2, col = "gray")

5.6. Ojiva ascendente y descendente (ni)

# Completado de forma simétrica a las variables anteriores
eje_x_asc <- c(Li2, max(Ls2))
ni_asc_graf <- c(0, Ni_asc2)

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

plot(
  eje_x_asc,
  ni_asc_graf,
  type = "o",
  pch = 16,
  lwd = 2,
  col = "blue",
  ylim = c(0, n_total),
  main = "Gráfico N°6: Ojivas de Frecuencia Absoluta Acumulada de Latitud",
  xlab = "Latitud (°)",
  ylab = "Frecuencia Acumulada (Ni)",
  xaxt = "n"
)

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

axis(1, at = c(Li2, max(Ls2)), labels = c(Li2, max(Ls2)), las = 2, cex.axis = 0.8)

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.7. Ojiva ascendente y descendente (ni)

# 1. Preparar datos metodológicos para que las curvas inicien/terminen en cero sobre los límites de clase
eje_x_asc <- c(Li2, max(Ls2))
ni_asc_graf <- c(0, Ni_asc2)

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

# 2. 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°6: Ojivas de Frecuencia Absoluta Acumulada de la Latitud",
     xlab = "Latitud (°)", 
     ylab = "Frecuencia Acumulada (Ni)",
     xaxt = "n")

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

# 4. Personalizar Eje X con tus intervalos de 15 en 15 rotados
axis(1, at = c(Li2, max(Ls2)), labels = c(Li2, max(Ls2)), las = 2, cex.axis = 0.8)

# 5. Leyenda y cuadrícula horizontal
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)

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

# 2. 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°7: Ojivas de Frecuencia Relativa Acumulada de la Latitud",
     xlab = "Latitud (°)", 
     ylab = "Frecuencia Acumulada (%)",
     xaxt = "n")

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

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

# 5. Leyenda y cuadrícula horizontal
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

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

# 2. Límites teóricos estándar de Tukey (Acotados al dominio geográfico de tus intervalos)
Lim_inf_Tukey <- max(min(Li2), Q1 - 1.5 * RIC)
Lim_sup_Tukey <- min(max(Ls2), Q3 + 1.5 * RIC)

# 3. Dibujar Boxplot base alineado a la misma escala de las barras anteriores
boxplot(Latitud,
        horizontal = TRUE,
        outline = FALSE,
        ylim = c(min(Li2), max(Ls2)), # Alineación exacta con la escala de tus intervalos (-90 a 90 o límites reales)
        main = "Gráfico N°8: Diagrama de Caja y Bigotes de la Latitud con Límites de Tukey",
        xlab = "Latitud (°)",
        col = "lightblue",
        xaxt = "n")

# 4. Personalizar el eje X para que coincida exactamente con las marcas de las gráficas previas
axis(1, at = c(Li2, max(Ls2)), labels = c(Li2, max(Ls2)), las = 2, cex.axis = 0.8)

# 5. Identificar y dibujar valores atípicos reales personalizados
atipicos <- Latitud[Latitud < (Q1 - 1.5 * RIC) | Latitud > (Q3 + 1.5 * RIC)]

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

# 6. Dibujar las 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)

# 7. Leyenda estadística final 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(Latitud)

mediana <- median(Latitud)

# Moda aproximada usando la clase modal

tabla_moda <- TDF_Latitud[
  TDF_Latitud$`Lim. Inf.` != "TOTAL",
]

ni_num <- as.numeric(tabla_moda$ni)

max_ni <- max(ni_num)

moda <- as.numeric(
  tabla_moda$MC[
    ni_num == max_ni
  ]
)

6.2. Dispersión

varianza <- var(Latitud)

desv_est <- sd(Latitud)

cv <- (desv_est / media) * 100

6.3. Forma

asimetria <- skewness(Latitud)

curtosis <- kurtosis(Latitud)

6.4. Valores atípicos

Q1 <- quantile(Latitud, 0.25)

Q3 <- quantile(Latitud, 0.75)

RIC <- Q3 - Q1

lim_inf <- Q1 - 1.5 * RIC

lim_sup <- Q3 + 1.5 * RIC

atipicos <- Latitud[
  Latitud < lim_inf |
    Latitud > lim_sup
]

n_atipicos <- length(atipicos)

intervalo_atipicos <- paste0(
  "[",
  round(lim_inf,2),
  "; ",
  round(lim_sup,2),
  "]"
)

# Rango

rango_texto <- paste0(
  "[",
  round(min(Latitud),2),
  "; ",
  round(max(Latitud),2),
  "]"
)

6.5. Tabla de indicadores

tabla_indicadores <- data.frame(
  
  Variable = "Latitud",
  
  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 = round(cv,2),
  
  Asimetria = round(asimetria,2),
  
  Curtosis = round(curtosis,2),
  
  Limite_Atipicos = intervalo_atipicos,
  
  N_Atipicos = n_atipicos
)

# Mostrar tabla

kable(
  tabla_indicadores,
  align = "c",
  caption = "Indicadores estadísticos descriptivos de la variable 
  latitud en registros meteorológicos mundiales, período 2024–2026"
) |>
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c(
      "striped",
      "hover",
      "condensed",
      "responsive"
    )
  ) |>
  row_spec(
    0,
    bold = TRUE,
    color = "white",
    background = "#2C3E50"
  )|>
  
  footnote(
    general = "Elaborado por Grupo 2. 
    Fuente: Global Weather Repository.",
    general_title = "Nota: ",
    footnote_as_chunk = TRUE,
    title_format = c("italic","bold")
  )
Indicadores estadísticos descriptivos de la variable latitud en registros meteorológicos mundiales, período 2024–2026
Variable Rango Media Mediana Moda Varianza Desv_Est CV Asimetria Curtosis Limite_Atipicos N_Atipicos
Latitud [-41.3; 64.15] 19.22 17.25 7.5 595.98 24.41 127.03 -0.3 -0.74 [-50.47; 94.92] 0
Nota: Elaborado por Grupo 2.
Fuente: Global Weather Repository.

7. Conclusiones

El análisis de la variable Latitud de las estaciones meteorológicas promedia los 19.22°, dispersándose de manera muy heterogénea en un rango real que va desde los -41.30° hasta los 64.15°, lo cual se confirma con una desviación estándar de 24.41° y un elevado coeficiente de variación del 127.03%. Los datos presentan una distribución platicúrtica (Curtosis = -0.74) y una ligera asimetría negativa (Asimetría = -0.30), lo que indica que las observaciones están ampliamente esparcidas por el plano con una sutil concentración hacia latitudes más altas y con 0 valores atípicos según el umbral de Tukey. Esta distribución es muy beneficiosa para las personas y el análisis ambiental porque certifica que el estudio no se limita a una sola región, sino que posee una amplia representatividad geográfica que abarca tanto el hemisferio sur (zonas templadas) como el hemisferio norte (zonas boreales), permitiendo capturar de manera equitativa la diversidad de microclimas y fenómenos meteorológicos globales sin sesgos de localización.