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.

2 Probabilidad y Estadística Aplicada

En este tema se usa la base Sleep Health and Lifestyle. Con ella se responden preguntas de salud y seguridad en el trabajo sobre el sueño de los trabajadores, entre ellos personal de enfermería y medicina. Las herramientas son la probabilidad (qué tan posible es que algo ocurra), la simulación (datos artificiales que imitan la realidad) y la estadística descriptiva e inferencial (sacar conclusiones confiables a partir de los datos).

2.1 Preparación de la base de datos

2.1.1 Carga y ajuste de variables

El archivo Sleep_health_and_lifestyle_dataset.xlsx debe estar en la misma carpeta de este .Rmd.

library(readxl)

sueno <- read_excel("Sleep_health_and_lifestyle_dataset.xlsx") %>%
  rename(id = `Person ID`, sexo = Gender, edad = Age, ocupacion = Occupation,
         horas_sueno = `Sleep Duration`, calidad_sueno = `Quality of Sleep`,
         actividad_fisica = `Physical Activity Level`, estres = `Stress Level`,
         imc = `BMI Category`, presion = `Blood Pressure`,
         frec_cardiaca = `Heart Rate`, pasos = `Daily Steps`,
         trastorno = `Sleep Disorder`) %>%
  mutate(
    horas_sueno = as.numeric(horas_sueno),               # venía como texto
    pas = as.numeric(sub("/.*", "", presion)),           # presión sistólica (número antes de "/")
    pad = as.numeric(sub(".*/", "", presion)),           # presión diastólica (número después de "/")
    sexo = recode(sexo, "Female" = "Mujer", "Male" = "Hombre"),
    imc = recode(imc, "Normal Weight" = "Normal", "Overweight" = "Sobrepeso", "Obese" = "Obesidad"),
    imc = factor(imc, levels = c("Normal", "Sobrepeso", "Obesidad")),
    trastorno = factor(ifelse(trastorno == 1, "Con trastorno", "Sin trastorno"),
                       levels = c("Sin trastorno", "Con trastorno")),
    ocupacion = recode(ocupacion,
                       "Nurse" = "Enfermería", "Doctor" = "Medicina", "Engineer" = "Ingeniería",
                       "Lawyer" = "Derecho", "Teacher" = "Docencia", "Accountant" = "Contaduría",
                       "Salesperson" = "Ventas", "Sales Representative" = "Representante de ventas",
                       "Scientist" = "Ciencias", "Software Engineer" = "Ingeniería de software",
                       "Manager" = "Gerencia"),
    estres_nivel = factor(ifelse(estres >= 7, "Alto (7-8)", "Bajo o medio (3-6)")),
    sueno_corto  = factor(ifelse(horas_sueno < 7, "Menos de 7 h", "7 h o más"))
  )

dim(sueno)
## [1] 374  17
glimpse(sueno)
## Rows: 374
## Columns: 17
## $ id               <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16…
## $ sexo             <chr> "Hombre", "Hombre", "Hombre", "Hombre", "Hombre", "Ho…
## $ edad             <dbl> 27, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 2…
## $ ocupacion        <chr> "Ingeniería de software", "Medicina", "Medicina", "Re…
## $ horas_sueno      <dbl> 6.1, 6.2, 6.2, 5.9, 5.9, 5.9, 6.3, 7.8, 7.8, 7.8, 6.1…
## $ calidad_sueno    <dbl> 6, 6, 6, 4, 4, 4, 6, 7, 7, 7, 6, 7, 6, 6, 6, 6, 5, 6,…
## $ actividad_fisica <dbl> 42, 60, 60, 30, 30, 30, 40, 75, 75, 75, 30, 75, 30, 3…
## $ estres           <dbl> 6, 8, 8, 8, 8, 8, 7, 6, 6, 6, 8, 6, 8, 8, 8, 8, 7, 8,…
## $ imc              <fct> Sobrepeso, Normal, Normal, Obesidad, Obesidad, Obesid…
## $ presion          <chr> "126/83", "125/80", "125/80", "140/90", "140/90", "14…
## $ frec_cardiaca    <dbl> 77, 75, 75, 85, 85, 85, 82, 70, 70, 70, 70, 70, 70, 7…
## $ pasos            <dbl> 4200, 10000, 10000, 3000, 3000, 3000, 3500, 8000, 800…
## $ trastorno        <fct> Sin trastorno, Sin trastorno, Sin trastorno, Con tras…
## $ pas              <dbl> 126, 125, 125, 140, 140, 140, 140, 120, 120, 120, 120…
## $ pad              <dbl> 83, 80, 80, 90, 90, 90, 90, 80, 80, 80, 80, 80, 80, 8…
## $ estres_nivel     <fct> Bajo o medio (3-6), Alto (7-8), Alto (7-8), Alto (7-8…
## $ sueno_corto      <fct> Menos de 7 h, Menos de 7 h, Menos de 7 h, Menos de 7 …

Interpretación y advertencias sobre la base: el archivo recibido tiene diferencias con la descripción del enunciado:

  • Tiene 374 filas, no 400.
  • La presión arterial viene en una sola columna con formato “126/83”. Por eso el código la separa en pas (sistólica) y pad (diastólica).
  • Las horas de sueño venían como texto y se convirtieron a número.
  • La categoría de IMC aparece escrita de dos formas (“Normal” y “Normal Weight”); se unificaron.
  • El trastorno del sueño viene codificado como 0/1. Se interpreta 1 = tiene trastorno (insomnio o apnea).

