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

El EDA (revisión inicial de los datos antes de cualquier análisis formal) permite conocer qué contiene la base, en qué estado está y qué patrones visuales aparecen.

1.1 Preparación del entorno

1.1.1 Paquetes

library(tidyverse)  # manipulación de datos y gráficos (incluye ggplot2 y lubridate)
library(plotly)     # vuelve interactivos los gráficos
library(DT)         # tablas interactivas

1.1.2 Carga de la base de datos

Los identificadores se leen como texto para no perder los ceros a la izquierda.

citas <- read_csv("Appointments.csv",
                  col_types = cols(appointment_id = col_character(),
                                   slot_id        = col_character(),
                                   patient_id     = col_character()))

1.2 Descripción de las variables

1.2.1 Estructura general

dim(citas)      # filas y columnas
## [1] 111488     16
glimpse(citas)  # nombre, tipo y primeros valores de cada variable
## Rows: 111,488
## Columns: 16
## $ appointment_id       <chr> "0000138", "0000146", "0000021", "0000233", "0000…
## $ slot_id              <chr> "0000001", "0000023", "0000024", "0000025", "0000…
## $ scheduling_date      <date> 2014-12-28, 2014-12-29, 2014-12-17, 2014-12-31, …
## $ appointment_date     <date> 2015-01-01, 2015-01-01, 2015-01-01, 2015-01-01, …
## $ appointment_time     <time> 08:00:00, 13:30:00, 13:45:00, 14:00:00, 14:15:00…
## $ scheduling_interval  <dbl> 4, 3, 15, 1, 6, 2, 2, 2, 4, 4, 4, 1, 1, 7, 3, 2, …
## $ status               <chr> "did not attend", "did not attend", "attended", "…
## $ check_in_time        <time>       NA,       NA, 13:36:45, 13:59:32,       NA…
## $ appointment_duration <dbl> NA, NA, 5.2, 28.9, NA, 7.7, 4.2, 27.1, NA, 1.2, 7…
## $ start_time           <time>       NA,       NA, 13:37:57, 14:00:40,       NA…
## $ end_time             <time>       NA,       NA, 13:43:09, 14:29:34,       NA…
## $ waiting_time         <dbl> NA, NA, 1.2, 1.1, NA, 21.7, 16.2, 1.0, NA, 8.5, 2…
## $ patient_id           <chr> "08285", "05972", "06472", "05376", "08028", "043…
## $ sex                  <chr> "Male", "Male", "Male", "Female", "Male", "Female…
## $ age                  <dbl> 37, 84, 77, 37, 72, 51, 28, 33, 29, 90, 66, 64, 3…
## $ age_group            <chr> "35-39", "80-84", "75-79", "35-39", "70-74", "50-…
datatable(head(citas, 100), options = list(scrollX = TRUE, pageLength = 5))

La base contiene 111.488 citas y 16 variables. Las citas van del 1 de enero de 2015 al 24 de diciembre de 2024 (los agendamientos inician en diciembre de 2014) y corresponden a 36.697 pacientes distintos; es decir, un mismo paciente tiene varias citas.

1.2.2 Diccionario de variables

diccionario <- tibble(
  Variable = names(citas),
  Descripcion = c(
    "Identificador único de la cita",
    "Identificador del espacio (turno) en la agenda",
    "Fecha en que se agendó la cita",
    "Fecha programada de la cita",
    "Hora programada de la cita",
    "Días entre el agendamiento y la cita",
    "Estado final de la cita",
    "Hora de llegada/registro del paciente",
    "Duración de la atención (minutos)",
    "Hora real de inicio de la atención",
    "Hora real de finalización de la atención",
    "Tiempo de espera antes de ser atendido (minutos)",
    "Identificador del paciente",
    "Sexo del paciente",
    "Edad del paciente (años)",
    "Grupo de edad (rangos de 5 años)"
  ),
  Tipo = sapply(citas, function(x) class(x)[1])
)

datatable(diccionario, options = list(pageLength = 16))

Las variables se agrupan en: identificadores (appointment_id, slot_id, patient_id), fechas y horas de agenda y de atención real, tiempos en minutos (appointment_duration, waiting_time) y características del paciente (sex, age, age_group).

1.2.3 Ajuste de tipos de dato

Se traducen las categorías al español y se convierten a factor (variable con categorías fijas).

citas <- citas %>%
  mutate(
    status = recode(status,
                    "attended"       = "Asistió",
                    "cancelled"      = "Cancelada",
                    "did not attend" = "No asistió",
                    "scheduled"      = "Programada",
                    "unknown"        = "Desconocido"),
    sex = recode(sex, "Female" = "Femenino", "Male" = "Masculino"),
    status    = as.factor(status),
    sex       = as.factor(sex),
    age_group = factor(age_group, levels = c("15-19","20-24","25-29","30-34","35-39",
                                             "40-44","45-49","50-54","55-59","60-64",
                                             "65-69","70-74","75-79","80-84","85-89","90+"))
  )

