Procesos de Poisson usando Distribución exponencial

Author

Mario Cruz

1 ANÁLISIS DE LOS TIEMPOS DE ATENCIÓN DE PACIENTES CLÍNICA SAN RAFAEL DE SANTA ANA

1.1 Verificación de la Distribución Exponencial

1.2 Cargar librerías necesarias

library(readxl)       # Para leer archivos Excel
library(dplyr)        # Manipulación de datos

Adjuntando el paquete: 'dplyr'
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
library(ggplot2)      # Visualización
Warning: package 'ggplot2' was built under R version 4.5.2
library(fitdistrplus) # Ajuste de distribuciones
Warning: package 'fitdistrplus' was built under R version 4.5.2
Cargando paquete requerido: MASS

Adjuntando el paquete: 'MASS'
The following object is masked from 'package:dplyr':

    select
Cargando paquete requerido: survival
library(DescTools)    # Funciones estadísticas adicionales
Warning: package 'DescTools' was built under R version 4.5.2
library(car)          # Para tests de normalidad
Cargando paquete requerido: carData

Adjuntando el paquete: 'car'
The following object is masked from 'package:DescTools':

    Recode
The following object is masked from 'package:dplyr':

    recode
library(EnvStats)     # Para pruebas con distribución exponencial
Warning: package 'EnvStats' was built under R version 4.5.2

Adjuntando el paquete: 'EnvStats'
The following object is masked from 'package:car':

    qqPlot
The following object is masked from 'package:MASS':

    boxcox
The following objects are masked from 'package:stats':

    predict, predict.lm
The following object is masked from 'package:base':

    print.default
library(goftest)
Warning: package 'goftest' was built under R version 4.5.2

1.3 Importar la base de datos

library(readxl)
datos <- read_excel("simulacion/simulacion_clinicaSanRafael_semana_seed42.xlsx")

# Vista general
cat("Base importada con éxito.\n\n")
Base importada con éxito.
str(datos)
tibble [415 × 5] (S3: tbl_df/tbl/data.frame)
 $ Fecha                    : chr [1:415] "2024-10-07" "2024-10-07" "2024-10-07" "2024-10-07" ...
 $ Hora_Llegada             : chr [1:415] "08:03:45" "08:14:17" "08:15:38" "08:16:07" ...
 $ Tiempo_entre_Llegadas_min: chr [1:415] "-" "10.53" "1.36" "0.48" ...
 $ Numero_Pacientes         : num [1:415] 3 1 1 2 1 3 1 1 1 1 ...
 $ Comentarios              : chr [1:415] "Paciente U - Consulta pediátrica (viene acompañado por un familiar); Paciente X - Dolor de cabeza persistente ("| __truncated__ "Paciente D - Consulta por ansiedad o estrés (viene acompañado por un familiar)" "Paciente S - Control de colesterol (viene acompañado por un familiar)" "Paciente A - Control de presión arterial (espera consulta con el Dr. Martínez); Paciente H - Problemas digestiv"| __truncated__ ...
head(datos)
# A tibble: 6 × 5
  Fecha      Hora_Llegada Tiempo_entre_Llegadas_min Numero_Pacientes Comentarios
  <chr>      <chr>        <chr>                                <dbl> <chr>      
1 2024-10-07 08:03:45     -                                        3 Paciente U…
2 2024-10-07 08:14:17     10.53                                    1 Paciente D…
3 2024-10-07 08:15:38     1.36                                     1 Paciente S…
4 2024-10-07 08:16:07     0.48                                     2 Paciente A…
5 2024-10-07 08:23:28     7.35                                     1 Paciente A…
6 2024-10-07 08:23:38     0.17                                     3 Paciente W…

1.4 Verificación y limpieza de la base

cat("\n Verificación de valores faltantes \n")

 Verificación de valores faltantes 
colSums(is.na(datos))
                    Fecha              Hora_Llegada Tiempo_entre_Llegadas_min 
                        0                         0                         0 
         Numero_Pacientes               Comentarios 
                        0                         0 
# Eliminar filas con tiempos no válidos o "-"
datos <- datos%>%
  filter(Tiempo_entre_Llegadas_min != "-") %>%
  mutate(Tiempo_entre_Llegadas_min = as.numeric(Tiempo_entre_Llegadas_min))

# Verificar duplicados
cat("\n Verificación de duplicados \n")

 Verificación de duplicados 
sum(duplicated(datos))
[1] 0
# Resumen estadístico de la variable de interés
cat("\n Resumen descriptivo del tiempo entre llegadas (minutos) \n")

 Resumen descriptivo del tiempo entre llegadas (minutos) 
summary(datos$Tiempo_entre_Llegadas_min)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.040   2.220   5.430   7.951  11.425  45.560 
  • Verificamos valores faltantes (NA).

  • Elimina filas con guiones “-”, esto porque no hay tiempo entre llegada con el primer paciente con el anterior.

  • Convierte los tiempos a numéricos.

  • Busca duplicados.

  • Calcula estadísticas descriptivas (mínimo, máximo, media, mediana, etc.)

1.5 Análisis exploratorio

# Histograma + densidad
ggplot(datos, aes(x = Tiempo_entre_Llegadas_min)) +
  geom_histogram(aes(y = ..density..), bins = 25, fill = "lightblue", color = "black") +
  geom_density(color = "red", linewidth = 1) +
  labs(title = "Distribución de los tiempos entre llegadas",
       x = "Tiempo entre llegadas (minutos)",
       y = "Densidad") +
  theme_minimal()
Warning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.
ℹ Please use `after_stat(density)` instead.

