1. Exploración Analítica de Datos (EDA)

library(readr)
library(dplyr)
library(janitor)
library(stringr)
library(tidyr)
library(ggplot2)
library(plotly)

datos <- readr::read_csv(
  "output.csv",
  show_col_types = FALSE
)

datos <- janitor::clean_names(datos)

1.1. Reconocimiento y estructura de la base de datos

La base de datos contiene registros de instituciones sanitarias,su ubicación y las agrupaciones registradas en la aplicación HMIS. La exploración inicial permite conocer su estructura y las características de las variables.

# Consultar el numero de filas y columnas
dim(datos)
## [1] 1801    5
# Mostrar los nombres de las variables
names(datos)
## [1] "sr_no"                        "name"                        
## [3] "district"                     "address_of_hospital"         
## [5] "clusters_in_hmis_application"
# Consultar la estructura de los datos
str(datos)
## spc_tbl_ [1,801 × 5] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ sr_no                       : num [1:1801] 1 2 3 4 5 6 7 8 9 10 ...
##  $ name                        : chr [1:1801] "AMCU-PHC AAMBLI" "AMCU-PHC AMRAIWADI" "AMCU-PHC ARBUDANAGAR" "AMCU-PHC ASARWA" ...
##  $ district                    : chr [1:1801] "AHMADABAD" "AHMADABAD" "AHMADABAD" "AHMADABAD" ...
##  $ address_of_hospital         : chr [1:1801] "Ambli Brts Bus Stand,B/H Iscon Mandir,Ambli Gam" "Amraiwadi Urban Primery  Health Center, Amraiwadi Swastik Char Rasta ,Metro Train 40Na Pillar  Pase, Nr Mu Garden Amraiwadi" "Arbudanagar Phc Opp.Krishna Watter Park Nr.Panchal Vadi ,Viratnagar Road Ahemadabad" "Juna Asarwa ,Haripura Gam,Asarwa" ...
##  $ clusters_in_hmis_application: chr [1:1801] "CLUSTER-17 OBSTETRICS AND GYNAECOLOGY, CLUSTER-21 GENERAL MEDICINE, CLUSTER-23 EMERGENCY ROOM PACKAGES (Care Re"| __truncated__ "CLUSTER-17 OBSTETRICS AND GYNAECOLOGY, CLUSTER-21 GENERAL MEDICINE, CLUSTER-23 EMERGENCY ROOM PACKAGES (Care Re"| __truncated__ "CLUSTER-17 OBSTETRICS AND GYNAECOLOGY, CLUSTER-21 GENERAL MEDICINE, CLUSTER-23 EMERGENCY ROOM PACKAGES (Care Re"| __truncated__ "CLUSTER-17 OBSTETRICS AND GYNAECOLOGY, CLUSTER-21 GENERAL MEDICINE, CLUSTER-23 EMERGENCY ROOM PACKAGES (Care Re"| __truncated__ ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   `Sr. No.` = col_double(),
##   ..   Name = col_character(),
##   ..   District = col_character(),
##   ..   `Address of Hospital` = col_character(),
##   ..   `Clusters in HMIS application` = col_character()
##   .. )
##  - attr(*, "problems")=<pointer: 0x74a4ffd580>
# Mostrar los primeros seis registros
head(datos, 6)
## # A tibble: 6 × 5
##   sr_no name                 district address_of_hospital clusters_in_hmis_app…¹
##   <dbl> <chr>                <chr>    <chr>               <chr>                 
## 1     1 AMCU-PHC AAMBLI      AHMADAB… Ambli Brts Bus Sta… "CLUSTER-17 OBSTETRIC…
## 2     2 AMCU-PHC AMRAIWADI   AHMADAB… Amraiwadi Urban Pr… "CLUSTER-17 OBSTETRIC…
## 3     3 AMCU-PHC ARBUDANAGAR AHMADAB… Arbudanagar Phc Op… "CLUSTER-17 OBSTETRIC…
## 4     4 AMCU-PHC ASARWA      AHMADAB… Juna Asarwa ,Harip… "CLUSTER-17 OBSTETRIC…
## 5     5 AMCU-PHC BAGE FIRDO… AHMADAB… Indrapuri Uhc, Sur… "CLUSTER-17 OBSTETRIC…
## 6     6 AMCU-PHC BAHERAMPURA AHMADAB… Nr.  Khodiyarnagar… "CLUSTER-17 OBSTETRIC…
## # ℹ abbreviated name: ¹​clusters_in_hmis_application

1.2. Revisión de la calidad de los datos

En esta etapa se revisa si la base contiene datos faltantes, registros duplicados o direcciones que requieren verificación. Esto permite reconocer posibles problemas en la información antes de elaborar las visualizaciones.