No hay datos faltantes. Sin embargo, 248 filas repiten exactamente el mismo perfil de otra persona, lo que sugiere que la base fue generada artificialmente para fines académicos. Las conclusiones sirven para aprender el método, no para describir una población real.

2.1.2 Descripción de las variables

dicc_sueno <- tibble(
  Variable = c("sexo", "edad", "ocupacion", "horas_sueno", "calidad_sueno",
               "actividad_fisica", "estres", "imc", "pas / pad", "frec_cardiaca",
               "pasos", "trastorno"),
  Descripcion = c("Sexo del trabajador", "Edad (años)", "Ocupación",
                  "Horas de sueño por día", "Calidad del sueño (escala 1 a 10)",
                  "Minutos diarios de actividad física", "Nivel de estrés (escala 1 a 10)",
                  "Categoría de índice de masa corporal", "Presión arterial sistólica / diastólica (mmHg)",
                  "Frecuencia cardíaca en reposo (latidos por minuto)", "Pasos diarios",
                  "Presencia de trastorno del sueño (insomnio o apnea)")
)
datatable(dicc_sueno, options = list(pageLength = 12))

2.2 Probabilidad

2.2.1 Espacio muestral y eventos con sample()

El espacio muestral (todos los resultados posibles) de un trabajador tamizado es: “Sin trastorno” o “Con trastorno”. El evento (resultado que interesa) es que haya más trabajadores positivos que cupos disponibles con el especialista.

Caso práctico: tamizaje de trastornos del sueño. El área de Seguridad y Salud en el Trabajo (SST) escoge al azar 20 trabajadores para un tamizaje (prueba rápida para detectar posibles casos). Solo hay 10 cupos de remisión a polisomnografía (estudio del sueño en laboratorio).

set.seed(2026)  # fija la semilla (punto de partida del azar) para poder repetir el resultado

espacio_muestral <- c("Sin trastorno", "Con trastorno")

# 1. Selección real: 20 trabajadores al azar de la base, sin repetir
seleccionados <- sample(1:nrow(sueno), size = 20)
tamizaje <- sueno[seleccionados, ]
table(tamizaje$trastorno)
## 
## Sin trastorno Con trastorno 
##            12             8
# 2. Simulación: repetir el tamizaje 1.000 veces con la probabilidad observada
p_trast <- mean(sueno$trastorno == "Con trastorno")
p_trast
## [1] 0.4144385
supera_cupos <- replicate(1000, {
  grupo <- sample(espacio_muestral, size = 20, replace = TRUE,
                  prob = c(1 - p_trast, p_trast))
  sum(grupo == "Con trastorno") > 10
})
mean(supera_cupos)   # probabilidad del evento "más de 10 positivos"
## [1] 0.151

Interpretación: en la selección real, 8 de los 20 trabajadores tienen trastorno, así que los 10 cupos alcanzan. Cuando se repite el tamizaje 1.000 veces (con probabilidad de trastorno de 41,4 %), en el 15,1 % de las veces hay más de 10 positivos. Para SST esto significa que, en más o menos 1 de cada 7 jornadas de tamizaje, faltarían cupos de polisomnografía. Con esa cifra se puede justificar ante la gerencia o la ARL la necesidad de cupos de reserva.

2.2.2 Probabilidades empíricas con table()

La probabilidad empírica (la que sale de contar lo observado) es casos del evento / total de casos.

# Probabilidad de tener trastorno del sueño
round(prop.table(table(sueno$trastorno)), 3)
## 
## Sin trastorno Con trastorno 
##         0.586         0.414
# Probabilidad de trastorno según la categoría de IMC
round(prop.table(table(sueno$imc, sueno$trastorno), margin = 1), 3)
##            
##             Sin trastorno Con trastorno
##   Normal            0.926         0.074
##   Sobrepeso         0.128         0.872
##   Obesidad          0.000         1.000
# Probabilidad de trastorno y de dormir menos de 7 h según la ocupación
ocupaciones_principales <- c("Enfermería", "Medicina", "Ingeniería", "Derecho",
                             "Docencia", "Contaduría", "Ventas")
sueno_ocup <- sueno %>% filter(ocupacion %in% ocupaciones_principales)

round(prop.table(table(sueno_ocup$ocupacion, sueno_ocup$trastorno), margin = 1), 3)
##             
##              Sin trastorno Con trastorno
##   Contaduría         0.811         0.189
##   Derecho            0.894         0.106
##   Docencia           0.225         0.775
##   Enfermería         0.123         0.877
##   Ingeniería         0.905         0.095
##   Medicina           0.901         0.099
##   Ventas             0.062         0.938
round(prop.table(table(sueno_ocup$ocupacion, sueno_ocup$sueno_corto), margin = 1), 3)
##             
##              7 h o más Menos de 7 h
##   Contaduría     0.838        0.162
##   Derecho        0.979        0.021
##   Docencia       0.150        0.850
##   Enfermería     0.493        0.507
##   Ingeniería     0.952        0.048
##   Medicina       0.535        0.465
##   Ventas         0.000        1.000

