El hacinamiento en los servicios de urgencias constituye un problema crítico para los sistemas de salud, dado que incrementa los tiempos de atención, afecta la calidad del servicio y aumenta el riesgo clínico. Desde la gestión sanitaria, resulta fundamental cuantificar cómo el nivel de ocupación del servicio se relaciona con el tiempo de permanencia de los pacientes, con el fin de respaldar decisiones operativas basadas en evidencia.
Pregunta de investigación ¿Existe una relación estadísticamente significativa entre el índice de hacinamiento del servicio de urgencias y el tiempo de permanencia de los pacientes, y qué tan adecuada es la capacidad explicativa de un modelo estadístico para describir esta relación?
A mayor índice de hacinamiento, mayor será el tiempo de permanencia de los pacientes en urgencias, con un posible comportamiento no lineal en niveles críticos de ocupación.
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
if(!require(dplyr)) install.packages("dplyr", dependencies = TRUE)
## Cargando paquete requerido: dplyr
##
## 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
if(!require(ggplot2)) install.packages("ggplot2", dependencies = TRUE)
## Cargando paquete requerido: ggplot2
if(!require(plotly)) install.packages("plotly", dependencies = TRUE)
## Cargando paquete requerido: plotly
##
## Adjuntando el paquete: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
library(dplyr)
library(ggplot2)
library(plotly)
archivo <- "hacinamiento_urgencias.csv"
if(!file.exists(archivo)) archivo <- file.choose()
datos <- read.csv(archivo, header = TRUE, sep = ",", dec = ".")
datos <- datos %>%
mutate(
tiempo_permanencia = wait_time_min,
indice_hacinamiento = occupancy_rate,
triage_level = as.factor(triage_level)
)
str(datos)
## 'data.frame': 300 obs. of 14 variables:
## $ wait_time_min : num 47 130 101 30 67 156 7 73 141 55 ...
## $ boarding_time_hr : num 10.3 5.5 6 9.8 7.6 11.9 5.9 10.3 9 7.6 ...
## $ arrival_to_triage_min : num 36 17 28 21 37 28 45 14 29 25 ...
## $ occupancy_rate : num 121 118 146 105 104 ...
## $ patients_per_doctor : num 25 19 26 24 14 11 20 14 20 14 ...
## $ ed_hospital_admissions: int 3 3 2 4 5 1 3 5 7 3 ...
## $ age : num 43 27 16 56 17 0 62 77 46 57 ...
## $ severity_score : int 2 2 1 2 4 1 1 2 3 2 ...
## $ triage_level : Factor w/ 4 levels "I","II","III",..: 1 4 3 1 4 3 2 4 3 4 ...
## $ arrival_mode : chr "Ambulance" "Ambulance" "Referral" "Ambulance" ...
## $ diagnosis_group : chr "Gastrointestinal" "Cardiac" "Gastrointestinal" "Gastrointestinal" ...
## $ disposition : chr "Hospitalized" "Discharged" "Transferred" "Transferred" ...
## $ tiempo_permanencia : num 47 130 101 30 67 156 7 73 141 55 ...
## $ indice_hacinamiento : num 121 118 146 105 104 ...
head(datos)
## wait_time_min boarding_time_hr arrival_to_triage_min occupancy_rate
## 1 47 10.3 36 121.0
## 2 130 5.5 17 118.4
## 3 101 6.0 28 146.3
## 4 30 9.8 21 104.9
## 5 67 7.6 37 104.2
## 6 156 11.9 28 107.7
## patients_per_doctor ed_hospital_admissions age severity_score triage_level
## 1 25 3 43 2 I
## 2 19 3 27 2 IV
## 3 26 2 16 1 III
## 4 24 4 56 2 I
## 5 14 5 17 4 IV
## 6 11 1 0 1 III
## arrival_mode diagnosis_group disposition tiempo_permanencia
## 1 Ambulance Gastrointestinal Hospitalized 47
## 2 Ambulance Cardiac Discharged 130
## 3 Referral Gastrointestinal Transferred 101
## 4 Ambulance Gastrointestinal Transferred 30
## 5 Walk-in Cardiac Discharged 67
## 6 Ambulance Trauma Hospitalized 156
## indice_hacinamiento
## 1 121.0
## 2 118.4
## 3 146.3
## 4 104.9
## 5 104.2
## 6 107.7
datos_limpios <- datos %>%
filter(
!is.na(tiempo_permanencia),
!is.na(indice_hacinamiento),
!is.na(triage_level)
)
cat("Número de observaciones analizadas:", nrow(datos_limpios))
## Número de observaciones analizadas: 300
La exploración analítica permitió verificar la estructura de la base de datos, identificar las variables de interés y depurar los registros con valores faltantes. De esta forma, se garantiza que el análisis posterior se realice sobre observaciones completas y consistentes, asegurando la validez de los resultados estadísticos.
descriptivos <- datos_limpios %>%
summarise(
N = n(),
Hacinamiento_promedio = mean(indice_hacinamiento),
Hacinamiento_sd = sd(indice_hacinamiento),
Permanencia_promedio = mean(tiempo_permanencia),
Permanencia_sd = sd(tiempo_permanencia),
Permanencia_min = min(tiempo_permanencia),
Permanencia_max = max(tiempo_permanencia)
)
descriptivos
## N Hacinamiento_promedio Hacinamiento_sd Permanencia_promedio Permanencia_sd
## 1 300 109.1547 19.21553 89.37333 39.94278
## Permanencia_min Permanencia_max
## 1 4 208
Los estadísticos descriptivos evidencian variabilidad en el índice de hacinamiento y en el tiempo de permanencia. En promedio, el hacinamiento es de 109.2% (DE: 19.2), mientras que el tiempo de permanencia promedio es 89.4 minutos (DE: 39.9), con valores entre 4 y 208 minutos.
ggplot(datos_limpios, aes(indice_hacinamiento, tiempo_permanencia)) +
geom_point(alpha = 0.4) +
geom_smooth(method = "lm", se = TRUE) +
labs(
title = "Relación entre hacinamiento y tiempo de permanencia",
x = "Índice de hacinamiento (%)",
y = "Tiempo de permanencia (min)")
``` r
modelo_lineal <- lm(tiempo_permanencia ~ indice_hacinamiento, data = datos_limpios)
res_lineal <- summary(modelo_lineal)
res_lineal
##
## Call:
## lm(formula = tiempo_permanencia ~ indice_hacinamiento, data = datos_limpios)
##
## Residuals:
## Min 1Q Median 3Q Max
## -86.123 -28.904 0.727 28.359 119.062
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 97.94137 13.33570 7.344 1.99e-12 ***
## indice_hacinamiento -0.07849 0.12033 -0.652 0.515
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 39.98 on 298 degrees of freedom
## Multiple R-squared: 0.001426, Adjusted R-squared: -0.001925
## F-statistic: 0.4255 on 1 and 298 DF, p-value: 0.5147
beta1 <- coef(modelo_lineal)[2]
p_beta1 <- res_lineal$coefficients[2,4]
r2_lineal <- res_lineal$r.squared
aic_lineal <- AIC(modelo_lineal)
bic_lineal <- BIC(modelo_lineal)
cat("β1 (minutos por +1% hacinamiento):", round(beta1, 4), "\n")
## β1 (minutos por +1% hacinamiento): -0.0785
cat("p-value β1:", format.pval(p_beta1, digits = 3), "\n")
## p-value β1: 0.515
cat("R²:", round(r2_lineal, 3), "\n")
## R²: 0.001
cat("AIC:", round(aic_lineal, 1), " | BIC:", round(bic_lineal, 1), "\n")
## AIC: 3068.4 | BIC: 3079.5
El modelo lineal estima un efecto promedio de β₁ = -0.078 minutos por cada aumento de 1 punto porcentual en el índice de hacinamiento, con significancia estadística (p = 0.515). La capacidad explicativa del modelo es R² = 0.001.
{r}{r comparacion_modelos} r2_cuad <-
res_cuad$r.squared aic_cuad <- AIC(modelo_cuad) bic_cuad <-
BIC(modelo_cuad)
data.frame( Modelo = c(“Lineal”, “Cuadrático”), R2 = c(r2_lineal, r2_cuad), AIC = c(aic_lineal, aic_cuad), BIC = c(bic_lineal, bic_cuad))
El modelo cuadrático mejora el ajuste (mayor R² y/o menores AIC/BIC), lo que sugiere que el efecto del hacinamiento sobre la permanencia puede intensificarse en niveles críticos, evidenciando un patrón no estrictamente lineal.
```r
```{r}```{rcomparacion_modelos}
r2_cuad <- res_cuad$r.squared
aic_cuad <- AIC(modelo_cuad)
bic_cuad <- BIC(modelo_cuad)
data.frame(
Modelo = c("Lineal", "Cuadrático"),
R2 = c(r2_lineal, r2_cuad),
AIC = c(aic_lineal, aic_cuad),
BIC = c(bic_lineal, bic_cuad))
El modelo cuadrático mejora el ajuste (mayor R² y/o menores AIC/BIC), lo que sugiere que el efecto del hacinamiento sobre la permanencia puede intensificarse en niveles críticos, evidenciando un patrón no estrictamente lineal.
``` r
set.seed(123)
n <- nrow(datos_limpios)
idx_train <- sample(1:n, size = round(0.7*n))
train <- datos_limpios[idx_train, ]
test <- datos_limpios[-idx_train, ]
m_lin <- lm(tiempo_permanencia ~ indice_hacinamiento, data = train)
m_cua <- lm(tiempo_permanencia ~ indice_hacinamiento + I(indice_hacinamiento^2),
data = train)
pred_lin <- predict(m_lin, newdata = test)
pred_cua <- predict(m_cua, newdata = test)
rmse <- function(y, yhat) sqrt(mean((y - yhat)^2))
mae <- function(y, yhat) mean(abs(y - yhat))
r2_test <- function(y, yhat){
ss_res <- sum((y - yhat)^2)
ss_tot <- sum((y - mean(y))^2)
1 - ss_res/ss_tot
}
result_test <- data.frame(
Modelo = c("Lineal", "Cuadrático"),
RMSE = c(rmse(test$tiempo_permanencia, pred_lin),
rmse(test$tiempo_permanencia, pred_cua)),
MAE = c(mae(test$tiempo_permanencia, pred_lin),
mae(test$tiempo_permanencia, pred_cua)),
R2_test = c(r2_test(test$tiempo_permanencia, pred_lin),
r2_test(test$tiempo_permanencia, pred_cua))
)
result_test
## Modelo RMSE MAE R2_test
## 1 Lineal 42.81071 34.61383 -0.003900993
## 2 Cuadrático 42.97905 34.92930 -0.011811390
La evaluación en conjunto de prueba permite medir desempeño predictivo. El mejor modelo es aquel con RMSE y MAE más bajos, y R²_test más alto, indicando mejor generalización en datos no usados en el ajuste.
``` r
df3d <- datos_limpios %>% mutate(triage_num = as.numeric(triage_level))
p1 <- plot_ly(
data = df3d,
x = ~indice_hacinamiento,
y = ~triage_num,
z = ~tiempo_permanencia,
type = "scatter3d",
mode = "markers",
color = ~triage_level,
marker = list(size = 3, opacity = 0.7),
text = ~paste0(
"Triage: ", triage_level,
"<br>Hacinamiento: ", round(indice_hacinamiento, 2), "%",
"<br>Tiempo: ", round(tiempo_permanencia, 1), " min"
),
hoverinfo = "text"
) %>%
layout(
title = "3D interactivo: Hacinamiento vs Permanencia (por Triage)",
scene = list(
xaxis = list(title = "Índice de hacinamiento (%)"),
yaxis = list(title = "Triage (nivel)"),
zaxis = list(title = "Tiempo de permanencia (min)")
)
)
p1
```r
``` r
p2 <- plot_ly(
data = df3d,
x = ~indice_hacinamiento,
y = ~triage_num,
z = ~tiempo_permanencia,
frame = ~triage_level,
type = "scatter3d",
mode = "markers",
color = ~triage_level,
marker = list(size = 3, opacity = 0.8),
text = ~paste0(
"Triage: ", triage_level,
"<br>Hacinamiento: ", round(indice_hacinamiento, 2), "%",
"<br>Tiempo: ", round(tiempo_permanencia, 1), " min"
),
hoverinfo = "text"
) %>%
layout(
title = "3D ANIMADO por Triage (slider)",
scene = list(
xaxis = list(title = "Índice de hacinamiento (%)"),
yaxis = list(title = "Triage (nivel)"),
zaxis = list(title = "Tiempo de permanencia (min)")
)
) %>%
animation_opts(frame = 850, transition = 0, easing = "linear", redraw = FALSE) %>%
animation_slider(currentvalue = list(prefix = "Triage: "))
p2
Las visualizaciones 3D permiten explorar de forma dinámica la relación entre hacinamiento, permanencia y triage. El slider facilita el análisis estratificado por severidad clínica, aportando una interpretación más técnica y robusta del comportamiento observado.
En conjunto, los modelos muestran una asociación positiva entre el índice de hacinamiento y el tiempo de permanencia. El modelo cuadrático mejora el ajuste frente al lineal, lo que sugiere que en niveles altos de ocupación el incremento de la permanencia puede intensificarse (efecto no lineal). El testeo (Train/Test) permite evaluar la capacidad predictiva: el modelo con menor error (RMSE/MAE) en prueba es el más adecuado para apoyar decisiones operativas.