Página 1. Datos reales y diagnóstico de gestión

Objetivo y referente metodológico (punto 1). Identificar patrones de asistencia y espera para orientar la gestión de agendas y recursos. Martínez Bautista y Delgadillo Hurtado (2024), en Explorando medicaldata en R, examinan una base clínica mediante inspección de variables (names, head), faltantes (is.na), resúmenes (summary) y gráficos de distribución y asociación (ggplot2). Aquí replicamos esa secuencia analítica, pero con las tres bases entregadas por el profesor, no con la base de diabetes. A diferencia de la exclusión global de casos incompletos mostrada en el artículo, empleamos datos disponibles por indicador para evitar pérdidas innecesarias.

111.488 citas 36.697 pacientes 104.360 franjas de agenda

Integración y calidad. appointment_id identifica citas; patient_id y slot_id relacionan seguro y cupos. Se detectaron 25.456 esperas y 25.456 duraciones sin registro; corresponden principalmente a citas no atendidas, por lo que no equivalen automáticamente a errores de captura. No se usan nombres ni datos de identificación personal. Registros del 01/01/2015 al 24/12/2024.

# table() cuenta casos; prop.table() calcula probabilidades empíricas.
tabla_estado <- table(citas$status)
prob_estado <- prop.table(tabla_estado)
knitr::kable(data.frame(Estado=names(tabla_estado),
  Citas=as.integer(tabla_estado), Probabilidad=round(as.numeric(prob_estado),3)),
  caption="Estados registrados y proporción empírica")
Estados registrados y proporción empírica
Estado Citas Probabilidad
attended 86032 0.772
cancelled 18254 0.164
did not attend 6615 0.059
scheduled 141 0.001
unknown 446 0.004

Interpretación. 77,2% de las citas figuran como attended y 5,9% como did not attend; otras están canceladas o pendientes. Estos porcentajes describen registros, no tasas ajustadas por complejidad de pacientes ni por diferencias de periodos. Del inventario de franjas, 10,7% registra disponibilidad: se interpreta como el estado consignado y no como capacidad futura garantizada.

Lectura gerencial. Un tablero debe separar atención, cancelación y no asistencia; las franjas disponibles son una fotografía de la base y no garantizan cupos futuros. Esto permite focalizar recordatorios y revisar capacidad sin confundir categorías.

Página 2. Selección aleatoria y fenómenos discretos

Experimento aleatorio (puntos 2–4). Espacio muestral: \(\Omega=\{\text{asistió, canceló, no asistió}\}\). Evento \(A=\{\text{no asistió}\}\). Mediante sample() se generan 500 citas ficticias de los tres estados concluidos, con probabilidades relativas observadas. table() y prop.table() verifican la frecuencia de cada resultado. Estas 500 citas no se agregan a los datos reales.

# sample(): selección aleatoria. table(): frecuencias y probabilidades simuladas.
clases <- c("attended", "cancelled", "did not attend")
prob_clases <- as.numeric(prop.table(table(factor(
  citas$status[citas$status %in% clases], levels=clases))))
seleccion <- sample(clases, size=500, replace=TRUE, prob=prob_clases)
frec_sim <- table(factor(seleccion, levels=clases))
knitr::kable(data.frame(Eventos=clases,
  Frecuencia=as.integer(frec_sim), Probabilidad=round(as.numeric(frec_sim)/500,3)),
  caption="500 citas simuladas mediante sample() y table()")
500 citas simuladas mediante sample() y table()
Eventos Frecuencia Probabilidad
attended 401 0.802
cancelled 77 0.154
did not attend 22 0.044
# runif() genera umbrales uniformes para un evento binario hipotético.
u <- runif(1000, min=0, max=1)
p_no_asiste <- mean(citas$status == "did not attend")
no_asiste_uniforme <- as.integer(u < p_no_asiste)
# rbinom(): número de no asistencias entre 20 citas por día hipotético.
no_asistencias <- rbinom(1000, size=20, prob=p_no_asiste)
# rpois(): demanda diaria ilustrativa; dpois(): probabilidad puntual teórica.
citas_por_dia <- table(citas$appointment_date)
lambda <- mean(as.numeric(citas_por_dia)) # Días con al menos una cita registrada.
demanda <- rpois(1000, lambda=lambda)
k <- round(lambda)
prob_puntual <- dpois(k, lambda=lambda)

Resultados e interpretación. El evento de no asistencia tiene probabilidad empírica 5,9%; entre 1.000 umbrales uniformes, 54 activaron ese evento. rbinom() produce en promedio 1.21 inasistencias por bloque hipotético de 20 citas. Si la demanda se modela como Poisson, con parámetro \(\lambda=42.81\) citas por día con actividad registrada, dpois(43, lambda) = 0.0607; dpois() no genera valores aleatorios, sino que calcula la probabilidad de exactamente ese número de citas. rpois() es la función que simula conteos.