Interpretación:

  • El 41,4 % de los trabajadores tiene un trastorno del sueño.
  • El riesgo cambia mucho según el peso: 7,4 % en peso normal, 87,2 % en sobrepeso y 100 % en obesidad (aunque obesidad solo tiene 10 personas).
  • El riesgo cambia según la ocupación: enfermería (87,7 %), ventas (93,8 %) y docencia (77,5 %) concentran los casos. En medicina el porcentaje es apenas 9,9 %, a pesar de que la mitad de los médicos (46,5 %) duerme menos de 7 horas.
  • Aplicación en un hospital: el personal de enfermería es el grupo prioritario para un programa de higiene del sueño y para revisar el diseño de los turnos.

2.3 Distribuciones de probabilidad

Una distribución de probabilidad describe qué valores puede tomar una variable y qué tan frecuente es cada uno. Las discretas cuentan (número de trabajadores con trastorno) y las continuas miden (horas de sueño).

2.3.1 Distribuciones discretas

runif(): un servicio de 30 enfermeras. runif() genera números al azar entre 0 y 1. Si el número es menor que la probabilidad de trastorno en enfermería, esa enfermera “tiene trastorno”.

set.seed(2026)
p_enf <- mean(sueno$trastorno[sueno$ocupacion == "Enfermería"] == "Con trastorno")
p_med <- mean(sueno$trastorno[sueno$ocupacion == "Medicina"] == "Con trastorno")
p_enf
## [1] 0.8767123
p_med
## [1] 0.09859155
servicio <- ifelse(runif(30) < p_enf, "Con trastorno", "Sin trastorno")
table(servicio)
## servicio
## Con trastorno 
##            30

rbinom(): 1.000 servicios simulados. La distribución binomial cuenta cuántos “casos” aparecen en un número fijo de personas con la misma probabilidad. Se comparan servicios de 30 enfermeras con servicios de 30 médicos.

set.seed(2026)
sim_enf <- rbinom(1000, size = 30, prob = p_enf)
sim_med <- rbinom(1000, size = 30, prob = p_med)

table(sim_enf)
## sim_enf
##  20  21  22  23  24  25  26  27  28  29  30 
##   1   5  17  37 101 152 210 205 160  92  20
mean(sim_enf >= 25)   # P(25 o más enfermeras con trastorno en un servicio de 30)
## [1] 0.839
table(sim_med)
## sim_med
##   0   1   2   3   4   5   6   7   8  10 
##  27 150 250 244 165  95  39  23   6   1
mean(sim_med >= 5)    # P(5 o más médicos con trastorno en un grupo de 30)
## [1] 0.164

dpois(): positivos por día en el tamizaje. La distribución de Poisson describe cuántas veces ocurre algo en un periodo cuando se conoce el promedio (lambda). Si se tamizan 10 trabajadores al día, el promedio de positivos es 10 × p. La unidad de SST tiene 6 cupos diarios de valoración con medicina laboral.

lambda_pos <- 10 * p_trast
lambda_pos
## [1] 4.144385
positivos <- tibble(
  positivos_dia = 0:10,
  Poisson  = dpois(0:10, lambda = lambda_pos),
  Binomial = dbinom(0:10, size = 10, prob = p_trast)
)
round(positivos, 3)
## # A tibble: 11 × 3
##    positivos_dia Poisson Binomial
##            <dbl>   <dbl>    <dbl>
##  1             0   0.016    0.005
##  2             1   0.066    0.034
##  3             2   0.136    0.107
##  4             3   0.188    0.202
##  5             4   0.195    0.25 
##  6             5   0.162    0.212
##  7             6   0.112    0.125
##  8             7   0.066    0.051
##  9             8   0.034    0.013
## 10             9   0.016    0.002
## 11            10   0.007    0
1 - sum(dpois(0:6, lambda = lambda_pos))           # P(más de 6 positivos) según Poisson
## [1] 0.1262543
1 - sum(dbinom(0:6, size = 10, prob = p_trast))    # mismo cálculo con binomial
## [1] 0.0662868

Interpretación:

  • runif(): en el servicio simulado de 30 enfermeras, las 30 resultaron con trastorno. Con una probabilidad individual de 87,7 %, que casi todo el servicio esté afectado es un resultado esperable.
  • rbinom(): en el 83,9 % de los servicios simulados de enfermería, 25 o más de las 30 enfermeras tienen trastorno. En medicina lo más común son 2 o 3 casos por cada 30, y solo el 16,4 % de los grupos llega a 5 o más. La diferencia entre las dos profesiones del mismo hospital es enorme, así que las intervenciones deben dirigirse por profesión y no aplicarse igual a todos.
  • dpois() vs. dbinom(): en un día con 10 trabajadores tamizados se esperan 4,1 positivos. Según Poisson, en el 12,6 % de los días habría más de 6 positivos (más que los cupos). Según la binomial, solo en el 6,6 %. La diferencia se explica porque Poisson supone que no hay un límite de casos, y aquí nunca puede haber más de 10 positivos. Para planear los cupos, el modelo correcto es la binomial. La Poisson es más útil cuando se cuentan eventos sin un tope claro, como las consultas que llegan por semana.

2.3.2 Distribuciones continuas

rnorm(): horas de sueño. La distribución normal tiene forma de campana, simétrica alrededor del promedio. La referencia de salud pública para adultos es dormir 7 horas o más.