# Revisar datos faltantes por variable
faltantes <- data.frame(
  Variable = names(datos),
  Datos_faltantes = colSums(is.na(datos))
)

knitr::kable(
  faltantes,
  caption = "Datos faltantes por variable"
)
Datos faltantes por variable
Variable Datos_faltantes
sr_no sr_no 0
name name 0
district district 0
address_of_hospital address_of_hospital 56
clusters_in_hmis_application clusters_in_hmis_application 0
# Revisar registros completamente duplicados
sum(duplicated(datos))
## [1] 0
# Revisar las direcciones registradas
datos %>%
  mutate(
    estado_direccion = case_when(
      is.na(address_of_hospital) |
        trimws(address_of_hospital) == "" ~
        "Sin direccion registrada",
      trimws(address_of_hospital) == "0" ~
        "Direccion registrada como cero",
      TRUE ~ "Direccion registrada"
    )
  ) %>%
  count(estado_direccion) %>%
  knitr::kable(
    caption = "Estado de las direcciones hospitalarias"
  )
Estado de las direcciones hospitalarias
estado_direccion n
Direccion registrada 1664
Direccion registrada como cero 81
Sin direccion registrada 56

1.3. Visualización interactiva e interpretación

Gráfico 1. Instituciones registradas por distrito

Este gráfico permite explorar la cantidad de instituciones registradas en cada distrito

 instituciones_distrito <- datos %>%
  count(district, sort = TRUE)

grafico1 <- ggplot(
  instituciones_distrito,
  aes(x = reorder(district, n), y = n)
) +
geom_col() +
geom_text(aes(label = n), hjust = -0.2, size = 3) +
coord_flip() +
  labs(
    title = "Instituciones registradas por distrito",
    x = "Distrito",
    y = "Número de instituciones"
  ) +
  theme_minimal()

plotly::ggplotly(grafico1)

Interpretación del gráfico

El gráfico permite comparar la cantidad de instituciones sanitarias registradas en cada distrito. Esta visualización facilita identificar los distritos con mayor y menor número de registros y observar cómo se distribuyen las instituciones en la base de datos. Los resultados corresponden a los registros disponibles y no necesariamente representan la totalidad de las instituciones existentes en cada distrito.

Gráfico 2. Pareto de registros por distrito

El diagrama de Pareto permite analizar la distribución de los registros de instituciones sanitarias entre los distritos. Para ello, se seleccionan los diez distritos con mayor número de registros y se calcula el porcentaje que representa cada uno respecto al total de la base de datos, así como el porcentaje acumulado. Esta visualización facilita identificar los distritos que concentran una mayor proporción de los registros disponibles

library(dplyr)
library(ggplot2)
library(plotly)

pareto <- datos %>%
  count(district, sort = TRUE) %>%
  mutate(
    porcentaje = n / sum(n) * 100,
    acumulado = cumsum(porcentaje),
    posicion = row_number()
  ) %>%
  filter(posicion <= 10)

grafico2 <- ggplot(pareto, aes(x = posicion)) +
  geom_col(aes(y = porcentaje),
           fill = "steelblue") +
  geom_line(aes(y = acumulado, group = 1),
            color = "red", linewidth = 1) +
  geom_point(aes(y = acumulado),
             color = "red", size = 2) +
  scale_x_continuous(
    breaks = pareto$posicion,
    labels = pareto$district
  ) +
  labs(
    title = "Pareto de los 10 principales distritos",
    x = "Distrito",
    y = "Porcentaje de registros"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(
    angle = 45, hjust = 1
  ))

plotly::ggplotly(grafico2)

Interpretación del gráfico

Banas Kantha presenta el mayor número de registros, seguido de Ahmadabad y Surat. La línea roja muestra el porcentaje acumulado de los diez distritos con más registros, permitiendo identificar dónde se concentra una mayor proporción de la información disponible.

Gráfico 3. Estado de las direcciones hospitalarias

Este gráfico muestra la proporción de registros con dirección hospitalaria registrada, con valor cero o sin información.

library(dplyr)
library(ggplot2)
library(plotly)

estado_direcciones <- datos %>%
  mutate(
    estado_direccion = case_when(
      is.na(address_of_hospital) |
        trimws(address_of_hospital) == "" ~ "Sin dirección registrada",
      trimws(address_of_hospital) == "0" ~ "Dirección registrada como cero",
      TRUE ~ "Dirección registrada"
    )
  ) %>%
  count(estado_direccion) %>%
  mutate(
    porcentaje = n / sum(n) * 100,
    etiqueta = paste0(
      estado_direccion, "\n",
      round(porcentaje, 1), "%"
    )
  )