# Boxplot para detectar outliers
ggplot(datos, aes(y = Tiempo_entre_Llegadas_min)) +
  geom_boxplot(fill = "orange", color = "black") +
  labs(title = "Boxplot de los tiempos entre llegadas",
       y = "Minutos entre llegadas") +
  theme_minimal()

#------------------ los mismos graficos pero mas bonutos -----------------------------------

library(ggplot2)

#--- histograma

ggplot(datos, aes(x = Tiempo_entre_Llegadas_min, y = ..density..)) +
  geom_histogram(
    bins = 25,
    aes(fill = ..density..),
    color = "#4B4B4B",
    alpha = 0.8
  ) +
  scale_fill_gradient(low = "#B0E0E6", high = "#4682B4") +  # degradado azul
  geom_density(
    color = "#FF4500",
    linewidth = 1.2,
    fill = "#FF4500",
    alpha = 0.2
  ) +
  labs(
    title = "Distribución de los Tiempos entre Llegadas",
    subtitle = "Histograma con curva de densidad",
    x = "Tiempo entre llegadas (minutos)",
    y = "Densidad"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(face = "bold", size = 18, hjust = 0.5, color = "#333333"),
    plot.subtitle = element_text(size = 12, hjust = 0.5, color = "gray40"),
    axis.title = element_text(face = "bold"),
    axis.text = element_text(color = "#333333"),
    panel.grid.minor = element_blank()
  )

#--- cajas

ggplot(datos, aes(x = "", y = Tiempo_entre_Llegadas_min)) +
  geom_boxplot(
    fill = "#FFA500",       # color naranja
    color = "#4B4B4B",      # borde gris oscuro
    outlier.color = "red",  # outliers en rojo
    outlier.shape = 16,
    outlier.size = 3,
    notch = TRUE
  ) +
  geom_jitter(width = 0.1, alpha = 0.5, color = "#FF4500") + # puntos dispersos
  labs(
    title = "Distribución de Tiempos entre Llegadas",
    subtitle = "Boxplot",
    y = "Minutos entre llegadas",
    x = ""
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
    plot.subtitle = element_text(size = 12, hjust = 0.5, color = "gray40"),
    axis.title.y = element_text(face = "bold"),
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank()
  )

distribución es asimétrica a la derecha, concuerda con la forma de una distribución exponencial.

Boxplot muestra que hay tiempos muy grandes entre la llegada de un paciente con otro.

1.6 Cálculo de parámetros de la distribución exponencial

media_tiempo <- mean(datos$Tiempo_entre_Llegadas_min)
lambda <- 1 / media_tiempo

cat("\n--- Parámetros estimados ---\n")

--- Parámetros estimados ---
cat("Media de tiempos entre llegadas:", round(media_tiempo, 3), "minutos\n")
Media de tiempos entre llegadas: 7.951 minutos
cat("Lambda estimado:", round(lambda, 5), "\n")
Lambda estimado: 0.12576 

Calcula el parámetro λ (lambda) de la distribución exponencial:

\[ \lambda = \frac{1}{\text{media}} \]

1.7 Ajuste a la distribución exponencial

ajuste_exp <- fitdist(datos$Tiempo_entre_Llegadas_min, "exp")
summary(ajuste_exp)
Fitting of the distribution ' exp ' by maximum likelihood 
Parameters : 
      estimate  Std. Error
rate 0.1257648 0.006225894
Loglikelihood:  -1253.923   AIC:  2509.847   BIC:  2513.858 
# Visualización del ajuste
plot(ajuste_exp)

1.8 Pruebas de bondad de ajuste

cat("\n--- Prueba Kolmogorov–Smirnov ---\n")

--- Prueba Kolmogorov–Smirnov ---
ks_test <- ks.test(datos$Tiempo_entre_Llegadas_min, "pexp", rate = lambda)
Warning in ks.test.default(datos$Tiempo_entre_Llegadas_min, "pexp", rate =
lambda): ties should not be present for the one-sample Kolmogorov-Smirnov test
print(ks_test)

Results of Hypothesis Test
--------------------------

Alternative Hypothesis:          two-sided

Test Name:                       Asymptotic one-sample Kolmogorov-Smirnov test

Data:                            datos$Tiempo_entre_Llegadas_min

Test Statistic:                  D = 0.02483314

P-value:                         0.9629095
cat("\n--- Prueba de Anderson–Darling ---\n")

--- Prueba de Anderson–Darling ---
ad_test <- ad.test(datos$Tiempo_entre_Llegadas_min, null = "pexp", rate = lambda)
print(ad_test)

Results of Hypothesis Test
--------------------------

Alternative Hypothesis:          

Test Name:                       Anderson-Darling test of goodness-of-fitNull hypothesis: exponential distributionwith parameter    rate = 0.125764838247307Parameters assumed to be fixed

Data:                            datos$Tiempo_entre_Llegadas_min

Test Statistic:                  An = 0.3106723

P-value:                         0.9298914
cat("\n--- Prueba de Shapiro–Wilk (para descartar normalidad) ---\n")

--- Prueba de Shapiro–Wilk (para descartar normalidad) ---
print(shapiro.test(datos$Tiempo_entre_Llegadas_min))

Results of Hypothesis Test
--------------------------

Alternative Hypothesis:          

Test Name:                       Shapiro-Wilk normality test

Data:                            datos$Tiempo_entre_Llegadas_min

Test Statistic:                  W = 0.8455681

P-value:                         1.442319e-19

1.8.1 Prueba de Kolmogorov–Smirnov (KS)

Objetivo: Verificar si los datos (Tiempo_entre_Llegadas_min) siguen una distribución exponencial con parámetro lambda.