set.seed(2026)
m_sueno <- mean(sueno$horas_sueno)
s_sueno <- sd(sueno$horas_sueno)

sim_sueno <- rnorm(1000, mean = m_sueno, sd = s_sueno)

pnorm(7, mean = m_sueno, sd = s_sueno)   # P(dormir menos de 7 h) según la normal
## [1] 0.4340752
mean(sueno$horas_sueno < 7)              # realidad
## [1] 0.4144385
pnorm(6, mean = m_sueno, sd = s_sueno)   # P(dormir menos de 6 h) según la normal
## [1] 0.07739249
mean(sueno$horas_sueno < 6)              # realidad
## [1] 0.01604278

runif(): modelo uniforme de las horas de sueño. La distribución uniforme continua da la misma probabilidad a cualquier valor entre un mínimo y un máximo. Sirve para comparar: ¿qué pasa si se supone que cualquier duración entre 5,8 y 8,5 h es igual de probable?

set.seed(2026)
sim_unif <- runif(1000, min = min(sueno$horas_sueno), max = max(sueno$horas_sueno))
punif(7, min = min(sueno$horas_sueno), max = max(sueno$horas_sueno))   # P(< 7 h) según uniforme
## [1] 0.4444444
mean(sim_unif < 7)                                                      # en la simulación
## [1] 0.447

rexp(): espera para la polisomnografía (escenario supuesto). La distribución exponencial describe tiempos de espera: muchos cortos y pocos muy largos. La base no trae tiempos de espera, así que se supone un promedio de 30 días entre la remisión y el estudio. Cambia ese valor por el de la EPS o el prestador con que trabajes.

set.seed(2026)
espera_media <- 30   # días (supuesto)
sim_espera <- rexp(1000, rate = 1 / espera_media)

pexp(15, rate = 1 / espera_media)                       # P(esperar 15 días o menos)
## [1] 0.3934693
pexp(60, rate = 1 / espera_media, lower.tail = FALSE)   # P(esperar más de 60 días)
## [1] 0.1353353
mean(sim_espera > 60)                                   # en la simulación
## [1] 0.152

Interpretación:

  • Normal: el promedio es 7,13 h de sueño, con desviación de 0,80 h. La normal calcula que el 43,4 % duerme menos de 7 h, y en la realidad es el 41,4 %, así que la aproximación es buena. Para menos de 6 h la normal exagera: calcula 7,7 % y en la realidad solo el 1,6 % duerme tan poco. La normal funciona bien cerca del promedio, pero no en los extremos de esta base.
  • Uniforme: suponer que cualquier duración entre 5,8 y 8,5 h es igual de probable da 44,4 % de personas con menos de 7 h. Es una aproximación más tosca, porque ignora que la mayoría de valores se concentra alrededor del promedio.
  • Exponencial (escenario supuesto): con una espera promedio de 30 días, el 39,3 % de los trabajadores remitidos haría el estudio en 15 días o menos, pero el 13,5 % esperaría más de 2 meses (en la simulación, 15,2 %). Esa cola de esperas largas es la que genera quejas, ausentismo e incapacidades, y la que conviene negociar con la EPS o el prestador.

2.3.3 Visualización interactiva de las simulaciones

g_binom <- tibble(Enfermería = sim_enf, Medicina = sim_med) %>%
  pivot_longer(everything(), names_to = "Grupo", values_to = "casos") %>%
  ggplot(aes(x = casos, fill = Grupo)) +
  geom_bar(position = "identity", alpha = 0.7) +
  labs(title = "Trabajadores con trastorno en grupos de 30 (1.000 simulaciones)",
       x = "Número de trabajadores con trastorno", y = "Simulaciones") +
  theme_minimal()
ggplotly(g_binom)

Lectura del gráfico: las dos distribuciones no se cruzan. La de enfermería se concentra entre 24 y 29 casos por cada 30 enfermeras, y la de medicina entre 1 y 4. Al pasar el mouse se ve cuántas simulaciones tuvo cada valor.

g_pois <- positivos %>%
  pivot_longer(c(Poisson, Binomial), names_to = "Modelo", values_to = "Probabilidad") %>%
  ggplot(aes(x = positivos_dia, y = Probabilidad, fill = Modelo)) +
  geom_col(position = "dodge") +
  geom_vline(xintercept = 6.5, linetype = "dashed") +
  labs(title = "Positivos por día al tamizar 10 trabajadores (línea: 6 cupos)",
       x = "Positivos en el día", y = "Probabilidad") +
  theme_minimal()
ggplotly(g_pois)

Lectura del gráfico: la binomial se concentra entre 3 y 5 positivos y cae rápido. La Poisson es más ancha y asigna probabilidad a valores que en la práctica son raros. La línea punteada marca el límite de 6 cupos: lo que queda a la derecha de la línea son los días en que faltarían cupos.

g_normal <- ggplot(sueno, aes(x = horas_sueno)) +
  geom_histogram(aes(y = after_stat(density)), binwidth = 0.2, fill = "grey70") +
  stat_function(fun = dnorm, args = list(mean = m_sueno, sd = s_sueno),
                color = "red", linewidth = 1) +
  stat_function(fun = dunif, args = list(min = min(sueno$horas_sueno), max = max(sueno$horas_sueno)),
                color = "blue", linewidth = 1, linetype = "dashed") +
  geom_vline(xintercept = 7, linetype = "dotted") +
  labs(title = "Horas de sueño reales vs. normal (rojo) y uniforme (azul)",
       x = "Horas de sueño", y = "Densidad") +
  theme_minimal()