grafico3 <- ggplot(
  estado_direcciones,
  aes(x = "", y = n, fill = estado_direccion)
) +
  geom_col(width = 1) +
  geom_text(
    aes(
      label = ifelse(
        porcentaje >= 10,
        paste0(n, "\n", round(porcentaje, 1), "%"),
        ""
      )
    ),
    position = position_stack(vjust = 0.5),
    size = 3
  ) +
  geom_text(
  data = subset(estado_direcciones, porcentaje < 10),
  aes(
    x = 1,
    y = n,
    label = paste0(n, "\n", round(porcentaje, 1), "%"),
    hjust = ifelse(n == 56, 1.5, -0.8)
  ),
  inherit.aes = FALSE,
  size = 3
) +
coord_polar(theta = "y") +
  scale_y_continuous(breaks = NULL, labels = NULL) +
  labs(
    title = "Estado de las direcciones hospitalarias",
    fill = "Estado de la dirección"
  ) +
  theme_minimal(base_size = 12)

grafico3

Interpretación del gráfico

La mayoría de los registros tiene dirección registrada (1.664; 92,4 %). Además, 81 registros tienen dirección en cero (4,5 %) y 56 no cuentan con dirección registrada (3,1 %). Esto evidencia oportunidades de mejora en la calidad de los datos.

2. Probabilidad y Estadística Descriptiva e Inferencial

Este apartado aplica conceptos básicos de probabilidad y estadística a la gestión hospitalaria. Se utilizan registros reales de instituciones para analizar frecuencias y datos simulados para practicar distribuciones de probabilidad y pruebas estadísticas. Los datos simulados se identifican expresamente y no representan resultados reales de pacientes.

# 2.1. Probabilidad y simulación de eventos

# # 2.1.1. Selección aleatoria de instituciones

set.seed(123)

muestra <- sample( seq_len(nrow(datos)), size = 10 )

datos[muestra, ] # # # 2.1.2. Frecuencias y probabilidades empíricas # Contar cuántas instituciones hay por distrito frecuencias <- table(datos$district)

Mostrar los 10 distritos con mayor frecuencia

head(sort(frecuencias, decreasing = TRUE), 10) # # # 2.1.3. Simulación de eventos discretos # Simular la selección de 100 instituciones set.seed(123)

seleccion <- sample( datos$district, size = 100, replace = TRUE )

Contar cuántas instituciones seleccionadas hay por distrito

table(seleccion) # # 2.2. Estadística descriptiva y distribuciones de probabilidad

# # 2.2.1. Medidas descriptivas de los registros

Cantidad de instituciones por distrito

frecuencias <- table(datos$district)

Convertir las frecuencias en una variable numérica

cantidad <- as.numeric(frecuencias)

Estadística descriptiva

mean(cantidad) median(cantidad) sd(cantidad) var(cantidad) range(cantidad)

Resumen general

summary(cantidad)

Interpretación de las medidas descriptivas

La base de datos contiene instituciones distribuidas en 36 distritos. La cantidad promedio de instituciones por distrito es de 50,03, mientras que la mediana es de 39. Esta diferencia sugiere que algunos distritos concentran una cantidad elevada de instituciones. Se observa una variación importante entre distritos, con un mínimo de 1 y un máximo de 148 instituciones. Estos resultados permiten identificar diferencias en la distribución territorial de los registros y orientan la identificación de distritos que podrían requerir una revisión más detallada.

# # 2.2.2. Simulación de distribuciones continuas

Simular 1000 valores alrededor de un promedio

set.seed(123)

valores <- rnorm( n = 1000, mean = 50, sd = 10 )

Histograma de los valores simulados

library(ggplot2)

ggplot(data.frame(valores), aes(x = valores)) + geom_histogram( bins = 30, fill = “steelblue”, color = “white” ) + labs( title = “Distribución normal simulada”, x = “Valor simulado”, y = “Frecuencia” ) + theme_minimal()

Interpretación de la distribución normal simulada

Se generaron 1.000 valores aleatorios con una media teórica de 50 y una desviación estándar de 10. El histograma muestra que la mayoría de los valores se concentra alrededor de 50, mientras que las frecuencias disminuyen hacia los extremos. Esta simulación permite comprender el comportamiento de una distribución normal. Los valores son simulados y no representan mediciones reales de las instituciones.

# # 2.2.3. Visualización interactiva de los resultados

# Simulación de tiempos de espera
set.seed(123)

tiempos <- rexp(
  n = 1000,
  rate = 1/10
)