Resultado:

P-valor =0.963

La hipótesis nula es que los datos provienen de una distribución exponencial con la tasa lambda. Como el p-valor es mayor a 0.05, no se rechaza la hipótesis nula

1.8.2 Prueba de Anderson–Darling (AD)

Objetivo: Evaluar si los datos (Tiempo_entre_Llegadas_min) siguen una distribución exponencial con parámetro lambda.

Resultado:
P-valor = 0.930

La hipótesis nula es que los datos siguen una distribución exponencial. Como el p-valor es mayor a 0.05, no se rechaza la hipótesis nula. Esto confirma que los datos se ajustan adecuadamente a la distribución exponencial.

1.8.3 Prueba de Shapiro–Wilk (Normalidad)

Objetivo: Comprobar si los datos (Tiempo_entre_Llegadas_min) siguen una distribución normal.

Resultado:
P-valor = 1.442319e-19

La hipótesis nula es que los datos provienen de una distribución normal. Como el p-valor es mucho menor a 0.05, se rechaza la hipótesis nula. Esto indica que los datos no son normales, lo cual es consistente con el modelo exponencial esperado para los tiempos entre llegadas de pacientes.

1.9 Gráficos comparativos

# Histograma con la densidad teórica superpuesta
ggplot(datos, aes(x = Tiempo_entre_Llegadas_min)) +
  geom_histogram(aes(y = ..density..), bins = 25, fill = "skyblue", color = "black") +
  stat_function(fun = dexp, args = list(rate = lambda), color = "red", size = 1.2) +
  labs(title = "Ajuste de la distribución exponencial",
       x = "Tiempo entre llegadas (minutos)",
       y = "Densidad") +
  theme_minimal()
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.

# Función de distribución acumulada
ggplot(datos, aes(x = Tiempo_entre_Llegadas_min)) +
  stat_ecdf(geom = "step", color = "blue") +
  stat_function(fun = pexp, args = list(rate = lambda), color = "red", size = 1) +
  labs(title = "Comparación entre F(x) empírica y teórica",
       x = "Tiempo entre llegadas (minutos)",
       y = "Probabilidad acumulada") +
  theme_minimal()

1.10 Extensión del análisis: Relación entre la distribución Exponencial y la distribución Gamma

En este estudio se analizaron los tiempos entre llegadas de pacientes a la Clínica San Rafael bajo el supuesto que los tiempos entre eventos sigan una distribución exponencial. Este modelo fue implementado en el análisis inicial y permitió evaluar, mediante la prueba de Kolmogorov-Smirnov, la adecuación de los datos simulados al modelo teórico.

Sin embargo, es importante destacar que la distribución exponencial es un caso particular de la distribución Gamma, con parámetro de forma (shape) ( k = 1 ). Por tanto, la distribución Gamma puede considerarse una generalización de la exponencial, capaz de modelar escenarios más complejos en los que:

  • Los tiempos entre eventos no son completamente independientes.
  • Se acumulan varios tiempos de espera hasta que ocurren múltiples eventos (por ejemplo, el tiempo hasta la llegada del segundo o tercer paciente).
  • Existe mayor variabilidad (dispersión) en los tiempos de llegada que la esperada bajo la distribución exponencial.

En consecuencia, la distribución exponencial empleada en este análisis puede interpretarse como una aproximación simplificada de la distribución Gamma. A continuación, se incluye un bloque de código que permite comparar ambos ajustes (Exponencial y Gamma) utilizando los datos simulados:

# --- Cargar librerías necesarias ---
library(readxl)
library(MASS)

# --- Leer los datos desde el archivo Excel ---
datos <- read_excel("simulacion/simulacion_clinicaSanRafael_semana_seed42.xlsx")

# --- Limpieza segura de la columna de tiempos ---
# Reemplazar guiones o valores vacíos por NA
datos$Tiempo_entre_Llegadas_min <- gsub("-", NA, datos$Tiempo_entre_Llegadas_min)

# Convertir a numérico
datos$Tiempo_entre_Llegadas_min <- as.numeric(datos$Tiempo_entre_Llegadas_min)

# Eliminar valores faltantes, ceros o negativos
tiempos <- datos$Tiempo_entre_Llegadas_min
tiempos <- tiempos[!is.na(tiempos) & tiempos > 0]

# Para evitar el warning de "ties", se agrega un pequeño ruido (jitter) sin alterar el análisis
tiempos_jitter <- tiempos + runif(length(tiempos), -1e-6, 1e-6)

# --- Ajuste a la distribución Exponencial ---
lambda <- 1 / mean(tiempos_jitter)
ks_exp <- suppressWarnings(ks.test(tiempos_jitter, "pexp", rate = lambda))

# --- Ajuste a la distribución Gamma ---
ajuste_gamma <- suppressWarnings(fitdistr(tiempos_jitter, "gamma"))
ks_gamma <- suppressWarnings(
  ks.test(
    tiempos_jitter,
    "pgamma",
    shape = ajuste_gamma$estimate["shape"],
    rate  = ajuste_gamma$estimate["rate"]
  )
)

# --- Mostrar resultados comparativos ---
cat("\n--- Prueba Kolmogorov–Smirnov (Exponencial) ---\n")

--- Prueba Kolmogorov–Smirnov (Exponencial) ---
print(ks_exp)

Results of Hypothesis Test
--------------------------

Alternative Hypothesis:          two-sided

Test Name:                       Asymptotic one-sample Kolmogorov-Smirnov test

Data:                            tiempos_jitter

Test Statistic:                  D = 0.02483314

P-value:                         0.9629096
cat("\n--- Prueba Kolmogorov–Smirnov (Gamma) ---\n")