glimpse(citas)
## Rows: 111,488
## Columns: 16
## $ appointment_id       <chr> "0000138", "0000146", "0000021", "0000233", "0000…
## $ slot_id              <chr> "0000001", "0000023", "0000024", "0000025", "0000…
## $ scheduling_date      <date> 2014-12-28, 2014-12-29, 2014-12-17, 2014-12-31, …
## $ appointment_date     <date> 2015-01-01, 2015-01-01, 2015-01-01, 2015-01-01, …
## $ appointment_time     <time> 08:00:00, 13:30:00, 13:45:00, 14:00:00, 14:15:00…
## $ scheduling_interval  <dbl> 4, 3, 15, 1, 6, 2, 2, 2, 4, 4, 4, 1, 1, 7, 3, 2, …
## $ status               <fct> No asistió, No asistió, Asistió, Asistió, Cancela…
## $ check_in_time        <time>       NA,       NA, 13:36:45, 13:59:32,       NA…
## $ appointment_duration <dbl> NA, NA, 5.2, 28.9, NA, 7.7, 4.2, 27.1, NA, 1.2, 7…
## $ start_time           <time>       NA,       NA, 13:37:57, 14:00:40,       NA…
## $ end_time             <time>       NA,       NA, 13:43:09, 14:29:34,       NA…
## $ waiting_time         <dbl> NA, NA, 1.2, 1.1, NA, 21.7, 16.2, 1.0, NA, 8.5, 2…
## $ patient_id           <chr> "08285", "05972", "06472", "05376", "08028", "043…
## $ sex                  <fct> Masculino, Masculino, Masculino, Femenino, Mascul…
## $ age                  <dbl> 37, 84, 77, 37, 72, 51, 28, 33, 29, 90, 66, 64, 3…
## $ age_group            <fct> 35-39, 80-84, 75-79, 35-39, 70-74, 50-54, 25-29, …

1.2.4 Calidad de los datos

faltantes <- tibble(
  Variable   = names(citas),
  Faltantes  = colSums(is.na(citas)),
  Porcentaje = round(colSums(is.na(citas)) / nrow(citas) * 100, 2)
)
datatable(faltantes)
# ¿En qué estados están las celdas vacías de la atención?
citas %>%
  filter(is.na(check_in_time)) %>%
  count(status)
## # A tibble: 4 × 2
##   status          n
##   <fct>       <int>
## 1 Cancelada   18254
## 2 Desconocido   446
## 3 No asistió   6615
## 4 Programada    141
sum(duplicated(citas$appointment_id))  # citas repetidas
## [1] 0
sum(duplicated(citas))                 # filas completas repetidas
## [1] 0

Las cinco variables de atención (check_in_time, appointment_duration, start_time, end_time, waiting_time) tienen los mismos 25.456 faltantes (NA, celdas vacías), y todos pertenecen a citas sin atención: canceladas, no asistidas, desconocidas y programadas. Las citas atendidas no tienen faltantes. Por eso no son errores de captura y no deben eliminarse ni rellenarse. No hay citas ni filas duplicadas.

1.3 Visualización interactiva

1.3.1 Estado de las citas

g1 <- citas %>%
  count(status) %>%
  ggplot(aes(x = reorder(status, -n), y = n, fill = status)) +
  geom_col() +
  labs(title = "Citas según estado", x = "Estado", y = "Número de citas") +
  theme_minimal() +
  theme(legend.position = "none")

ggplotly(g1)

La mayoría de citas fueron atendidas (86.032). Las canceladas (18.254) y las inasistencias (6.615) suman 24.869 espacios de agenda no usados como se planeó, lo que representa capacidad instalada perdida (horas de profesional disponibles que no se convierten en atención). Las 141 citas “Programadas” son de diciembre de 2024: aún no habían ocurrido al corte de la base. Los 446 casos “Desconocido” son un problema de registro a reportar.

1.3.2 Citas por grupo de edad y sexo

g2 <- citas %>%
  count(age_group, sex) %>%
  ggplot(aes(x = age_group, y = n, fill = sex)) +
  geom_col(position = "dodge") +
  labs(title = "Citas por grupo de edad y sexo",
       x = "Grupo de edad", y = "Número de citas", fill = "Sexo") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggplotly(g2)

La demanda se concentra en mayores de 55 años; el grupo con más citas es 75-79 (9.844). Las mujeres tienen más citas en todos los grupos (66.086 frente a 45.402 de hombres), con la brecha más amplia entre 25 y 39 años (en 30-34: 5.807 mujeres frente a 1.720 hombres), posiblemente asociada a atención en edad reproductiva. Desde los 60 años la demanda de ambos sexos casi se iguala.

1.3.3 Evolución mensual de las citas

Se excluye diciembre de 2024 porque solo tiene 166 citas (corte a mitad de mes) y mostraría una caída falsa.

g3 <- citas %>%
  filter(appointment_date < as.Date("2024-12-01")) %>%
  mutate(mes = floor_date(appointment_date, "month")) %>%
  count(mes, status) %>%
  ggplot(aes(x = mes, y = n, color = status)) +
  geom_line() +
  labs(title = "Citas por mes según estado (ene-2015 a nov-2024)",
       x = "Mes", y = "Número de citas", color = "Estado") +
  theme_minimal()

ggplotly(g3)

El volumen es estable durante los diez años (alrededor de 8.600 a 8.700 citas atendidas por año), sin tendencia de crecimiento ni caída. 2020 no muestra la caída esperable por la pandemia, lo que sugiere que la base podría ser simulada (generada artificialmente); conviene confirmarlo con la fuente. Febrero y diciembre son los meses con menos citas. Cancelaciones e inasistencias se mueven en paralelo a las atendidas, sin picos atípicos.