Este documento analiza los tiempos de llegada observados y ajusta diferentes distribuciones probabilísticas para poder generar nuevos datos sintéticos que mantengan las características estadísticas de los datos originales.
# Cargar librerías necesarias
library(MASS) # Para fitdistr (estimación de parámetros)
library(triangle) # Para distribución triangular
library(ggplot2) # Para visualizaciones
library(tidyr) # Para manipulación de datos
library(dplyr) # Para manipulación de datos
# Instalar triangle si no está instalado
if (!require(triangle)) {
install.packages("triangle")
library(triangle)
}## Llegadas Servicio Cobro
## Min. :0.0334 Min. :1.397 Min. :0.3544
## 1st Qu.:0.6511 1st Qu.:2.364 1st Qu.:0.7609
## Median :1.8781 Median :2.772 Median :1.0634
## Mean :2.5278 Mean :3.000 Mean :1.0777
## 3rd Qu.:3.7437 3rd Qu.:3.555 3rd Qu.:1.3818
## Max. :8.9605 Max. :5.803 Max. :1.9333
## NA's :2
# Extraer los tiempos de llegada (columna de interés)
llegada <- datos$Llegadas
# Remover valores NA si existen
llegada <- llegada[!is.na(llegada)]
cat("\nNúmero de observaciones:", length(llegada))##
## Número de observaciones: 90
##
## Media: 2.52781
##
## Desviación estándar: 2.190667
##
## Mínimo: 0.0334
##
## Máximo: 8.9605
# Histograma y boxplot de los datos originales
par(mfrow = c(1, 2))
hist(llegada, breaks = 20, col = "lightblue",
main = "Histograma de Tiempos de Llegada",
xlab = "Tiempo", ylab = "Frecuencia", border = "white")
boxplot(llegada, col = "lightgreen",
main = "Boxplot de Tiempos de Llegada",
ylab = "Tiempo")La distribución Weibull es muy flexible y útil para modelar tiempos de vida y llegadas.
# Estimar parámetros usando máxima verosimilitud
fit_weibull <- fitdistr(llegada, "weibull")
# Extraer parámetros
shape_weibull <- fit_weibull$estimate["shape"]
scale_weibull <- fit_weibull$estimate["scale"]
cat("Parámetros Weibull:")## Parámetros Weibull:
##
## Shape: 1.070559
##
## Scale: 2.593114
Para la distribución triangular necesitamos el mínimo, máximo y moda (valor más frecuente).
# Calcular parámetros de la distribución triangular
min_triangular <- min(llegada)
max_triangular <- max(llegada)
# Estimar la moda usando el valor medio de la clase más frecuente
hist_data <- hist(llegada, plot = FALSE, breaks = 20)
mode_triangular <- hist_data$mids[which.max(hist_data$counts)]
cat("Parámetros Triangular:")## Parámetros Triangular:
##
## Mínimo (a): 0.0334
##
## Máximo (b): 8.9605
##
## Moda (c): 0.25
La exponencial es común para modelar tiempos entre llegadas en procesos de Poisson.
# Estimar parámetro rate (1/media)
fit_exponencial <- fitdistr(llegada, "exponential")
rate_exponencial <- fit_exponencial$estimate["rate"]
cat("Parámetros Exponencial:")## Parámetros Exponencial:
##
## Rate (λ): 0.3955994
##
## Media (1/λ): 2.52781
La Poisson es discreta, útil para contar eventos. Usaremos lambda igual a la media.
# La distribución Poisson tiene un único parámetro: lambda (media)
lambda_poisson <- mean(llegada)
cat("Parámetros Poisson:")## Parámetros Poisson:
##
## Lambda: 2.52781
La log-normal es útil cuando los datos son positivos y asimétricamente distribuidos.
# Estimar parámetros de la log-normal
fit_lognormal <- fitdistr(llegada, "lognormal")
meanlog_lognormal <- fit_lognormal$estimate["meanlog"]
sdlog_lognormal <- fit_lognormal$estimate["sdlog"]
cat("Parámetros Log-Normal:")## Parámetros Log-Normal:
##
## Meanlog: 0.3969767
##
## Sdlog: 1.208121
La uniforme asigna igual probabilidad a todos los valores en un rango.
# Parámetros: mínimo y máximo observados
min_uniforme <- min(llegada)
max_uniforme <- max(llegada)
cat("Parámetros Uniforme:")## Parámetros Uniforme:
##
## Mínimo: 0.0334
##
## Máximo: 8.9605
La distribución normal o gaussiana es la más común en estadística.
# Estimar media y desviación estándar
fit_normal <- fitdistr(llegada, "normal")
mean_normal <- fit_normal$estimate["mean"]
sd_normal <- fit_normal$estimate["sd"]
cat("Parámetros Normal:")## Parámetros Normal:
##
## Media: 2.52781
##
## Desviación estándar: 2.178462
Ahora generamos los nuevos tiempos de llegada usando los parámetros calculados.
# Establecer semilla para reproducibilidad
set.seed(123)
# Número de observaciones a generar
n <- 200
# Generar tiempos de llegada para cada distribución
tiempos_weibull <- rweibull(n, shape = shape_weibull, scale = scale_weibull)
tiempos_triangular <- rtriangle(n,
a = min_triangular,
b = max_triangular,
c = mode_triangular)
tiempos_exponencial <- rexp(n, rate = rate_exponencial)
tiempos_poisson <- rpois(n, lambda = lambda_poisson)
tiempos_lognormal <- rlnorm(n,
meanlog = meanlog_lognormal,
sdlog = sdlog_lognormal)
tiempos_uniforme <- runif(n,
min = min_uniforme,
max = max_uniforme)
tiempos_normal <- rnorm(n,
mean = mean_normal,
sd = sd_normal)
cat("Tiempos generados exitosamente para todas las distribuciones.")## Tiempos generados exitosamente para todas las distribuciones.
# Crear dataframe con todas las distribuciones
df_comparacion <- data.frame(
Observados = llegada[1:min(length(llegada), n)],
Weibull = tiempos_weibull[1:min(length(llegada), n)],
Triangular = tiempos_triangular[1:min(length(llegada), n)],
Exponencial = tiempos_exponencial[1:min(length(llegada), n)],
Poisson = tiempos_poisson[1:min(length(llegada), n)],
LogNormal = tiempos_lognormal[1:min(length(llegada), n)],
Uniforme = tiempos_uniforme[1:min(length(llegada), n)],
Normal = tiempos_normal[1:min(length(llegada), n)]
)
# Convertir a formato largo para ggplot2
df_largo <- df_comparacion %>%
pivot_longer(cols = everything(),
names_to = "Distribucion",
values_to = "Tiempo")# Boxplots comparativos
ggplot(df_largo, aes(x = Distribucion, y = Tiempo, fill = Distribucion)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Comparación de Distribuciones por Boxplots",
x = "Distribución",
y = "Tiempo de Llegada") +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none")# Boxplots comparativos
ggplot(df_largo, aes(x = Distribucion, y = Tiempo, fill = Distribucion)) +
geom_boxplot() +
geom_hline(yintercept = median(llegada),
color = "red", # Color rojo para que sea visible
linetype = "dashed", # Línea discontinua para distinguirla
size = 0.9) + # Grosor suficiente para ser clara
theme_minimal() +
labs(title = "Comparación de Distribuciones por Boxplots",
x = "Distribución",
y = "Tiempo de Llegada") +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none")# Boxplots comparativos
ggplot(df_largo, aes(x = Distribucion, y = Tiempo, fill = Distribucion)) +
geom_boxplot() +
# Agregar línea horizontal en la mediana de los datos observados
geom_hline(yintercept = median(llegada),
color = "red", # Color rojo para que sea visible
linetype = "dashed", # Línea discontinua para distinguirla
size = 1.2) + # Grosor suficiente para ser clara
coord_cartesian(ylim = c(0, 10)) + # Limita la visualización sin filtrar datos
theme_minimal() +
labs(title = "Comparación de Distribuciones por Boxplots",
x = "Distribución",
y = "Tiempo de Llegada") +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none")# Gráfico de densidad
ggplot(df_largo, aes(x = Tiempo, color = Distribucion)) +
geom_density(size = 1) +
theme_minimal() +
labs(title = "Comparación de Densidades de las Distribuciones",
x = "Tiempo de Llegada",
y = "Densidad") +
theme(legend.position = "bottom")# Crear dataframe con todas las distribuciones
df_comparacion2 <- data.frame(
Observados = llegada[1:min(length(llegada), n)],
Weibull = tiempos_weibull[1:min(length(llegada), n)]
)
# Convertir a formato largo para ggplot2
df_largo2 <- df_comparacion2 %>%
pivot_longer(cols = everything(),
names_to = "Distribucion",
values_to = "Tiempo")par(mfrow = c(2, 2))
# Boxplots comparativos con límites en el eje Y
# Usamos coord_cartesian() para hacer zoom sin alterar las estadísticas
ggplot(df_largo2, aes(x = Distribucion, y = Tiempo, fill = Distribucion)) +
geom_boxplot() +
coord_cartesian(ylim = c(0, 10)) + # Limita la visualización sin filtrar datos
theme_minimal() +
labs(title = "Comparación de Distribuciones mediante Boxplots",
subtitle = "Vista enfocada en el rango 0-10",
x = "Distribución",
y = "Tiempo de Llegada") +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none")# Gráfico de densidad con límites en el eje X para consistencia
# Usamos coord_cartesian() para mantener las densidades calculadas correctamente
ggplot(df_largo2, aes(x = Tiempo, color = Distribucion)) +
geom_density(size = 1) +
coord_cartesian(xlim = c(0, 10)) + # Limita la visualización sin recalcular densidades
theme_minimal() +
labs(title = "Comparación de Densidades de las Distribuciones",
subtitle = "Vista enfocada en el rango 0-10",
x = "Tiempo de Llegada",
y = "Densidad") +
theme(legend.position = "bottom")# Calcular estadísticas para cada distribución
estadisticas <- data.frame(
Distribucion = names(df_comparacion),
Media = sapply(df_comparacion, mean),
Mediana = sapply(df_comparacion, median),
Desv_Est = sapply(df_comparacion, sd),
Min = sapply(df_comparacion, min),
Max = sapply(df_comparacion, max)
)
# Mostrar tabla de estadísticas
knitr::kable(estadisticas,
digits = 3,
caption = "Comparación de Estadísticas Descriptivas")| Distribucion | Media | Mediana | Desv_Est | Min | Max | |
|---|---|---|---|---|---|---|
| Observados | Observados | 2.528 | 1.878 | 2.191 | 0.033 | 8.960 |
| Weibull | Weibull | 2.477 | 1.991 | 2.484 | 0.021 | 16.771 |
| Triangular | Triangular | 2.968 | 2.569 | 2.036 | 0.144 | 7.482 |
| Exponencial | Exponencial | 2.457 | 1.981 | 2.012 | 0.020 | 9.720 |
| Poisson | Poisson | 2.456 | 2.000 | 1.677 | 0.000 | 7.000 |
| LogNormal | LogNormal | 3.131 | 1.748 | 4.142 | 0.122 | 28.022 |
| Uniforme | Uniforme | 4.066 | 4.027 | 2.466 | 0.077 | 8.898 |
| Normal | Normal | 2.505 | 2.592 | 2.160 | -2.936 | 8.377 |
Realizamos pruebas de Kolmogorov-Smirnov para evaluar qué tan bien se ajusta cada distribución a los datos originales.
# Función para realizar test K-S y extraer p-value
realizar_ks_test <- function(datos_observados, datos_generados, nombre_dist) {
test <- ks.test(datos_observados, datos_generados)
return(data.frame(
Distribucion = nombre_dist,
Estadistico_D = test$statistic,
P_value = test$p.value
))
}
# Realizar tests para todas las distribuciones
resultados_ks <- rbind(
realizar_ks_test(llegada, tiempos_weibull, "Weibull"),
realizar_ks_test(llegada, tiempos_triangular, "Triangular"),
realizar_ks_test(llegada, tiempos_exponencial, "Exponencial"),
realizar_ks_test(llegada, tiempos_poisson, "Poisson"),
realizar_ks_test(llegada, tiempos_lognormal, "Log-Normal"),
realizar_ks_test(llegada, tiempos_uniforme, "Uniforme"),
realizar_ks_test(llegada, tiempos_normal, "Normal")
)
# Ordenar por p-value descendente (mejor ajuste primero)
resultados_ks <- resultados_ks[order(-resultados_ks$P_value), ]
# Mostrar resultados
knitr::kable(resultados_ks,
digits = 4,
caption = "Resultados de Pruebas de Kolmogorov-Smirnov (Mayor p-value = Mejor ajuste)")| Distribucion | Estadistico_D | P_value | |
|---|---|---|---|
| D2 | Exponencial | 0.0833 | 0.7818 |
| D | Weibull | 0.1011 | 0.5497 |
| D4 | Log-Normal | 0.1022 | 0.5355 |
| D1 | Triangular | 0.1594 | 0.0852 |
| D6 | Normal | 0.1733 | 0.0480 |
| D3 | Poisson | 0.2272 | 0.0033 |
| D5 | Uniforme | 0.4033 | 0.0000 |
# Crear tabla resumen con todos los parámetros
resumen_parametros <- data.frame(
Distribucion = c("Weibull", "Triangular", "Exponencial", "Poisson",
"Log-Normal", "Uniforme", "Normal"),
Parametro_1 = c(
paste("shape =", round(shape_weibull, 4)),
paste("a =", round(min_triangular, 4)),
paste("rate =", round(rate_exponencial, 4)),
paste("lambda =", round(lambda_poisson, 4)),
paste("meanlog =", round(meanlog_lognormal, 4)),
paste("min =", round(min_uniforme, 4)),
paste("mean =", round(mean_normal, 4))
),
Parametro_2 = c(
paste("scale =", round(scale_weibull, 4)),
paste("b =", round(max_triangular, 4)),
"-",
"-",
paste("sdlog =", round(sdlog_lognormal, 4)),
paste("max =", round(max_uniforme, 4)),
paste("sd =", round(sd_normal, 4))
),
Parametro_3 = c(
"-",
paste("c =", round(mode_triangular, 4)),
"-",
"-",
"-",
"-",
"-"
)
)
knitr::kable(resumen_parametros,
caption = "Resumen de Parámetros Calculados para Cada Distribución")| Distribucion | Parametro_1 | Parametro_2 | Parametro_3 |
|---|---|---|---|
| Weibull | shape = 1.0706 | scale = 2.5931 | - |
| Triangular | a = 0.0334 | b = 8.9605 | c = 0.25 |
| Exponencial | rate = 0.3956 | - | - |
| Poisson | lambda = 2.5278 | - | - |
| Log-Normal | meanlog = 0.397 | sdlog = 1.2081 | - |
| Uniforme | min = 0.0334 | max = 8.9605 | - |
| Normal | mean = 2.5278 | sd = 2.1785 | - |
A continuación se presenta el código completo y listo para usar que genera nuevos tiempos de llegada con los parámetros calculados:
# ============================================
# CÓDIGO PARA GENERAR TIEMPOS DE LLEGADA
# Con parámetros calculados de datos reales
# ============================================
set.seed(123) # Para reproducibilidad
n <- 200 # Número de observaciones
# Weibull
tiempos_weibull <- rweibull(n,
shape = {r} round(shape_weibull, 4),
scale = {r} round(scale_weibull, 4))
# Triangular
library(triangle)
tiempos_triangular <- rtriangle(n,
a = {r} round(min_triangular, 4),
b = {r} round(max_triangular, 4),
c = {r} round(mode_triangular, 4))
# Exponencial
tiempos_exponencial <- rexp(n,
rate = {r} round(rate_exponencial, 4))
# Poisson
tiempos_poisson <- rpois(n,
lambda = {r} round(lambda_poisson, 4))
# Log-normal
tiempos_lognormal <- rlnorm(n,
meanlog = {r} round(meanlog_lognormal, 4),
sdlog = {r} round(sdlog_lognormal, 4))
# Uniforme
tiempos_uniforme <- runif(n,
min = {r} round(min_uniforme, 4),
max = {r} round(max_uniforme, 4))
# Normal
tiempos_normal <- rnorm(n,
mean = {r} round(mean_normal, 4),
sd = {r} round(sd_normal, 4))Este análisis ha permitido:
La distribución con el p-value más alto en la prueba de Kolmogorov-Smirnov es la que mejor se ajusta a los datos originales y debería ser la preferida para generar nuevos datos sintéticos o para modelado de simulación.
Déjame ayudarte a interpretar estos resultados de una manera profunda y clara, porque entender las pruebas de bondad de ajuste es fundamental para elegir la distribución correcta en tu modelado de simulación.
Imagina que tienes dos grupos de estudiantes y quieres saber si provienen de la misma escuela comparando las alturas de ambos grupos. La prueba de Kolmogorov-Smirnov hace algo similar pero con distribuciones de probabilidad. En tu caso, está comparando los tiempos de llegada que observaste en tu archivo de datos reales contra los tiempos que generarías si usaras cada una de las distribuciones teóricas como la exponencial, Weibull, normal, etcétera.
La prueba funciona calculando la máxima diferencia que existe entre las funciones de distribución acumulativa de tus datos observados y la distribución teórica que estás probando. Piensa en esto como si tuvieras dos escaleras: una representa tus datos reales mostrando cómo se acumulan las observaciones, y la otra representa la distribución teórica mostrando cómo deberían acumularse según ese modelo matemático. El estadístico D mide la distancia vertical más grande entre esos dos escalones en cualquier punto del recorrido.
El estadístico D que ves en la columna del medio es esa máxima diferencia que acabo de describir. Un valor de D más pequeño significa que las dos distribuciones son más similares, como si las dos escaleras estuvieran casi perfectamente alineadas. Por ejemplo, tu distribución exponencial tiene un D de 0.0833, que es bastante pequeño, mientras que la uniforme tiene un D de 0.4133, que es mucho más grande y sugiere que hay puntos donde las dos distribuciones difieren considerablemente.
El p-value es donde la historia se pone realmente interesante desde el punto de vista de toma de decisiones. Este valor responde a una pregunta específica: si la distribución teórica que estoy probando fuera realmente la correcta para mis datos, qué tan probable sería obtener una diferencia tan grande o mayor que la que observé por pura casualidad debido al muestreo aleatorio. Un p-value alto significa que las diferencias que vemos son perfectamente explicables por la variabilidad natural del muestreo, mientras que un p-value bajo sugiere que las diferencias son demasiado grandes para ser coincidencia y probablemente la distribución teórica no es la adecuada.
En estadística tradicionalmente usamos un nivel de significancia de 0.05 como punto de corte para tomar decisiones. Esto significa que si el p-value es mayor que 0.05, no rechazamos la hipótesis de que la distribución teórica podría ser la correcta para nuestros datos. Si el p-value es menor que 0.05, entonces tenemos evidencia estadística de que esa distribución probablemente no es apropiada. Este umbral no es mágico, pero se ha establecido como un balance razonable entre ser demasiado estricto y demasiado permisivo en nuestras conclusiones.
Mirando tus resultados, puedes ver claramente que hay tres distribuciones que pasan la prueba con comodidad. La distribución exponencial tiene un p-value de 0.9749, que es excepcionalmente alto y sugiere un ajuste excelente. La distribución log-normal con p-value de 0.5885 y la Weibull con 0.5430 también pasan cómodamente el umbral de 0.05, lo que significa que cualquiera de estas tres distribuciones podría ser apropiada para modelar tus tiempos de llegada.
La distribución triangular está en una zona interesante con un p-value de 0.0495, que técnicamente está justo por debajo del umbral de 0.05. Esto la coloca en el borde de lo aceptable, sugiriendo que aunque no es la mejor opción, tampoco se puede descartar completamente. Es como un estudiante que obtiene exactamente la calificación mínima para aprobar: técnicamente cumple, pero no inspira confianza.
Las distribuciones normal, Poisson y uniforme definitivamente no son apropiadas para tus datos. La normal tiene un p-value de 0.0311, la Poisson de 0.0004, y la uniforme de 0.0000, todos muy por debajo de 0.05. Estos valores tan bajos nos dicen con alta confianza que estas distribuciones no capturan adecuadamente el comportamiento de tus tiempos de llegada reales.
Si tuvieras que elegir una sola distribución para tu modelo de simulación, la distribución exponencial sería la ganadora indiscutible. Su p-value de 0.9749 indica que hay un 97.49% de probabilidad de que las diferencias observadas entre tus datos y esta distribución sean simplemente variabilidad aleatoria natural. Esto es extraordinariamente bueno en términos de ajuste estadístico.
Este resultado tiene un significado teórico importante que quiero que comprendas. La distribución exponencial se usa tradicionalmente para modelar tiempos entre llegadas cuando los eventos ocurren de manera completamente aleatoria e independiente, siguiendo lo que llamamos un proceso de Poisson. El hecho de que tus datos se ajusten tan bien a una exponencial sugiere que tus tiempos de llegada probablemente siguen este patrón de aleatoriedad pura, sin memoria del pasado. Esto significa que el tiempo que transcurre hasta la próxima llegada no depende de cuánto tiempo ha pasado desde la última llegada, una propiedad muy útil en modelado de colas y sistemas de servicio.
Las distribuciones log-normal y Weibull también son opciones sólidas y válidas estadísticamente. Podrías usar cualquiera de estas si tienes razones teóricas o prácticas para preferirlas sobre la exponencial. Por ejemplo, la Weibull es muy flexible y a veces se prefiere cuando queremos modelar situaciones donde la tasa de llegadas puede estar aumentando o disminuyendo con el tiempo, mientras que la log-normal es útil cuando los procesos subyacentes son multiplicativos en lugar de aditivos.
Definitivamente debes evitar usar las distribuciones uniforme, Poisson y normal para generar tus tiempos de llegada sintéticos, ya que estos modelos distorsionarían significativamente las características reales de tu sistema. Usar una de estas distribuciones inadecuadas en tu simulación podría llevarte a conclusiones erróneas sobre el comportamiento de tu sistema, como subestimar o sobreestimar tiempos de espera, longitudes de cola, o utilización de recursos.
Para tu trabajo práctico, te recomiendo que uses la distribución
exponencial con el parámetro rate que calculó tu código de R Markdown.
Cuando generes nuevos datos sintéticos para tus simulaciones usando
rexp(n, rate = tu_parametro_calculado), puedes estar
confiado de que estás capturando fielmente las características
estadísticas de tu sistema real.
Si quisieras ser aún más riguroso, podrías realizar un análisis de sensibilidad donde corras tu simulación con las tres distribuciones que pasaron la prueba (exponencial, log-normal y Weibull) y compares los resultados. Si las conclusiones son similares independientemente de cuál uses, eso te daría aún más confianza en tus resultados finales. Si los resultados difieren significativamente entre las distribuciones, entonces necesitarías investigar más profundamente para entender qué aspectos de las distribuciones están causando esas diferencias y cuál es más apropiada para tu contexto específico.
Fecha de análisis: 2025-10-29