--- Prueba Kolmogorov–Smirnov (Gamma) ---
print(ks_gamma)

Results of Hypothesis Test
--------------------------

Alternative Hypothesis:          two-sided

Test Name:                       Asymptotic one-sample Kolmogorov-Smirnov test

Data:                            tiempos_jitter

Test Statistic:                  D = 0.02273362

P-value:                         0.9842914
cat("\nParámetros estimados (Gamma):\n")

Parámetros estimados (Gamma):
print(ajuste_gamma)
$estimate
    shape      rate 
0.9854105 0.1239300 

$sd
      shape        rate 
0.060666310 0.009818564 

$vcov
             shape         rate
shape 0.0036804011 0.0004628394
rate  0.0004628394 0.0000964042

$loglik
[1] -1253.895

$n
[1] 408

attr(,"class")
[1] "fitdistr"

1.10.1 Interpretación

  • Como el valor p de la prueba K-S para la distribución Gamma es bastante similar al obtenido con la Exponencial, se concluye que el modelo exponencial describe adecuadamente los tiempos entre llegadas.
  • Si el modelo Gamma muestra un p-value significativamente mayor, se considera un mejor ajuste, indicando una estructura más compleja en la llegada de pacientes.
# ANÁLISIS DE LA DISTRIBUCIÓN GAMMA - CLÍNICA SAN RAFAEL


# --- Cargar librerías necesarias ---
library(readxl)
library(MASS)
library(ggplot2)

# --- Leer los datos desde el archivo Excel ---
datos <- read_excel("simulacion/simulacion_clinicaSanRafael_semana_seed42.xlsx")

# --- Limpieza robusta de la columna de tiempos ---
# Reemplazar guiones o valores vacíos por NA
datos$Tiempo_entre_Llegadas_min <- gsub("-", NA, datos$Tiempo_entre_Llegadas_min)

# Convertir a numérico (después de limpiar)
datos$Tiempo_entre_Llegadas_min <- as.numeric(datos$Tiempo_entre_Llegadas_min)

# Eliminar valores faltantes, ceros o negativos
tiempos <- datos$Tiempo_entre_Llegadas_min
tiempos <- tiempos[!is.na(tiempos) & tiempos > 0]

# Para evitar advertencias de valores repetidos (ties), agregamos un pequeño ruido sin alterar resultados
tiempos_jitter <- tiempos + runif(length(tiempos), -1e-6, 1e-6)

# --- Estimación de parámetros de la distribución Gamma ---
# fitdistr (MASS) estima los parámetros de forma (shape) y tasa (rate)
ajuste_gamma <- suppressWarnings(fitdistr(tiempos_jitter, "gamma"))

# Mostrar parámetros estimados
cat("\n--- Parámetros estimados (Distribución Gamma) ---\n")

--- Parámetros estimados (Distribución Gamma) ---
print(ajuste_gamma)
$estimate
    shape      rate 
0.9854105 0.1239300 

$sd
      shape        rate 
0.060666308 0.009818564 

$vcov
             shape         rate
shape 0.0036804009 0.0004628393
rate  0.0004628393 0.0000964042

$loglik
[1] -1253.895

$n
[1] 408

attr(,"class")
[1] "fitdistr"
shape_est <- ajuste_gamma$estimate["shape"]
rate_est  <- ajuste_gamma$estimate["rate"]

# --- Prueba de bondad de ajuste Kolmogorov–Smirnov ---
ks_gamma <- suppressWarnings(
  ks.test(
    tiempos_jitter,
    "pgamma",
    shape = shape_est,
    rate  = rate_est
  )
)

cat("\n--- Prueba Kolmogorov–Smirnov (Gamma) ---\n")

--- Prueba Kolmogorov–Smirnov (Gamma) ---
print(ks_gamma)

Results of Hypothesis Test
--------------------------

Alternative Hypothesis:          two-sided

Test Name:                       Asymptotic one-sample Kolmogorov-Smirnov test

Data:                            tiempos_jitter

Test Statistic:                  D = 0.0227336

P-value:                         0.9842916
# --- Visualización del ajuste --- 
ggplot(data.frame(tiempos), aes(x = tiempos)) +
  geom_histogram(aes(y = ..density..), bins = 25, fill = "#74add1", color = "white") +
  stat_function(fun = dgamma, args = list(shape = shape_est, rate = rate_est),
                color = "#023858", linewidth = 1.2) +
  labs(
    title = "Ajuste de la Distribución Gamma a los Tiempos entre Llegadas",
    x = "Tiempo entre llegadas (minutos)",
    y = "Densidad"
  ) +
  theme_minimal(base_size = 13)

# --- PREGUNTA 1: Probabilidad de que el tiempo entre llegadas sea menor a 10 minutos ---
prob_menor_10 <- pgamma(10, shape = shape_est, rate = rate_est)

cat("\n--- Cálculo de Probabilidad ---\n")

--- Cálculo de Probabilidad ---
cat("P(X < 10) =", round(prob_menor_10, 4), "\n")
P(X < 10) = 0.7159 
# --- PREGUNTA 2: Probabilidad de que lleguen al menos 3 pacientes en 30 minutos ---
shape_eventos <- 3     # número de pacientes que se espera
tiempo_limite <- 30    # minutos

prob_3_en_30 <- pgamma(tiempo_limite, shape = shape_eventos, rate = rate_est)

cat("\n--- Tercera Pregunta: Llegadas acumuladas ---\n")