ggplotly(g_normal)

Lectura del gráfico: las horas de sueño reales no forman una campana perfecta. Tienen varios picos (los valores más frecuentes son 7,2 h, 6,0 h y 7,8 h), porque hay grupos de trabajadores con hábitos parecidos. La curva normal (roja) sigue la forma general y la uniforme (azul) es plana. La línea punteada en 7 h separa a quienes no cumplen la recomendación.

g_exp <- tibble(dias = sim_espera) %>%
  ggplot(aes(x = dias)) +
  geom_histogram(aes(y = after_stat(density)), binwidth = 5, fill = "grey70") +
  stat_function(fun = dexp, args = list(rate = 1 / espera_media),
                color = "red", linewidth = 1) +
  labs(title = "Espera simulada para polisomnografía (promedio supuesto: 30 días)",
       x = "Días de espera", y = "Densidad") +
  theme_minimal()
ggplotly(g_exp)

Lectura del gráfico: la mayoría de las esperas simuladas son cortas, pero la cola se extiende más allá de 100 días. Por eso un promedio de 30 días puede esconder casos de varios meses de espera.

2.4 Estadística descriptiva

2.4.1 Medidas de tendencia central y dispersión

La media es el promedio. La mediana es el valor del medio (no se deja afectar por extremos). La desviación estándar mide qué tanto se alejan los datos del promedio, en las mismas unidades. La varianza es la desviación estándar al cuadrado. El rango muestra el mínimo y el máximo.

medidas <- function(x) {
  c(Media = mean(x), Mediana = median(x), Desv_estandar = sd(x),
    Varianza = var(x), Minimo = range(x)[1], Maximo = range(x)[2])
}

indicadores_sueno <- rbind(
  "Horas de sueño"              = medidas(sueno$horas_sueno),
  "Calidad del sueño (1-10)"    = medidas(sueno$calidad_sueno),
  "Estrés (1-10)"               = medidas(sueno$estres),
  "Actividad física (min/día)"  = medidas(sueno$actividad_fisica),
  "Pasos diarios"               = medidas(sueno$pasos),
  "Frecuencia cardíaca (lpm)"   = medidas(sueno$frec_cardiaca),
  "Presión sistólica (mmHg)"    = medidas(sueno$pas),
  "Presión diastólica (mmHg)"   = medidas(sueno$pad)
)
round(indicadores_sueno, 2)
##                              Media Mediana Desv_estandar   Varianza Minimo
## Horas de sueño                7.13     7.2          0.80       0.63    5.8
## Calidad del sueño (1-10)      7.31     7.0          1.20       1.43    4.0
## Estrés (1-10)                 5.39     5.0          1.77       3.15    3.0
## Actividad física (min/día)   59.17    60.0         20.83     433.92   30.0
## Pasos diarios              6816.84  7000.0       1617.92 2617651.14 3000.0
## Frecuencia cardíaca (lpm)    70.17    70.0          4.14      17.10   65.0
## Presión sistólica (mmHg)    128.55   130.0          7.75      60.03  115.0
## Presión diastólica (mmHg)    84.65    85.0          6.16      37.97   75.0
##                             Maximo
## Horas de sueño                 8.5
## Calidad del sueño (1-10)       9.0
## Estrés (1-10)                  8.0
## Actividad física (min/día)    90.0
## Pasos diarios              10000.0
## Frecuencia cardíaca (lpm)     86.0
## Presión sistólica (mmHg)     142.0
## Presión diastólica (mmHg)     95.0
# Indicadores por ocupación
sueno_ocup %>%
  group_by(ocupacion) %>%
  summarise(n = n(),
            Horas_sueno = mean(horas_sueno),
            Calidad = mean(calidad_sueno),
            Estres = mean(estres),
            Pct_trastorno = mean(trastorno == "Con trastorno") * 100) %>%
  arrange(Horas_sueno) %>%
  mutate(across(where(is.numeric), ~ round(.x, 1)))
## # A tibble: 7 × 6
##   ocupacion      n Horas_sueno Calidad Estres Pct_trastorno
##   <chr>      <dbl>       <dbl>   <dbl>  <dbl>         <dbl>
## 1 Ventas        32         6.4     6      7            93.8
## 2 Docencia      40         6.7     7      4.5          77.5
## 3 Medicina      71         7       6.6    6.7           9.9
## 4 Enfermería    73         7.1     7.4    5.5          87.7
## 5 Contaduría    37         7.1     7.9    4.6          18.9
## 6 Derecho       47         7.4     7.9    5.1          10.6
## 7 Ingeniería    63         8       8.4    3.9           9.5