# Histograma de tiempos de espera simulados
library(ggplot2)

ggplot(data.frame(tiempos), aes(x = tiempos)) +
  geom_histogram(
    bins = 30,
    fill = "darkseagreen3",
    color = "white"
  ) +
  labs(
    title = "Distribución de tiempos de espera simulados",
    x = "Tiempo de espera (minutos)",
    y = "Frecuencia"
  ) +
  theme_minimal()

Interpretación de los tiempos de espera simulados

Se generaron 1.000 tiempos de espera simulados mediante una distribución exponencial, con un promedio teórico de 10 minutos. Se espera que muchos tiempos sean cortos y que pocos sean muy prolongados. Este ejemplo permite comprender cómo puede variar el tiempo entre eventos, aunque los datos no corresponden a mediciones reales de una institución de salud.

# Simulación de tiempos con distribución uniforme
set.seed(123)

tiempos_uniformes <- runif(
  n = 1000,
  min = 5,
  max = 15
)

# Gráfico de la distribución uniforme
library(ggplot2)

ggplot(data.frame(tiempos_uniformes),
       aes(x = tiempos_uniformes)) +
  geom_histogram(
    bins = 20,
    fill = "cornflowerblue",
    color = "white"
  ) +
  labs(
    title = "Distribución uniforme de tiempos simulados",
    x = "Tiempo simulado (minutos)",
    y = "Frecuencia"
  ) +
  theme_minimal()

Interpretación de la distribución uniforme

Se generaron 1.000 valores aleatorios entre 5 y 15 minutos. En esta distribución, todos los intervalos de igual amplitud tienen la misma probabilidad de generar un valor. El histograma permite observar cómo se distribuyen los tiempos simulados dentro de ese rango. Este ejemplo es didáctico y no representa tiempos de espera reales de una institución de salud.

# 2.3. Estadística inferencial y comparación de grupos

# # 2.3.1. Comparación de medias e intervalos de confianza

# Comparar dos grupos de tiempos simulados
set.seed(123)

grupo_A <- rnorm(100, mean = 10, sd = 2)
grupo_B <- rnorm(100, mean = 12, sd = 2)

# Calcular las medias de cada grupo
mean(grupo_A)
## [1] 10.18081
mean(grupo_B)
## [1] 11.78491
# Comparar las medias e intervalo de confianza
prueba_t <- t.test(grupo_A, grupo_B)

prueba_t
## 
##  Welch Two Sample t-test
## 
## data:  grupo_A and grupo_B
## t = -6.0315, df = 197.35, p-value = 7.88e-09
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  -2.128572 -1.079617
## sample estimates:
## mean of x mean of y 
##  10.18081  11.78491

Interpretación de la comparación de medias

Se compararon dos grupos de 100 tiempos simulados. El grupo A tiene una media teórica de 10 minutos y el grupo B de 12 minutos. La prueba t permite estimar la diferencia entre las medias y obtener un intervalo de confianza del 95 %. El valor p ayuda a evaluar si la diferencia observada es compatible con la hipótesis de igualdad de medias. Como los datos son simulados, los resultados son demostrativos y no permiten sacar conclusiones sobre los tiempos reales de una institución de salud.

# # 2.3.2. Comparación de tres grupos y asociación entre variables

# Comparación de tres grupos mediante ANOVA
set.seed(123)

grupo_A <- rnorm(50, mean = 10, sd = 2)
grupo_B <- rnorm(50, mean = 12, sd = 2)
grupo_C <- rnorm(50, mean = 14, sd = 2)

# Crear una tabla con los tres grupos
tiempos <- data.frame(
  tiempo = c(grupo_A, grupo_B, grupo_C),
  grupo = factor(rep(c("A", "B", "C"), each = 50))
)

# Comparar las medias
modelo_anova <- aov(tiempo ~ grupo, data = tiempos)

summary(modelo_anova)
##              Df Sum Sq Mean Sq F value   Pr(>F)    
## grupo         2  301.7  150.87    42.6 2.54e-15 ***
## Residuals   147  520.5    3.54                     
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Interpretación del ANOVA

Se compararon tres grupos de 50 tiempos simulados, con medias teóricas de 10, 12 y 14 minutos. El ANOVA permite evaluar si existen diferencias estadísticamente significativas entre las medias de los tres grupos. Si el valor p es menor que 0,05, existe evidencia de que al menos una media es diferente. Esta prueba no identifica por sí sola qué grupos difieren entre sí. Los datos son simulados y no representan resultados reales de una institución de salud.