--- Tercera Pregunta: Llegadas acumuladas ---
cat("P(Tiempo hasta 3 llegadas < 30 min) =", round(prob_3_en_30, 4), "\n")
P(Tiempo hasta 3 llegadas < 30 min) = 0.7176 
# --- INTERPRETACIÓN FINAL ---
cat("\n--- INTERPRETACIÓN DEL AJUSTE ---\n")

--- INTERPRETACIÓN DEL AJUSTE ---
cat("El valor p obtenido en la prueba de Kolmogorov–Smirnov fue de", round(ks_gamma$p.value, 4), "\n")
El valor p obtenido en la prueba de Kolmogorov–Smirnov fue de 0.9843 
cat("Esto indica que no se rechaza la hipótesis nula de que los datos provienen de una distribución Gamma.\n")
Esto indica que no se rechaza la hipótesis nula de que los datos provienen de una distribución Gamma.
cat("La distribución Gamma describe adecuadamente los tiempos entre llegadas, con mayor flexibilidad que la exponencial.\n")
La distribución Gamma describe adecuadamente los tiempos entre llegadas, con mayor flexibilidad que la exponencial.
cat("Por tanto, el modelo Gamma representa una descripción más general y realista del proceso de llegadas de pacientes en la Clínica San Rafael.\n")
Por tanto, el modelo Gamma representa una descripción más general y realista del proceso de llegadas de pacientes en la Clínica San Rafael.

1.11 PREGUNTAS

1.11.1 1. ¿Cuál es la probabilidad de que la próxima llegada ocurra antes de 10 minutos?

p_menor_10 <- pexp(10, rate = lambda)
cat("P(X < 10) =", round(p_menor_10, 4), "\n")
P(X < 10) = 0.7157 

1.11.2 2. ¿Cuál es la probabilidad de que la próxima llegada ocurra entre 15 y 30 minutos?

p_15_30 <- pexp(30, rate = lambda) - pexp(15, rate = lambda)
cat("P(15 ≤ X < 30) =", round(p_15_30, 4), "\n")
P(15 ≤ X < 30) = 0.1286 

1.11.3 3. ¿Cuál es la probabilidad de que transcurran más de 40 minutos sin llegadas?

p_mayor_40 <- 1 - pexp(40, rate = lambda)
cat("P(X > 40) =", round(p_mayor_40, 4), "\n")
P(X > 40) = 0.0065 

1.11.4 4. ¿Qué tiempo garantiza el 90% de probabilidad de llegada?

tiempo_90 <- qexp(0.90, rate = lambda)
cat("Tiempo que garantiza el 90% de probabilidad =", round(tiempo_90, 2), "minutos\n")
Tiempo que garantiza el 90% de probabilidad = 18.31 minutos

1.11.5 5. ¿Cuál es la mediana del tiempo entre llegadas?

tiempo_50 <- qexp(0.5, rate = lambda)
cat("Mediana (50%) =", round(tiempo_50, 2), "minutos\n")
Mediana (50%) = 5.51 minutos

[ X () ]

2 PYTHON.

library(reticulate)

# Instalar los paquetes de Python necesarios
py_install(c("pandas", "numpy", "matplotlib", "seaborn", "scipy", "statsmodels"), pip = TRUE)
Using virtual environment "C:/Users/Admin/Documents/.virtualenvs/r-reticulate" ...
+ "C:/Users/Admin/Documents/.virtualenvs/r-reticulate/Scripts/python.exe" -m pip install --upgrade --no-user pandas numpy matplotlib seaborn scipy statsmodels
# Verificar la instalación
py_list_packages()
           package     version                  requirement
1        contourpy       1.3.3             contourpy==1.3.3
2           cycler      0.12.1               cycler==0.12.1
3       et_xmlfile       2.0.0            et_xmlfile==2.0.0
4        fonttools      4.61.0            fonttools==4.61.0
5       kiwisolver       1.4.9            kiwisolver==1.4.9
6       matplotlib      3.10.7           matplotlib==3.10.7
7            numpy       2.3.5                 numpy==2.3.5
8         openpyxl       3.1.5              openpyxl==3.1.5
9        packaging        25.0              packaging==25.0
10          pandas       2.3.3                pandas==2.3.3
11           patsy       1.0.2                 patsy==1.0.2
12          pillow      12.0.0               pillow==12.0.0
13       pyparsing       3.2.5             pyparsing==3.2.5
14 python-dateutil 2.9.0.post0 python-dateutil==2.9.0.post0
15            pytz      2025.2                 pytz==2025.2
16           scipy      1.16.3                scipy==1.16.3
17         seaborn      0.13.2              seaborn==0.13.2
18             six      1.17.0                  six==1.17.0
19     statsmodels      0.14.5          statsmodels==0.14.5
20          tzdata      2025.2               tzdata==2025.2
21           wheel      0.45.1                wheel==0.45.1
# Limpiar y reinstalar todo
library(reticulate)