Interpretación:

  • Sueño: en promedio 7,13 h, justo por encima de la referencia de 7 h. La calidad del sueño es 7,3 sobre 10 y el estrés 5,4 sobre 10.
  • Presión arterial: el promedio es 128,6/84,7 mmHg. La diastólica promedio ya supera 80 mmHg, umbral de hipertensión etapa 1 en la guía estadounidense ACC/AHA de 2017, lo que justifica incluir la toma de presión en el tamizaje ocupacional.
  • Actividad física y pasos: son las variables con mayor dispersión. Los pasos van de 3.000 a 10.000 por día, así que los hábitos de los trabajadores son muy distintos.
  • Por ocupación: ventas y docencia duermen menos de 7 h en promedio (6,4 y 6,7 h). Ingeniería duerme 8 h y tiene el menor estrés (3,9). Medicina tiene el estrés más alto entre las profesiones de salud (6,7) y la menor calidad de sueño (6,6), aunque pocos médicos tienen trastorno diagnosticado. En enfermería el sueño y el estrés son intermedios, pero tiene la proporción de trastorno más alta del sector salud.

2.4.2 Resumen general con summary()

summary(sueno %>% select(edad, horas_sueno, calidad_sueno, estres, actividad_fisica,
                         pasos, frec_cardiaca, pas, pad, imc, trastorno))
##       edad        horas_sueno    calidad_sueno       estres     
##  Min.   :27.00   Min.   :5.800   Min.   :4.000   Min.   :3.000  
##  1st Qu.:35.25   1st Qu.:6.400   1st Qu.:6.000   1st Qu.:4.000  
##  Median :43.00   Median :7.200   Median :7.000   Median :5.000  
##  Mean   :42.18   Mean   :7.132   Mean   :7.313   Mean   :5.385  
##  3rd Qu.:50.00   3rd Qu.:7.800   3rd Qu.:8.000   3rd Qu.:7.000  
##  Max.   :59.00   Max.   :8.500   Max.   :9.000   Max.   :8.000  
##  actividad_fisica     pasos       frec_cardiaca        pas       
##  Min.   :30.00    Min.   : 3000   Min.   :65.00   Min.   :115.0  
##  1st Qu.:45.00    1st Qu.: 5600   1st Qu.:68.00   1st Qu.:125.0  
##  Median :60.00    Median : 7000   Median :70.00   Median :130.0  
##  Mean   :59.17    Mean   : 6817   Mean   :70.17   Mean   :128.6  
##  3rd Qu.:75.00    3rd Qu.: 8000   3rd Qu.:72.00   3rd Qu.:135.0  
##  Max.   :90.00    Max.   :10000   Max.   :86.00   Max.   :142.0  
##       pad               imc              trastorno  
##  Min.   :75.00   Normal   :216   Sin trastorno:219  
##  1st Qu.:80.00   Sobrepeso:148   Con trastorno:155  
##  Median :85.00   Obesidad : 10                      
##  Mean   :84.65                                      
##  3rd Qu.:90.00                                      
##  Max.   :95.00

Interpretación: summary() muestra en una sola tabla el mínimo, los cuartiles (valores que dividen los datos ordenados en cuatro partes iguales), la mediana, la media y el máximo de cada variable. Para las variables categóricas muestra el conteo de cada categoría. Algunos datos para destacar:

  • El 25 % de los trabajadores duerme 6,4 h o menos.
  • La mitad tiene presión sistólica de 130 mmHg o más.
  • La edad va de 27 a 59 años, así que es una población en edad laboral.

2.5 Estadística inferencial

La estadística inferencial verifica si una diferencia observada es real o pudo deberse al azar. Se plantea una hipótesis nula (H0: “no hay diferencia”) y se calcula el valor p (probabilidad de ver una diferencia así si H0 fuera cierta). Si p < 0,05, se rechaza H0 y la diferencia se considera estadísticamente significativa.

2.5.1 Prueba t e intervalos de confianza

Un intervalo de confianza del 95 % es el rango donde, con 95 % de confianza, está el verdadero promedio de la población.

# 1. ¿Los trabajadores duermen, en promedio, las 7 h recomendadas?
t.test(sueno$horas_sueno, mu = 7)
## 
##  One Sample t-test
## 
## data:  sueno$horas_sueno
## t = 3.2104, df = 373, p-value = 0.00144
## alternative hypothesis: true mean is not equal to 7
## 95 percent confidence interval:
##  7.051185 7.212986
## sample estimates:
## mean of x 
##  7.132086
# 2. ¿Duermen lo mismo mujeres y hombres?
t.test(horas_sueno ~ sexo, data = sueno)
## 
##  Welch Two Sample t-test
## 
## data:  horas_sueno by sexo
## t = -2.3565, df = 349.38, p-value = 0.019
## alternative hypothesis: true difference in means between group Hombre and group Mujer is not equal to 0
## 95 percent confidence interval:
##  -0.35448564 -0.03195795
## sample estimates:
## mean in group Hombre  mean in group Mujer 
##             7.036508             7.229730
# 3. ¿La calidad del sueño es menor en quienes tienen trastorno?
t.test(calidad_sueno ~ trastorno, data = sueno)
## 
##  Welch Two Sample t-test
## 
## data:  calidad_sueno by trastorno
## t = 5.9881, df = 265.18, p-value = 6.878e-09
## alternative hypothesis: true difference in means between group Sin trastorno and group Con trastorno is not equal to 0
## 95 percent confidence interval:
##  0.5064815 1.0027245
## sample estimates:
## mean in group Sin trastorno mean in group Con trastorno 
##                    7.625571                    6.870968