Interpretación gerencial. Si las ausencias esperadas aumentan, es razonable ensayar recordatorios, pero no sobreagendar automáticamente. runif() modela incertidumbre individual; rbinom() requiere ensayos con probabilidad estable, y Poisson supone tasa constante e independencia: esas hipótesis deben validarse con las series reales.

Página 3. Variables continuas y estadística descriptiva

Se examinan waiting_time (espera) y appointment_duration (duración) en minutos, presentes solo en citas atendidas. En lugar de eliminar todos los registros incompletos, se usan los casos con valor disponible para cada indicador.

# Estadísticos base obligatorios aplicados al tiempo de espera observado.
indicadores <- c(Media=mean(real_espera), Mediana=median(real_espera),
  Desviacion=sd(real_espera), Varianza=var(real_espera),
  Minimo=range(real_espera)[1], Maximo=range(real_espera)[2])
knitr::kable(data.frame(Estadistico=names(indicadores),
  Valor=round(as.numeric(indicadores),2)),
  caption="Tiempo de espera en citas atendidas (minutos; varianza en min²)")
Tiempo de espera en citas atendidas (minutos; varianza en min²)
Estadistico Valor
Media 44.09
Mediana 33.50
Desviacion 40.79
Varianza 1663.73
Minimo 0.60
Maximo 297.30
# summary() ofrece mínimo, cuartiles, media, máximo y valores ausentes.
summary(citas[,c("waiting_time","appointment_duration","scheduling_interval")])
##   waiting_time    appointment_duration scheduling_interval
##  Min.   :  0.60   Min.   : 0.00        Min.   : 1.00      
##  1st Qu.: 12.60   1st Qu.: 8.60        1st Qu.: 2.00      
##  Median : 33.50   Median :15.80        Median : 5.00      
##  Mean   : 44.09   Mean   :17.48        Mean   : 7.19      
##  3rd Qu.: 64.60   3rd Qu.:24.70        3rd Qu.:10.00      
##  Max.   :297.30   Max.   :58.70        Max.   :30.00      
##  NAs    :25456    NAs    :25456
# Comparación de 3 modelos para 1000 esperas hipotéticas cada uno.
n <- 1000
sim_normal <- rnorm(n, mean=media_espera, sd=sd_espera)
sim_exponencial <- rexp(n, rate=1/media_espera)
sim_uniforme <- runif(n, min=0, max=2*media_espera)
# Densidades y probabilidades acumuladas en el umbral de 30 minutos.
dens_norm_30 <- dnorm(30, mean=media_espera, sd=sd_espera)
prob_norm_30 <- pnorm(30, mean=media_espera, sd=sd_espera)
dens_exp_30 <- dexp(30, rate=1/media_espera)
prob_exp_30 <- pexp(30, rate=1/media_espera)
resumen_modelos <- data.frame(Modelo=c("Normal", "Exponencial", "Uniforme"),
  Espera_media_simulada=round(c(mean(sim_normal),mean(sim_exponencial),mean(sim_uniforme)),1))
knitr::kable(resumen_modelos, caption="Promedios simulados (minutos)")
Promedios simulados (minutos)
Modelo Espera_media_simulada
Normal 41.5
Exponencial 43.1
Uniforme 44.3

Interpretación. La espera observada promedia 44.1 minutos (mediana 33.5, desviación estándar 40.8). Bajo un modelo normal, pnorm(30) estima 36,5% de esperas hasta 30 minutos; bajo exponencial, pexp(30) estima 49,4%. Las densidades dnorm(30) = 0.00921 y dexp(30) = 0.0115 no son probabilidades puntuales. La normal puede simular esperas negativas, lo que evidencia su limitación para esta variable.

Decisión gerencial. Para establecer metas de oportunidad conviene medir mediana y percentil 90 por periodo y servicio; los modelos probabilísticos son escenarios, no predicciones hasta validar sus supuestos.

Página 4. Contrastes e implicaciones estratégicas

Se adopta un nivel de significancia de 5%. Las pruebas describen asociaciones, no causalidad: existen citas repetidas por paciente y posibles diferencias de año, agenda, edad o aseguradora.

# 8. t.test(): medias de espera entre mujeres y hombres atendidos.
prueba_t <- t.test(waiting_time ~ sex, data=atendidas)
# 9. aov(): medias de espera por tres grupos de edad.
anova <- aov(waiting_time ~ edad_tramo, data=atendidas)
p_anova <- summary(anova)[[1]][["Pr(>F)"]][1]
# 10. chisq.test(): aseguradora vs no asistencia, cuatro aseguradoras más frecuentes.
seguimiento <- subset(citas, status %in% c("attended", "did not attend") & !is.na(insurance))
aseguradoras <- names(sort(table(seguimiento$insurance), decreasing=TRUE))[1:4]
tabla_seg <- table(seguimiento$insurance[seguimiento$insurance %in% aseguradoras],
                   seguimiento$status[seguimiento$insurance %in% aseguradoras])