# Forzar reinstalación de todas las librerías
py_install("pandas", force = TRUE)
Using virtual environment "C:/Users/Admin/Documents/.virtualenvs/r-reticulate" ...
+ "C:/Users/Admin/Documents/.virtualenvs/r-reticulate/Scripts/python.exe" -m pip install --upgrade --no-user pandas
py_install("numpy", force = TRUE)
Using virtual environment "C:/Users/Admin/Documents/.virtualenvs/r-reticulate" ...
+ "C:/Users/Admin/Documents/.virtualenvs/r-reticulate/Scripts/python.exe" -m pip install --upgrade --no-user numpy
py_install("matplotlib", force = TRUE)
Using virtual environment "C:/Users/Admin/Documents/.virtualenvs/r-reticulate" ...
+ "C:/Users/Admin/Documents/.virtualenvs/r-reticulate/Scripts/python.exe" -m pip install --upgrade --no-user matplotlib
py_install("seaborn", force = TRUE)
Using virtual environment "C:/Users/Admin/Documents/.virtualenvs/r-reticulate" ...
+ "C:/Users/Admin/Documents/.virtualenvs/r-reticulate/Scripts/python.exe" -m pip install --upgrade --no-user seaborn
py_install("scipy", force = TRUE)
Using virtual environment "C:/Users/Admin/Documents/.virtualenvs/r-reticulate" ...
+ "C:/Users/Admin/Documents/.virtualenvs/r-reticulate/Scripts/python.exe" -m pip install --upgrade --no-user scipy
py_install("statsmodels", force = TRUE)
Using virtual environment "C:/Users/Admin/Documents/.virtualenvs/r-reticulate" ...
+ "C:/Users/Admin/Documents/.virtualenvs/r-reticulate/Scripts/python.exe" -m pip install --upgrade --no-user statsmodels
# Verificar instalación
cat("Verificando instalación...\n")
Verificando instalación...
tryCatch({
  py_run_string("import pandas as pd; print('✓ pandas instalado')")
}, error = function(e) cat("Error con pandas:", e$message, "\n"))
✓ pandas instalado
tryCatch({
  py_run_string("import numpy as np; print('✓ numpy instalado')")
}, error = function(e) cat("Error con numpy:", e$message, "\n"))
✓ numpy instalado
tryCatch({
  py_run_string("import matplotlib.pyplot as plt; print('✓ matplotlib instalado')")
}, error = function(e) cat("Error con matplotlib:", e$message, "\n"))
✓ matplotlib instalado
cat("Instalación completada.\n")
Instalación completada.
py_install("openpyxl")
Using virtual environment "C:/Users/Admin/Documents/.virtualenvs/r-reticulate" ...
+ "C:/Users/Admin/Documents/.virtualenvs/r-reticulate/Scripts/python.exe" -m pip install --upgrade --no-user openpyxl



# Cargar librerías necesarias
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
from scipy.stats import expon, poisson, kstest, anderson, shapiro
import warnings
warnings.filterwarnings('ignore')

# Configurar estilo de gráficos
plt.style.use('default')
sns.set_palette("husl")
# Importar la base de datos
datos = pd.read_excel("simulacion/simulacion_clinicaSanRafael_semana_seed42.xlsx")