Interpretación:

  1. Meta de 7 h: el promedio es 7,13 h, con intervalo de confianza 95 % entre 7,05 y 7,21 h, y p = 0,001. En promedio la población sí supera las 7 h. Aun así, el 41,4 % de los trabajadores está por debajo. El promedio no muestra a todo el grupo en riesgo, y por eso en gestión se debe reportar también la proporción.
  2. Mujeres vs. hombres: las mujeres duermen 7,23 h y los hombres 7,04 h (p = 0,019). La diferencia es significativa, pero pequeña: entre 2 y 21 minutos al día.
  3. Calidad según trastorno: quienes tienen trastorno califican su sueño 0,75 puntos más bajo (6,9 frente a 7,6; p < 0,001). Esto confirma que la calidad autorreportada es un buen indicador sencillo para detectar posibles casos en una encuesta de bienestar.

2.5.2 Análisis de varianza (ANOVA)

El ANOVA compara los promedios de tres o más grupos al mismo tiempo. Si sale significativo, la prueba de Tukey (comparación de los grupos de dos en dos) indica cuáles grupos son distintos.

# 1. ¿Las horas de sueño cambian según la ocupación?
anova_ocup <- aov(horas_sueno ~ ocupacion, data = sueno_ocup)
summary(anova_ocup)
##              Df Sum Sq Mean Sq F value Pr(>F)    
## ocupacion     6  76.55  12.759   30.66 <2e-16 ***
## Residuals   356 148.14   0.416                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
tukey <- TukeyHSD(anova_ocup)$ocupacion
round(tukey[grepl("Enfermería|Medicina", rownames(tukey)), ], 3)
##                         diff    lwr    upr p adj
## Enfermería-Contaduría -0.050 -0.437  0.336 1.000
## Medicina-Contaduría   -0.143 -0.531  0.245 0.930
## Enfermería-Derecho    -0.348 -0.705  0.010 0.063
## Medicina-Derecho      -0.440 -0.800 -0.081 0.006
## Enfermería-Docencia    0.373 -0.003  0.749 0.054
## Medicina-Docencia      0.280 -0.098  0.659 0.299
## Ingeniería-Enfermería  0.924  0.595  1.253 0.000
## Medicina-Enfermería   -0.093 -0.411  0.226 0.978
## Ventas-Enfermería     -0.660 -1.065 -0.254 0.000
## Medicina-Ingeniería   -1.017 -1.348 -0.686 0.000
## Ventas-Medicina       -0.567 -0.975 -0.160 0.001
# 2. ¿La presión sistólica cambia según el IMC?
anova_imc <- aov(pas ~ imc, data = sueno)
summary(anova_imc)
##              Df Sum Sq Mean Sq F value Pr(>F)    
## imc           2  12674    6337   241.9 <2e-16 ***
## Residuals   371   9719      26                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
tapply(sueno$pas, sueno$imc, mean)
##    Normal Sobrepeso  Obesidad 
##  123.6065  135.0541  139.2000

Interpretación:

  1. Sueño por ocupación: F = 30,7 y p < 0,001, así que no todas las ocupaciones duermen lo mismo.
    • Según Tukey, enfermería duerme en promedio 0,92 h (55 minutos) menos que ingeniería.
    • Medicina duerme 1,02 h menos que ingeniería y 0,44 h menos que derecho.
    • Entre enfermería y medicina no hay diferencia (p = 0,98): las dos profesiones de salud duermen parecido.
    • Ventas duerme menos que ambas.
  2. Presión sistólica por IMC: F = 241,9 y p < 0,001. La presión sube con el peso: 123,6 mmHg en peso normal, 135,1 en sobrepeso y 139,2 en obesidad. Los programas de control de peso del área de SST tienen un efecto esperado sobre el riesgo cardiovascular.

2.5.3 Prueba chi-cuadrado de asociación

La prueba chi-cuadrado revisa si dos variables categóricas están relacionadas (H0: son independientes).

# Obesidad tiene solo 10 personas: se agrupa con sobrepeso para que la prueba sea válida
sueno <- sueno %>%
  mutate(peso = factor(ifelse(imc == "Normal", "Peso normal", "Exceso de peso"),
                       levels = c("Peso normal", "Exceso de peso")))

tabla_peso <- table(sueno$peso, sueno$trastorno)
tabla_peso
##                 
##                  Sin trastorno Con trastorno
##   Peso normal              200            16
##   Exceso de peso            19           139
chisq.test(tabla_peso)
## 
##  Pearson's Chi-squared test with Yates' continuity correction
## 
## data:  tabla_peso
## X-squared = 240.77, df = 1, p-value < 2.2e-16

Interpretación: X² = 240,8 y p < 0,001. El exceso de peso y el trastorno del sueño están fuertemente asociados: 139 de 158 trabajadores con exceso de peso (88 %) tienen trastorno, frente a 16 de 216 con peso normal (7 %). Se agruparon sobrepeso y obesidad porque, con solo 10 personas en obesidad, la prueba chi-cuadrado no es confiable (R muestra una advertencia). Para la gestión, el IMC es un criterio sencillo y barato para priorizar a quién remitir a estudio del sueño.

2.5.4 Comparación de proporciones