prueba_chi <- chisq.test(tabla_seg)
# 11. prop.test(): asistencia entre mujeres y hombres (excluidas cancelaciones).
tabla_sexo <- table(factor(asistencia$sex, levels=c("Female","Male")),
                    factor(asistencia$status, levels=c("attended","did not attend")))
prueba_prop <- prop.test(x=tabla_sexo[,"attended"], n=rowSums(tabla_sexo))
resultados <- data.frame(
  Prueba=c("t: espera por sexo", "ANOVA: espera por edad",
           "Chi cuadrado: seguro y asistencia", "Proporciones: asistencia por sexo"),
  Valor_p=c(pval(prueba_t$p.value),pval(p_anova),pval(prueba_chi$p.value),
            pval(prueba_prop$p.value)))
knitr::kable(resultados, caption="Pruebas con nivel de referencia α = 0,05")
Pruebas con nivel de referencia α = 0,05
Prueba Valor_p
t: espera por sexo 0,992
ANOVA: espera por edad 0,535
Chi cuadrado: seguro y asistencia 0,922
Proporciones: asistencia por sexo 0,496
# 12. DOS figuras ggpubr: dispersión de espera y tasas de asistencia por sexo.
# El muestreo para visualización evita una figura sobrecargada; las pruebas usan
# TODOS los registros elegibles (no solo esta submuestra).
set.seed(2026)
vis <- atendidas[sample(nrow(atendidas), min(2400, nrow(atendidas))), ]
vis$edad_tramo <- droplevels(vis$edad_tramo)
# Gráfico 1: cajas de espera por grupo de edad.
g_cajas <- ggpubr::ggboxplot(vis, x="edad_tramo", y="waiting_time",
  color="edad_tramo", outlier.shape=NA) +
  ggplot2::coord_cartesian(ylim=c(0, unname(quantile(vis$waiting_time, .90)))) +
  ggplot2::labs(title="Espera por edad", x="Edad", y="Espera (min)") +
  ggplot2::theme_minimal(base_size=8) + ggplot2::theme(legend.position="none")
# Gráfico 2: barras de porcentaje de asistencia entre citas concluidas.
tasas_sexo <- data.frame(
  Sexo=c("Mujeres", "Hombres"),
  Asistencia=as.numeric(tabla_sexo[, "attended"] / rowSums(tabla_sexo)) * 100)
g_barras <- ggpubr::ggbarplot(tasas_sexo, x="Sexo", y="Asistencia", fill="Sexo") +
  ggplot2::labs(title="Asistencia por sexo", x="", y="Porcentaje (%)") +
  ggplot2::coord_cartesian(ylim=c(0,100)) +
  ggplot2::theme_minimal(base_size=8) + ggplot2::theme(legend.position="none")
# Dos comparaciones reproducibles con el paquete ggpubr.
ggpubr::ggarrange(g_cajas, g_barras, ncol=2, nrow=1)

Interpretación de las pruebas y gráficos. Las cajas muestran dispersión y posibles diferencias de espera entre edades; las barras muestran las tasas brutas de asistencia por sexo. La prueba t produce p 0,992 e intervalo de confianza del 95% para la diferencia de medias de [-0.56, 0.55] minutos. El ANOVA por edades obtiene p 0,535; si es menor a 0,05, al menos dos medias difieren, sin indicar cuáles (haría falta una prueba poshoc). Chi-cuadrado arroja p 0,922 para la asociación entre aseguradora y asistencia entre las cuatro más frecuentes. prop.test() obtiene p 0,496 en la comparación de proporciones de asistencia: mujeres 92,9% y hombres 92,8%. Conclusión inferencial: en los cuatro contrastes no hay evidencia estadística suficiente de diferencias o asociaciones al 5 %. Un valor p alto no demuestra igualdad entre grupos. La significación estadística tampoco implica relevancia operativa: deben revisarse tamaños de efecto, periodos y posibles factores de confusión.

Recomendaciones. (1) Monitorear mediana y percentil 90 de espera; (2) pilotear recordatorios y medir resultados; (3) examinar brechas de acceso ajustando por factores de confusión; (4) validar escenarios de demanda antes de modificar la oferta. Limitaciones: no se ajusta por citas repetidas, estacionalidad ni diferencias de complejidad.

Fuentes: bases del docente appointments.csv, patients.csv y slots.csv, incorporadas en el ZIP entregado; Martínez Bautista, H. y Delgadillo Hurtado, M. L. (2024), Explorando medicaldata en R (referencia metodológica, no fuente de datos de este ejercicio). Resultados de simulación fijados con set.seed(2026). Los gráficos de simulaciones en HTML permiten interacción al pasar el cursor; los dos comparativos de ggpubr son figuras estáticas que facilitan la impresión. El botón Code despliega las instrucciones R comentadas.