# Vista general
print("Base importada con éxito.\n")
Base importada con éxito.
print("Estructura de los datos:")
Estructura de los datos:
print(datos.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 415 entries, 0 to 414
Data columns (total 5 columns):
 #   Column                     Non-Null Count  Dtype 
---  ------                     --------------  ----- 
 0   Fecha                      415 non-null    object
 1   Hora_Llegada               415 non-null    object
 2   Tiempo_entre_Llegadas_min  415 non-null    object
 3   Numero_Pacientes           415 non-null    int64 
 4   Comentarios                415 non-null    object
dtypes: int64(1), object(4)
memory usage: 16.3+ KB
None
print("\nPrimeras filas:")

Primeras filas:
print(datos.head())
        Fecha  ...                                        Comentarios
0  2024-10-07  ...  Paciente U - Consulta pediátrica (viene acompa...
1  2024-10-07  ...  Paciente D - Consulta por ansiedad o estrés (v...
2  2024-10-07  ...  Paciente S - Control de colesterol (viene acom...
3  2024-10-07  ...  Paciente A - Control de presión arterial (espe...
4  2024-10-07  ...  Paciente A - Consulta por ansiedad o estrés (e...

[5 rows x 5 columns]
# Verificación y limpieza de la base
print("\nVerificación de valores faltantes")

Verificación de valores faltantes
print(datos.isnull().sum())
Fecha                        0
Hora_Llegada                 0
Tiempo_entre_Llegadas_min    0
Numero_Pacientes             0
Comentarios                  0
dtype: int64
# Eliminar filas con tiempos no válidos o "-"
datos = datos[datos['Tiempo_entre_Llegadas_min'] != "-"]
datos['Tiempo_entre_Llegadas_min'] = pd.to_numeric(datos['Tiempo_entre_Llegadas_min'])

# Verificar duplicados
print(f"\nVerificación de duplicados: {datos.duplicated().sum()}")

Verificación de duplicados: 0
# Resumen estadístico de la variable de interés
print("\nResumen descriptivo del tiempo entre llegadas (minutos)")

Resumen descriptivo del tiempo entre llegadas (minutos)
print(datos['Tiempo_entre_Llegadas_min'].describe())
count    408.000000
mean       7.951348
std        7.739289
min        0.040000
25%        2.220000
50%        5.430000
75%       11.425000
max       45.560000
Name: Tiempo_entre_Llegadas_min, dtype: float64
# Análisis exploratorio
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))

# Histograma + densidad
ax1.hist(datos['Tiempo_entre_Llegadas_min'], bins=25, density=True, 
         alpha=0.7, color='lightblue', edgecolor='black')
datos['Tiempo_entre_Llegadas_min'].plot.density(ax=ax1, color='red', linewidth=2)
ax1.set_title('Distribución de los tiempos entre llegadas')
ax1.set_xlabel('Tiempo entre llegadas (minutos)')
ax1.set_ylabel('Densidad')

# Boxplot para detectar outliers
ax2.boxplot(datos['Tiempo_entre_Llegadas_min'], patch_artist=True,
           boxprops=dict(facecolor='orange'))
{'whiskers': [<matplotlib.lines.Line2D object at 0x000001629050EA50>, <matplotlib.lines.Line2D object at 0x000001629050EBA0>], 'caps': [<matplotlib.lines.Line2D object at 0x000001629050ECF0>, <matplotlib.lines.Line2D object at 0x000001629050EE40>], 'boxes': [<matplotlib.patches.PathPatch object at 0x00000162901C38C0>], 'medians': [<matplotlib.lines.Line2D object at 0x000001629050EF90>], 'fliers': [<matplotlib.lines.Line2D object at 0x000001629050F0E0>], 'means': []}
ax2.set_title('Boxplot de los tiempos entre llegadas')
ax2.set_ylabel('Minutos entre llegadas')

plt.tight_layout()
plt.show()

# Gráficos mejorados
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 6))

# Histograma mejorado
counts, bins, patches = ax1.hist(datos['Tiempo_entre_Llegadas_min'], bins=25, 
                                density=True, alpha=0.8, edgecolor='#4B4B4B')

# Añadir degradado de color
from matplotlib.colors import LinearSegmentedColormap
cmap = LinearSegmentedColormap.from_list("blue_gradient", ["#B0E0E6", "#4682B4"])
bin_centers = 0.5 * (bins[:-1] + bins[1:])
col = bin_centers - min(bin_centers)
col /= max(col)
for c, p in zip(col, patches):
    plt.setp(p, 'facecolor', cmap(c))
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
[None]
# Curva de densidad
datos['Tiempo_entre_Llegadas_min'].plot.density(ax=ax1, color='#FF4500', 
                                               linewidth=2, alpha=0.7)
ax1.fill_between(ax1.lines[0].get_xdata(), ax1.lines[0].get_ydata(), 
                alpha=0.2, color='#FF4500')
ax1.set_title('Distribución de los Tiempos entre Llegadas\nHistograma con curva de densidad', 
              fontsize=14, fontweight='bold')
ax1.set_xlabel('Tiempo entre llegadas (minutos)', fontweight='bold')
ax1.set_ylabel('Densidad', fontweight='bold')

# Boxplot mejorado
box_plot = ax2.boxplot(datos['Tiempo_entre_Llegadas_min'], patch_artist=True, 
                      notch=True, widths=0.6)
box_plot['boxes'][0].set_facecolor('#FFA500')
box_plot['boxes'][0].set_edgecolor('#4B4B4B')
box_plot['fliers'][0].set_marker('o')
box_plot['fliers'][0].set_markerfacecolor('red')
box_plot['fliers'][0].set_markeredgecolor('red')

# Jitter plot
y = datos['Tiempo_entre_Llegadas_min']
x = np.random.normal(1, 0.04, size=len(y))
ax2.plot(x, y, 'o', alpha=0.5, color='#FF4500', markersize=4)
ax2.set_title('Distribución de Tiempos entre Llegadas\nBoxplot', 
              fontsize=14, fontweight='bold')
ax2.set_ylabel('Minutos entre llegadas', fontweight='bold')
ax2.set_xticks([])
[]
plt.tight_layout()
plt.show()

# Cálculo de parámetros de la distribución exponencial
media_tiempo = datos['Tiempo_entre_Llegadas_min'].mean()
lambda_ = 1 / media_tiempo

print("\n--- Parámetros estimados ---")

--- Parámetros estimados ---
print(f"Media de tiempos entre llegadas: {media_tiempo:.3f} minutos")
Media de tiempos entre llegadas: 7.951 minutos
print(f"Lambda estimado: {lambda_:.5f}")
Lambda estimado: 0.12576
# Ajuste a la distribución exponencial
# En Python usamos scipy.stats.expon directamente
loc, scale = expon.fit(datos['Tiempo_entre_Llegadas_min'])
print(f"Parámetros del ajuste exponencial:")
Parámetros del ajuste exponencial:
print(f"Loc: {loc:.4f}, Scale: {scale:.4f}")
Loc: 0.0400, Scale: 7.9113
print(f"Lambda (1/scale): {1/scale:.5f}")
Lambda (1/scale): 0.12640
# Pruebas de bondad de ajuste
print("\n--- Prueba Kolmogorov–Smirnov ---")

--- Prueba Kolmogorov–Smirnov ---
ks_stat, ks_pvalue = kstest(datos['Tiempo_entre_Llegadas_min'], 'expon', 
                           args=(0, 1/lambda_))  # args: (loc, scale)
print(f"Estadístico KS: {ks_stat:.4f}, p-valor: {ks_pvalue:.4f}")
Estadístico KS: 0.0248, p-valor: 0.9575
print("\n--- Prueba de Anderson–Darling ---")

--- Prueba de Anderson–Darling ---
anderson_result = anderson(datos['Tiempo_entre_Llegadas_min'], dist='expon')
print(f"Estadístico AD: {anderson_result.statistic:.4f}")
Estadístico AD: 0.3107
print("Valores críticos:", anderson_result.critical_values)
Valores críticos: [0.921 1.076 1.339 1.604 1.954]
print("Niveles de significancia:", anderson_result.significance_level/100)
Niveles de significancia: [0.15  0.1   0.05  0.025 0.01 ]
print("\n--- Prueba de Shapiro–Wilk (para descartar normalidad) ---")

--- Prueba de Shapiro–Wilk (para descartar normalidad) ---
shapiro_stat, shapiro_pvalue = shapiro(datos['Tiempo_entre_Llegadas_min'])
print(f"Estadístico Shapiro-Wilk: {shapiro_stat:.4f}, p-valor: {shapiro_pvalue:.4e}")
Estadístico Shapiro-Wilk: 0.8456, p-valor: 1.4423e-19
# Gráficos comparativos
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))