# 1. ¿La proporción de trastorno es diferente entre mujeres y hombres?
tab_sexo <- table(sueno$sexo, sueno$trastorno)
tab_sexo
##         
##          Sin trastorno Con trastorno
##   Hombre           137            52
##   Mujer             82           103
prop.test(x = tab_sexo[, "Con trastorno"], n = rowSums(tab_sexo))
## 
##  2-sample test for equality of proportions with continuity correction
## 
## data:  tab_sexo[, "Con trastorno"] out of rowSums(tab_sexo)
## X-squared = 29.405, df = 1, p-value = 5.874e-08
## alternative hypothesis: two.sided
## 95 percent confidence interval:
##  -0.3827736 -0.1804754
## sample estimates:
##    prop 1    prop 2 
## 0.2751323 0.5567568
# 2. ¿Quienes tienen estrés alto presentan más trastornos del sueño?
tab_estres <- table(sueno$estres_nivel, sueno$trastorno)
tab_estres
##                     
##                      Sin trastorno Con trastorno
##   Alto (7-8)                    36            84
##   Bajo o medio (3-6)           183            71
prop.test(x = tab_estres[, "Con trastorno"], n = rowSums(tab_estres))
## 
##  2-sample test for equality of proportions with continuity correction
## 
## data:  tab_estres[, "Con trastorno"] out of rowSums(tab_estres)
## X-squared = 57.653, df = 1, p-value = 3.128e-14
## alternative hypothesis: two.sided
## 95 percent confidence interval:
##  0.3155022 0.5254427
## sample estimates:
##    prop 1    prop 2 
## 0.7000000 0.2795276

Interpretación:

  1. Sexo: el 55,7 % de las mujeres tiene trastorno, frente al 27,5 % de los hombres (p < 0,001). La diferencia está entre 18 y 38 puntos porcentuales. Ojo: en esta base las 73 personas de enfermería son mujeres y 69 de los 71 médicos son hombres. La diferencia por sexo puede deberse a la ocupación y no al sexo en sí. Por eso, antes de diseñar una intervención “para mujeres”, conviene revisar el resultado por profesión.
  2. Estrés: el 70 % de quienes tienen estrés alto (7-8) presenta trastorno, frente al 28 % de quienes tienen estrés bajo o medio (p < 0,001). El manejo del estrés (pausas activas, rotación de turnos, apoyo psicosocial) es una línea de acción con respaldo en los datos.

2.6 Gráficos comparativos con ggpubr

ggpubr hace gráficos comparativos que incluyen en la misma imagen el resultado de la prueba estadística.

library(ggpubr)

ggboxplot(sueno_ocup, x = "ocupacion", y = "horas_sueno", fill = "ocupacion",
          palette = "Set2", legend = "none",
          xlab = "Ocupación", ylab = "Horas de sueño",
          title = "Horas de sueño por ocupación",
          order = c("Ventas", "Docencia", "Medicina", "Enfermería",
                    "Contaduría", "Derecho", "Ingeniería")) +
  geom_hline(yintercept = 7, linetype = "dashed", color = "red") +
  stat_compare_means(method = "anova", label.y = 8.8)

Lectura del gráfico: la línea roja marca 7 h. Las cajas de ventas y docencia quedan casi completas por debajo de la línea. Enfermería tiene la mediana en 6,5 h: la mitad de las enfermeras duerme menos de 7 h. Medicina tiene la mediana en 7,6 h, pero su caja es muy amplia porque hay un grupo de médicos que duerme alrededor de 6 h. Las dos profesiones de salud son las que muestran mayor diferencia entre su propio personal. Ingeniería queda totalmente por encima. El valor p del ANOVA aparece en el gráfico.

ggboxplot(sueno, x = "trastorno", y = "calidad_sueno", fill = "trastorno",
          palette = c("#4E79A7", "#E15759"), legend = "none",
          xlab = "", ylab = "Calidad del sueño (1-10)",
          title = "Calidad del sueño según presencia de trastorno") +
  stat_compare_means(method = "t.test", label.y = 9.8)

Lectura del gráfico: la caja de quienes tienen trastorno está más abajo y es más ancha. Tienen peor calidad de sueño y más variable. El valor p de la prueba t aparece en el gráfico.

prop_grupos <- bind_rows(
  sueno %>% group_by(Grupo = sexo) %>% summarise(Pct = mean(trastorno == "Con trastorno") * 100),
  sueno %>% group_by(Grupo = estres_nivel) %>% summarise(Pct = mean(trastorno == "Con trastorno") * 100),
  sueno %>% group_by(Grupo = peso) %>% summarise(Pct = mean(trastorno == "Con trastorno") * 100)
) %>%
  mutate(Factor = rep(c("Sexo", "Estrés", "Peso"), each = 2),
         Pct = round(Pct, 1))

ggbarplot(prop_grupos, x = "Grupo", y = "Pct", fill = "Factor",
          palette = c("#59A14F", "#F28E2B", "#4E79A7"),
          label = TRUE, facet.by = "Factor", scales = "free_x", legend = "none",
          xlab = "", ylab = "% con trastorno del sueño", x.text.angle = 20,
          title = "Porcentaje de trabajadores con trastorno del sueño por grupo") +
  coord_cartesian(ylim = c(0, 100))

Lectura del gráfico: de los tres factores, el peso muestra la diferencia más grande (7 % frente a 88 %), seguido por el estrés (28 % frente a 70 %) y el sexo (28 % frente a 56 %). Es el gráfico más útil para presentar a la gerencia: con el peso y el estrés se puede priorizar el programa de sueño.