``` r
# Asociación entre tipo de atención y resultado
set.seed(123)

tipo_atencion <- sample(
  c("Urgencias", "Hospitalizacion"),
  size = 200,
  replace = TRUE
)

resultado <- sample(
  c("Favorable", "No favorable"),
  size = 200,
  replace = TRUE
)

# Crear una tabla de frecuencias
tabla_resultados <- table(tipo_atencion, resultado)

tabla_resultados
##                  resultado
## tipo_atencion     Favorable No favorable
##   Hospitalizacion        59           38
##   Urgencias              49           54
# Aplicar prueba chi-cuadrado
prueba_chi <- chisq.test(tabla_resultados)
prueba_chi
## 
##  Pearson's Chi-squared test with Yates' continuity correction
## 
## data:  tabla_resultados
## X-squared = 3.0184, df = 1, p-value = 0.08233

Interpretación de la prueba chi-cuadrado

Se construyó una tabla con 200 observaciones simuladas para explorar la asociación entre el tipo de atención y el resultado. La prueba chi-cuadrado evalúa si existe evidencia de asociación entre las dos variables categóricas. Si el valor p es menor que 0,05, existe evidencia estadística de asociación; si es mayor o igual a 0,05, no se encuentra evidencia suficiente para afirmarla. Como las variables se generaron aleatoriamente e independientemente, el ejercicio es didáctico y no permite establecer conclusiones sobre pacientes reales.

# # 2.3.3. Comparación de proporciones y gráficos comparativos

# Comparar la proporción de resultados favorables

# Número de resultados favorables en cada grupo
favorables <- c(59, 49)

# Total de observaciones de cada grupo
totales <- c(97, 103)

# Aplicar la prueba de comparación de proporciones
prueba_proporciones <- prop.test(
  x = favorables,
  n = totales
)

prueba_proporciones
## 
##  2-sample test for equality of proportions with continuity correction
## 
## data:  favorables out of totales
## X-squared = 3.0184, df = 1, p-value = 0.08233
## alternative hypothesis: two.sided
## 95 percent confidence interval:
##  -0.01437867  0.27941721
## sample estimates:
##    prop 1    prop 2 
## 0.6082474 0.4757282

Interpretación de la comparación de proporciones

Se comparó la proporción de resultados favorables entre hospitalización y urgencias mediante la función prop.test(). La prueba permite evaluar si existe una diferencia estadísticamente significativa entre las proporciones de ambos grupos. Si el valor p es menor que 0,05, existe evidencia estadística de una diferencia; si es igual o mayor que 0,05, no hay evidencia suficiente para afirmar que las proporciones son diferentes. Los resultados corresponden a datos simulados y no deben interpretarse como indicadores reales de desempeño hospitalario.

# Grafico comparativo de proporciones

library(ggplot2)

comparacion <- data.frame(
  tipo_atencion = c("Hospitalización", "Urgencias"),
  favorables = c(59, 49),
  total = c(97, 103)
)

comparacion$porcentaje <- (
  comparacion$favorables / comparacion$total
) * 100

ggplot(comparacion, aes(
  x = tipo_atencion,
  y = porcentaje,
  fill = tipo_atencion
)) +
  geom_col(width = 0.6) +
  geom_text(
    aes(label = paste0(round(porcentaje, 1), "%")),
    vjust = -0.5
  ) +
  labs(
    title = "Proporción de resultados favorables",
    subtitle = "Datos simulados",
    x = "Tipo de atención",
    y = "Resultados favorables (%)"
  ) +
  theme_minimal() +
  theme(legend.position = "none")

Interpretación del gráfico comparativo de proporciones

El gráfico muestra que, en los datos simulados, el grupo de hospitalización presenta una proporción de resultados favorables del 60,8 %, mientras que el grupo de urgencias presenta una proporción del 47,6 %. Se observa una diferencia descriptiva de 13,2 puntos porcentuales entre ambos grupos. Sin embargo, esta diferencia visual no demuestra por sí sola que exista una diferencia estadísticamente significativa. Para evaluarlo, se debe considerar el resultado de la prueba de comparación de proporciones realizada previamente. Los datos son simulados y no representan indicadores reales de desempeño hospitalario.

# Grafico comparativo con ggpubr

library(ggpubr)

ggbarplot(
  comparacion,
  x = "tipo_atencion",
  y = "porcentaje",
  fill = "tipo_atencion",
  label = FALSE
) +
  geom_text(
    aes(label = paste0(round(porcentaje, 1), "%")),
    vjust = -0.5
  ) +
  labs(
    title = "Comparación de resultados favorables",
    subtitle = "Datos simulados",
    x = "Tipo de atención",
    y = "Resultados favorables (%)"
  ) +
  theme_minimal() +
  theme(legend.position = "none")