# Histograma con la densidad teórica superpuesta
ax1.hist(datos['Tiempo_entre_Llegadas_min'], bins=25, density=True, 
         alpha=0.7, color='skyblue', edgecolor='black')
x = np.linspace(0, datos['Tiempo_entre_Llegadas_min'].max(), 100)
ax1.plot(x, expon.pdf(x, scale=1/lambda_), 'red', linewidth=2)
ax1.set_title('Ajuste de la distribución exponencial')
ax1.set_xlabel('Tiempo entre llegadas (minutos)')
ax1.set_ylabel('Densidad')

# Función de distribución acumulada
from statsmodels.distributions.empirical_distribution import ECDF
ecdf = ECDF(datos['Tiempo_entre_Llegadas_min'])
ax2.step(ecdf.x, ecdf.y, where='post', color='blue', label='Empírica')
ax2.plot(x, expon.cdf(x, scale=1/lambda_), 'red', linewidth=2, label='Teórica')
ax2.set_title('Comparación entre F(x) empírica y teórica')
ax2.set_xlabel('Tiempo entre llegadas (minutos)')
ax2.set_ylabel('Probabilidad acumulada')
ax2.legend()

plt.tight_layout()
plt.show()

# Cálculo de probabilidades de interés
# P(X < 10)
p_menor_10 = expon.cdf(10, scale=1/lambda_)
# P(15 ≤ X < 30)
p_15_30 = expon.cdf(30, scale=1/lambda_) - expon.cdf(15, scale=1/lambda_)
# P(X > 40)
p_mayor_40 = 1 - expon.cdf(40, scale=1/lambda_)
# P(5 ≤ X < 15)
p_5_15 = expon.cdf(15, scale=1/lambda_) - expon.cdf(5, scale=1/lambda_)
# P(10 ≤ X < 40)
p_10_40 = expon.cdf(40, scale=1/lambda_) - expon.cdf(10, scale=1/lambda_)
# Tiempo que garantiza el 90% de probabilidad de llegada
tiempo_90 = expon.ppf(0.90, scale=1/lambda_)

print("\n--- Probabilidades calculadas ---")

--- Probabilidades calculadas ---
print(f"P(X < 10)  = {p_menor_10:.4f}")
P(X < 10)  = 0.7157
print(f"P(15 ≤ X < 30) = {p_15_30:.4f}")
P(15 ≤ X < 30) = 0.1286
print(f"P(X > 40)  = {p_mayor_40:.4f}")
P(X > 40)  = 0.0065
print(f"P(5 ≤ X < 15) = {p_5_15:.4f}")
P(5 ≤ X < 15) = 0.3816
print(f"P(10 ≤ X < 40) = {p_10_40:.4f}")
P(10 ≤ X < 40) = 0.2778
print(f"Tiempo con 90% de probabilidad de llegada = {tiempo_90:.2f} minutos")
Tiempo con 90% de probabilidad de llegada = 18.31 minutos
# PREGUNTAS

print("\n--- RESPUESTA A LAS PREGUNTAS ---")

--- RESPUESTA A LAS PREGUNTAS ---
# 1. ¿Cuál es la probabilidad de que la próxima llegada ocurra antes de 10 minutos?
p_menor_10 = expon.cdf(10, scale=1/lambda_)
print(f"1. P(X < 10) = {p_menor_10:.4f}")
1. P(X < 10) = 0.7157
# 2. ¿Cuál es la probabilidad de que la próxima llegada ocurra entre 15 y 30 minutos?
p_15_30 = expon.cdf(30, scale=1/lambda_) - expon.cdf(15, scale=1/lambda_)
print(f"2. P(15 ≤ X < 30) = {p_15_30:.4f}")
2. P(15 ≤ X < 30) = 0.1286
# 3. ¿Cuál es la probabilidad de que transcurran más de 40 minutos sin llegadas?
p_mayor_40 = 1 - expon.cdf(40, scale=1/lambda_)
print(f"3. P(X > 40) = {p_mayor_40:.4f}")
3. P(X > 40) = 0.0065
# 4. ¿Qué tiempo garantiza el 90% de probabilidad de llegada?
tiempo_90 = expon.ppf(0.90, scale=1/lambda_)
print(f"4. Tiempo que garantiza el 90% de probabilidad = {tiempo_90:.2f} minutos")
4. Tiempo que garantiza el 90% de probabilidad = 18.31 minutos
# 5. ¿Cuál es la distribución del número de llegadas en 60 minutos?
# ¿Cuál es la probabilidad de que haya al menos 5 llegadas en ese período?
T = 60  # minutos
mu = lambda_ * T
k = 5

p_al_menos_5 = 1 - poisson.cdf(k-1, mu)
print(f"5. E[N(60)] = {mu:.2f}")
5. E[N(60)] = 7.55
print(f"   P(N(60) ≥ 5) = {p_al_menos_5:.4f}")
   P(N(60) ≥ 5) = 0.8712
# 6. ¿Cuál es la mediana del tiempo entre llegadas?
tiempo_50 = expon.ppf(0.5, scale=1/lambda_)
print(f"6. Mediana (50%) = {tiempo_50:.2f} minutos")
6. Mediana (50%) = 5.51 minutos
# 7. ¿Cuál es la probabilidad de que entre 0 y 2 llegadas ocurran en 30 minutos?
T2 = 30  # minutos
mu2 = lambda_ * T2
p_0_2 = poisson.cdf(2, mu2)  # acumulada hasta 2
print(f"7. P(0 ≤ N(30) ≤ 2) = {p_0_2:.4f}")
7. P(0 ≤ N(30) ≤ 2) = 0.2733