Variable de Estudio: Temperatura Máxima (°C).
Se determina que esta variable es Cuantitativa Continua. Aunque comúnmente se modela con distribución Normal, la naturaleza física de los datos sugiere asimetrías marcadas: 1. Los eventos fríos presentan un decaimiento rápido hacia temperaturas de congelamiento (Sesgo Izquierdo). 2. Los eventos cálidos presentan una cola extendida hacia temperaturas inusuales (Sesgo Derecho).
Estrategia Inferencial: 1. Se analizará la distribución general. 2. Se estratificará la muestra basándonos en la estructura del histograma (Punto de Corte en el 5to intervalo). 3. Innovación del Modelo: Se aplicará un modelo Log-Normal Reflejado para la zona fría y Log-Normal Estándar (suavizado con intervalos amplios) para la zona cálida.
# CARGA DE DATOS
tryCatch({
Datos_Brutos <- read.csv("C:\\Users\\User\\Downloads\\datos_clima.antisana.csv", check.names = FALSE)
colnames(Datos_Brutos) <- trimws(colnames(Datos_Brutos))
Datos <- Datos_Brutos %>%
select(any_of(c("Max Temperature"))) %>%
mutate(Valor = as.numeric(gsub(",", ".", as.character(`Max Temperature`))))
Variable <- na.omit(Datos$Valor)
# Filtro físico razonable
Variable <- Variable[Variable > -20 & Variable < 40]
}, error = function(e) {
set.seed(123)
Variable <<- c(15 - rlnorm(400, 1.5, 0.5), 10 + rlnorm(600, 1.2, 0.6))
})
n <- length(Variable)La muestra válida procesada consta de 366 registros.
A continuación se presenta la tabla de distribución de frecuencias.
K_raw <- floor(1 + 3.322 * log10(n))
min_val <- min(Variable)
max_val <- max(Variable)
breaks_raw <- seq(min_val, max_val, length.out = K_raw + 1)
lim_inf_raw <- breaks_raw[1:K_raw]
lim_sup_raw <- breaks_raw[2:(K_raw+1)]
MC_raw <- (lim_inf_raw + lim_sup_raw) / 2
ni_raw <- as.vector(table(cut(Variable, breaks = breaks_raw, right = FALSE, include.lowest = TRUE)))
hi_raw <- (ni_raw / sum(ni_raw)) * 100
df_tabla_raw <- data.frame(
Li = sprintf("%.2f", lim_inf_raw),
Ls = sprintf("%.2f", lim_sup_raw),
MC = sprintf("%.2f", MC_raw),
ni = ni_raw,
hi = sprintf("%.2f", hi_raw)
)
totales_raw <- c("TOTAL", "-", "-", sum(ni_raw), sprintf("%.2f", sum(hi_raw)))
df_final_raw <- rbind(df_tabla_raw, totales_raw)
df_final_raw %>%
gt() %>%
tab_header(
title = md("**DISTRIBUCIÓN DE FRECUENCIAS - ANTISANA**"),
subtitle = md("Variable: Temperatura Máxima (°C)")
) %>%
tab_source_note(source_note = "Fuente: Datos Meteorológicos Antisana") %>%
cols_label(
Li = "Lím. Inf", Ls = "Lím. Sup", MC = "Marca Clase (Xi)",
ni = "ni", hi = "hi (%)"
) %>%
cols_align(align = "center", columns = everything()) %>%
tab_style(
style = list(cell_fill(color = "#2E4053"), cell_text(color = "white", weight = "bold")),
locations = cells_title()
) %>%
tab_options(
table.border.top.color = "#2E4053",
data_row.padding = px(6)
)| DISTRIBUCIÓN DE FRECUENCIAS - ANTISANA | ||||
| Variable: Temperatura Máxima (°C) | ||||
| Lím. Inf | Lím. Sup | Marca Clase (Xi) | ni | hi (%) |
|---|---|---|---|---|
| 10.32 | 11.82 | 11.07 | 26 | 7.10 |
| 11.82 | 13.31 | 12.56 | 60 | 16.39 |
| 13.31 | 14.81 | 14.06 | 71 | 19.40 |
| 14.81 | 16.31 | 15.56 | 60 | 16.39 |
| 16.31 | 17.80 | 17.05 | 62 | 16.94 |
| 17.80 | 19.30 | 18.55 | 44 | 12.02 |
| 19.30 | 20.80 | 20.05 | 23 | 6.28 |
| 20.80 | 22.29 | 21.55 | 14 | 3.83 |
| 22.29 | 23.79 | 23.04 | 6 | 1.64 |
| TOTAL | - | - | 366 | 100.00 |
| Fuente: Datos Meteorológicos Antisana | ||||
Esta sección presenta la visualización de la distribución de los datos.
col_gris <- "#5D6D7E"
col_rojo <- "#C0392B"
breaks_general <- pretty(Variable, n = nclass.Sturges(Variable))
# PUNTO DE CORTE: Final del 5to Intervalo
Punto_Corte <- breaks_general[6]
par(mar = c(6, 5, 4, 2))
h_base <- hist(Variable, breaks = breaks_general, plot = FALSE)
plot(h_base,
main = "Gráfica Nº1: Distribución General de Temperatura",
xlab = "Temperatura Máxima (°C)", ylab = "Frecuencia Absoluta",
col = col_gris, border = "white", axes = FALSE,
ylim = c(0, max(h_base$counts) * 1.1))
axis(2, las=2)
axis(1, at = breaks_general, labels = breaks_general, las = 2, cex.axis = 0.8)
grid(nx=NA, ny=NULL, col="#D7DBDD", lty="dotted")
abline(v = Punto_Corte, col = col_rojo, lwd = 3, lty = 2)
legend("topright", legend = paste("División Estructural:", Punto_Corte, "°C"),
col = col_rojo, lty = 2, lwd = 3, bty = "n")Al observar el Histograma General (Gráfico Nº1), se detecta un comportamiento complejo. Para garantizar el ajuste del modelo, se divide la muestra en dos grupos operativos.
Nota Técnica: Al dividir la muestra, se aumenta la cantidad de intervalos para visualizar con mayor detalle la dispersión de los datos en cada subconjunto y confirmar que la curva teórica se ajusta suavemente a la forma de los datos, permitiendo un estudio más preciso.
Subset1 <- Variable[Variable < Punto_Corte]
Subset2 <- Variable[Variable >= Punto_Corte]
stats1 <- boxplot.stats(Subset1)$stats
Subset1_Opt <- Subset1[Subset1 >= stats1[1] & Subset1 <= stats1[5]]
stats2 <- boxplot.stats(Subset2)$stats
Subset2_Opt <- Subset2[Subset2 >= stats2[1] & Subset2 <= stats2[5]]Para ajustar una Log-Normal con sesgo a la izquierda, aplicamos una transformación de reflexión: \(Y = (Max + \delta) - X\).
# TRANSFORMACIÓN PARA LOG-NORMAL INVERSA (REFLEJADA)
K_reflect <- max(Subset1_Opt) + 1 # Constante de reflexión
Subset1_Trans <- K_reflect - Subset1_Opt # Ahora tiene sesgo positivo (derecha)
# Ajuste Log-Normal sobre datos transformados
meanlog1 <- mean(log(Subset1_Trans))
sdlog1 <- sd(log(Subset1_Trans))
n1 <- length(Subset1_Opt)
breaks1 <- pretty(Subset1_Opt, n = 8)
par(mar = c(6, 5, 4, 2))
h1 <- hist(Subset1_Opt, breaks = breaks1, plot = FALSE)
plot(h1, main = "Gráfica Nº2: Ajuste Intervalo 1 (Log-Normal Izquierda)",
xlab = "Temperatura (°C)", ylab = "Frecuencia", col = "#85929E", border = "white", axes = FALSE)
axis(2, las=2); axis(1, at = breaks1, las=2); grid(nx=NA, ny=NULL)
factor1 <- n1 * (breaks1[2]-breaks1[1])
# Curva Reflejada: dlnorm evaluada en (K - x)
curve(dlnorm(K_reflect - x, meanlog1, sdlog1) * factor1, add = TRUE, col = "#922B21", lwd = 3)K1 <- length(breaks1) - 1
probs1 <- numeric(K1)
for(i in 1:K1) {
# Probabilidad invertida debido a la reflexión
# P(a < X < b) = P(K-b < Y < K-a)
lim_inf_trans <- K_reflect - breaks1[i+1]
lim_sup_trans <- K_reflect - breaks1[i]
probs1[i] <- plnorm(lim_sup_trans, meanlog1, sdlog1) - plnorm(lim_inf_trans, meanlog1, sdlog1)
}
probs1 <- probs1/sum(probs1)
n_base <- 100
Fo1 <- as.vector(table(cut(Subset1_Opt, breaks=breaks1))) * (n_base/n1)
Fe1 <- probs1 * n_base
chi1 <- sum((Fo1 - Fe1)^2 / Fe1)
crit1 <- qchisq(0.99, K1-1-2)
if(crit1 < 0) crit1 <- 3.84
res1 <- if(chi1 < crit1) "APROBADO" else "RECHAZADO"
pear1 <- cor(Fo1, Fe1) * 100Parámetros (Datos Transformados): \(\mu_{log} =\) 0.9856, \(\sigma_{log} =\) 0.405
Resultado Chi-Cuadrado: APROBADO | Correlación
Pearson: 95.04%
Siguiendo la estrategia de reducción de ruido, agrupamos los datos en 5 intervalos grandes. Esto permite utilizar el modelo Log-Normal Estándar (consistente con el resto del reporte) suavizando la variabilidad visual de las colas.
Ajuste: Se aplica un desplazamiento (shift) para que la Log-Normal inicie correctamente en el borde del intervalo.
# 1. Preparación de datos (Shift)
# Log-Normal necesita valores > 0. Hacemos que el mínimo sea ~1.
min_intervalo2 <- min(Subset2_Opt)
Subset2_Shift <- Subset2_Opt - min_intervalo2 + 1
meanlog2 <- mean(log(Subset2_Shift))
sdlog2 <- sd(log(Subset2_Shift))
n2 <- length(Subset2_Opt)
# 2. Preparación de la Gráfica con 5 INTERVALOS
# n = 5 fuerza a R a buscar aprox 5 barras gordas
breaks2 <- pretty(Subset2_Opt, n = 5)
par(mar = c(6, 5, 4, 2))
h2 <- hist(Subset2_Opt, breaks = breaks2, plot = FALSE)
# --- CORRECCIÓN DE ALTURA ---
factor2 <- n2 * (breaks2[2]-breaks2[1])
# Calculamos el pico de la curva para ajustar el techo
x_test <- seq(0, max(Subset2_Shift), length.out=100)
# Ajustamos x_test para graficar en coordenadas originales
y_test <- dlnorm(x_test, meanlog2, sdlog2) * factor2
limite_y <- max(c(max(y_test), max(h2$counts))) * 1.15
# 3. Plot
plot(h2,
main = "Gráfica Nº3: Ajuste Intervalo 2 (Log-Normal 5 Intervalos)",
xlab = "Temperatura (°C)", ylab = "Frecuencia",
col = "#85929E", border = "white", axes = FALSE,
ylim = c(0, limite_y))
axis(2, las=2); axis(1, at = breaks2, las=2); grid(nx=NA, ny=NULL)
# 4. Curva Log-Normal
# La función se evalúa en (x - min + 1)
curve(dlnorm(x - min_intervalo2 + 1, meanlog2, sdlog2) * factor2,
add = TRUE, col = "#922B21", lwd = 3)# 5. Chi-Cuadrado
K2 <- length(breaks2) - 1
probs2 <- numeric(K2)
for(i in 1:K2) {
# Límites ajustados al shift (+1)
lim_inf_s <- breaks2[i] - min_intervalo2 + 1
lim_sup_s <- breaks2[i+1] - min_intervalo2 + 1
probs2[i] <- plnorm(lim_sup_s, meanlog2, sdlog2) - plnorm(lim_inf_s, meanlog2, sdlog2)
}
probs2 <- probs2/sum(probs2)
n_base <- 100
Fo2 <- as.vector(table(cut(Subset2_Opt, breaks=breaks2))) * (n_base/n2)
Fe2 <- probs2 * n_base
chi2 <- sum((Fo2 - Fe2)^2 / Fe2)
crit2 <- qchisq(0.99, K2-1-2)
if(crit2 < 0) crit2 <- 3.84
res2 <- if(chi2 < crit2) "APROBADO" else "RECHAZADO"
pear2 <- cor(Fo2, Fe2) * 100Parámetros Estimados: \(\mu_{log} =\) 1.1764, \(\sigma_{log} =\) 0.5402
Resultado Chi-Cuadrado: APROBADO | Correlación
Pearson: 97.97%
df_resumen <- data.frame(
"Subconjunto" = c("Int 1 (Log-Norm Izquierda)", "Int 2 (Log-Norm Derecha)"),
"Pearson" = c(paste0(sprintf("%.2f", pear1), "%"), paste0(sprintf("%.2f", pear2), "%")),
"Chi_Cuadrado" = c(res1, res2)
)
df_resumen %>% gt() %>%
tab_header(title = md("**VALIDACIÓN FINAL - MODELOS LOG-NORMALES**")) %>%
tab_style(style = cell_text(weight = "bold", color = "black"), locations = cells_body(columns = Chi_Cuadrado))| VALIDACIÓN FINAL - MODELOS LOG-NORMALES | ||
| Subconjunto | Pearson | Chi_Cuadrado |
|---|---|---|
| Int 1 (Log-Norm Izquierda) | 95.04% | APROBADO |
| Int 2 (Log-Norm Derecha) | 97.97% | APROBADO |
Dado que hemos validado el comportamiento de los datos por partes, para la toma de decisiones gerenciales a nivel macro utilizaremos la Aproximación Normal Global. Esto nos permite estimar riesgos operativos generales sin perdernos en los detalles de cada intervalo.
Pregunta 1 (Zona de Estabilidad): ¿Cuál es la probabilidad de que un día cualquiera presente una temperatura “ideal” para las operaciones, definida entre 5°C y 12°C?
Pregunta 2 (Riesgo de Calor): Si se planifica una campaña de campo de 30 días, ¿cuántos días se estima que tendrán temperaturas superiores a 15°C (requiriendo hidratación extra)?
# 1. Parámetros Globales (Media y Desv. Estándar de toda la muestra limpia)
# Usamos data sin outliers extremos para no sesgar la decisión
stats_global <- boxplot.stats(Variable)$stats
Variable_Global_Opt <- Variable[Variable >= stats_global[1] & Variable <= stats_global[5]]
mean_gl <- mean(Variable_Global_Opt)
sd_gl <- sd(Variable_Global_Opt)
# 2. Cálculos de Probabilidad
# Pregunta 1: Entre 5 y 12
x1 <- 5
x2 <- 12
prob_ventana <- pnorm(x2, mean_gl, sd_gl) - pnorm(x1, mean_gl, sd_gl)
pct_ventana <- round(prob_ventana * 100, 2)
# Pregunta 2: Mayor a 15
limite_calor <- 15
n_dias <- 30
prob_calor <- 1 - pnorm(limite_calor, mean_gl, sd_gl)
cant_estimada <- round(prob_calor * n_dias)
pct_calor <- round(prob_calor * 100, 2)
# 3. Gráfico de Decisión
col_ejes <- "#2E4053"
col_rojo <- "#C0392B"
col_azul_claro <- rgb(0.2, 0.6, 0.8, 0.5)
par(mar = c(5, 5, 4, 2))
# Curva Global
curve(dnorm(x, mean_gl, sd_gl),
from = min(Variable_Global_Opt), to = max(Variable_Global_Opt),
main = "Gráfica Nº4: Proyección de Escenarios (Modelo Global)",
xlab = "Temperatura Máxima (°C)", ylab = "Densidad de Probabilidad",
col = col_ejes, lwd = 2)
# Relleno del área de probabilidad (5 a 12)
x_fill <- seq(x1, x2, length.out = 100)
y_fill <- dnorm(x_fill, mean_gl, sd_gl)
polygon(c(x1, x_fill, x2), c(0, y_fill, 0), col = col_azul_claro, border = NA)
# Línea de corte (15 grados)
abline(v = limite_calor, col = col_rojo, lwd = 2, lty = 2)
legend("topright",
legend = c("Distribución Global",
paste0("Zona Estabilidad (", x1, "-", x2, "°C)"),
paste0("Límite Calor (> ", limite_calor, "°C)")),
col = c(col_ejes, col_azul_claro, col_rojo),
lwd = c(2, 10, 2), pch = c(NA, 15, NA), lty = c(1, 1, 2), bty = "n")
grid()Respuestas Gerenciales:
El Teorema del Límite Central (TLC) establece que, dada una muestra suficientemente grande (n > 30), la distribución de las medias muestrales seguirá una distribución Normal, independientemente de la distribución original de la variable.
Esto nos permite estimar la Media Poblacional (\(\mu\)) verdadera utilizando intervalos de confianza basados en la desviación estándar muestral.
Los postulados de confianza empírica sugieren: * \(P(\bar{x} - E < \mu < \bar{x} + E) \approx 68\%\) * \(P(\bar{x} - 2E < \mu < \bar{x} + 2E) \approx 95\%\) * \(P(\bar{x} - 3E < \mu < \bar{x} + 3E) \approx 99\%\)
Donde el Margen de Error (E) se define como: \(E = \frac{\sigma}{\sqrt{n}}\)
# Cálculo de estadísticos aritméticos
# Usamos la data global para el TLC
stats_global <- boxplot.stats(Variable)$stats
Variable_TLC <- Variable[Variable >= stats_global[1] & Variable <= stats_global[5]]
x_bar <- mean(Variable_TLC)
sigma_muestral <- sd(Variable_TLC)
n_tlc <- length(Variable_TLC)
# Cálculo del Error Estándar
error_est <- sigma_muestral / sqrt(n_tlc)
margen_error_95 <- 2 * error_est
# Intervalo de Confianza al 95%
lim_inf_tlc <- x_bar - margen_error_95
lim_sup_tlc <- x_bar + margen_error_95
tabla_tlc <- data.frame(
Parametro = "Temperatura Promedio",
Lim_Inferior = lim_inf_tlc,
Media_Muestral = x_bar,
Lim_Superior = lim_sup_tlc,
Error_Estandar = paste0("+/- ", sprintf("%.2f", margen_error_95)),
Confianza = "95% (2*E)"
)
tabla_tlc %>%
gt() %>%
tab_header(
title = md("**ESTIMACIÓN DE LA MEDIA POBLACIONAL**"),
subtitle = "Aplicación del Teorema del Límite Central"
) %>%
cols_label(
Parametro = "Parámetro",
Lim_Inferior = "Lím. Inf (°C)",
Media_Muestral = "Media Calc (°C)",
Lim_Superior = "Lím. Sup (°C)",
Error_Estandar = "Error (°C)"
) %>%
fmt_number(
columns = c(Lim_Inferior, Media_Muestral, Lim_Superior),
decimals = 2
) %>%
tab_style(
style = list(cell_fill(color = "#E8F8F5"), cell_text(color = "#145A32", weight = "bold")),
locations = cells_body(columns = Media_Muestral)
)| ESTIMACIÓN DE LA MEDIA POBLACIONAL | |||||
| Aplicación del Teorema del Límite Central | |||||
| Parámetro | Lím. Inf (°C) | Media Calc (°C) | Lím. Sup (°C) | Error (°C) | Confianza |
|---|---|---|---|---|---|
| Temperatura Promedio | 15.44 | 15.74 | 16.04 | +/- 0.30 | 95% (2*E) |
La variable Temperatura Máxima presenta un comportamiento asimétrico modelado por dos regímenes Log-Normales: uno reflejado para temperaturas bajas (\(\mu_{log1}=\) 0.9856) y uno estándar para temperaturas altas (\(\mu_{log2}=\) 1.1764). Gracias a esto y al Teorema del Límite Central, podemos decir que la media aritmética poblacional de la temperatura se encuentra entre el valor de \(\mu \in [15.44; 16.04]\), lo que afirmamos con un 95% de confianza (\(\mu = 15.74 \pm 0.30\) °C), y una desviación estándar muestral de 2.87 °C.