Introducción a la Sesión

Bienvenidos a nuestro laboratorio interactivo de Bioestadística. Como futuros profesionales de la salud, deben recordar que la curación de datos no es una simple manipulación informática, sino una profunda responsabilidad ética. Un error de digitación en un expediente clínico o en una base de datos epidemiológica puede distorsionar nuestras políticas de salud pública o alterar el diagnóstico de un paciente.

En esta sesión, combinaremos conceptos básicos de R con la limpieza de una base de datos simulada. Esta base integra variables clave de sus propios protocolos de investigación (uso de redes sociales nocturnas, calidad del sueño y niveles de estrés).


1. Conceptos Básicos: Nuestro Entorno RStudio

Ya que acaban de instalar R y RStudio, familiaricémonos con cómo nos comunicamos con este entorno. R funciona mediante la creación de objetos y el uso de funciones.

# 1. Asignación: Usamos la flecha (<-) para guardar información en un objeto.
mi_edad <- 20

# 2. Vectores: Colecciones de datos del mismo tipo, creados con la función c()
edades_grupo <- c(19, 21, 20, 22, 18)

# 3. Funciones básicas:
mean(edades_grupo) # Promedio de las edades
## [1] 20

Para el análisis de datos en salud, utilizamos un conjunto de paquetes llamado tidyverse, que nos permite leer el código de forma lógica y humana.

# Instalamos y Cargamos la librería principal para nuestro flujo metodológico
installed.packages("tidyverse")
##      Package LibPath Version Priority Depends Imports LinkingTo Suggests
##      Enhances License License_is_FOSS License_restricts_use OS_type Archs
##      MD5sum NeedsCompilation Built
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.5.3
## Warning: package 'tibble' was built under R version 4.5.3
## Warning: package 'tidyr' was built under R version 4.5.3
## Warning: package 'purrr' was built under R version 4.5.3
## Warning: package 'dplyr' was built under R version 4.5.3
## Warning: package 'stringr' was built under R version 4.5.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.1          ✔ readr     2.1.5     
## ✔ forcats   1.0.0          ✔ stringr   1.6.0     
## ✔ ggplot2   4.0.3.9000     ✔ tibble    3.3.1     
## ✔ lubridate 1.9.4          ✔ tidyr     1.3.2     
## ✔ purrr     1.2.2          
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

2. Creación de nuestra Base de Datos Ficticia

Dado que sus formularios digitales están listos pero en fase de recolección, hoy trabajaremos con una muestra clínica simulada. Esta base hipotética evalúa la asociación entre el estrés académico, el uso de redes sociales y la calidad del sueño.

Ejecuten el siguiente bloque para generar los datos crudos. Hemos introducido intencionalmente errores humanos comunes en la digitación (nuestro “dato sucio”).

# Fijamos una semilla para que todos obtengamos los mismos datos simulados
set.seed(2026)

# Simulamos respuestas de 50 estudiantes de medicina
datos_crudos <- data.frame(
  id_paciente = 1:50,
  # Introducimos edades imposibles (199, -5) como errores de digitación
  edad = sample(c(18:25, 199, -5), 50, replace = TRUE), 
  # Errores de estandarización en la variable categórica
  sexo = sample(c("Masculino", "Femenino", "Masc", "F", "Mujer"), 50, replace = TRUE),
  # Nivel de estrés académico (1-10)
  nivel_estres = sample(1:10, 50, replace = TRUE),
  # Horas de sueño con valores irreales (25) y datos perdidos (NA)
  horas_sueno = sample(c(3:9, 25, NA), 50, replace = TRUE),
  # Frecuencia de uso nocturno de redes sociales (22:00 - 06:00)
  uso_redes_noche = sample(c("Nunca", "A veces", "Frecuentemente", "Siempre", "no se"), 50, replace = TRUE)
)

print("¡Base de datos cruda generada con éxito!")
## [1] "¡Base de datos cruda generada con éxito!"

3. Auditoría Estructural: Inspección del Dato

Antes de limpiar, debemos conocer a nuestro “paciente”. En estadística, esto significa auditar la estructura de nuestros datos.

# str() nos permite ver la estructura interna (tipos de datos)
str(datos_crudos)
## 'data.frame':    50 obs. of  6 variables:
##  $ id_paciente    : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ edad           : num  199 18 23 21 22 -5 19 25 20 18 ...
##  $ sexo           : chr  "Mujer" "Masc" "Masculino" "F" ...
##  $ nivel_estres   : int  8 9 3 10 2 1 2 10 10 8 ...
##  $ horas_sueno    : num  8 8 3 7 8 6 8 4 4 4 ...
##  $ uso_redes_noche: chr  "no se" "Frecuentemente" "Nunca" "A veces" ...
# summary() nos da estadísticas descriptivas basales. 
# ¡Noten los valores máximos y mínimos imposibles en edad y horas de sueño!
summary(datos_crudos)
##   id_paciente         edad            sexo            nivel_estres  
##  Min.   : 1.00   Min.   : -5.00   Length:50          Min.   : 1.00  
##  1st Qu.:13.25   1st Qu.: 19.00   Class :character   1st Qu.: 3.00  
##  Median :25.50   Median : 21.50   Mode  :character   Median : 5.00  
##  Mean   :25.50   Mean   : 48.10                      Mean   : 5.14  
##  3rd Qu.:37.75   3rd Qu.: 24.75                      3rd Qu.: 7.00  
##  Max.   :50.00   Max.   :199.00                      Max.   :10.00  
##                                                                     
##   horas_sueno    uso_redes_noche   
##  Min.   : 3.00   Length:50         
##  1st Qu.: 4.75   Class :character  
##  Median : 7.00   Mode  :character  
##  Mean   : 8.75                     
##  3rd Qu.: 9.00                     
##  Max.   :25.00                     
##  NA's   :6

Reflexión en subgrupos: ¿Qué impacto clínico o estadístico tendría calcular un promedio de edad si incluimos a un paciente de 199 años o de -5 años?

4. Laboratorio de “Curación” de Datos

Construiremos nuestro algoritmo de limpieza usando el operador “pipe” (%>%). El pipe se lee como “y entonces…”, permitiéndonos encadenar decisiones lógicas.

4.1 Filtrado de Inconsistencias Biológicas

Usaremos la función filter() para quedarnos únicamente con los registros que tienen sentido fisiológico y lógico.

datos_filtrados <- datos_crudos %>%
  # La edad de un estudiante universitario debe ser lógica
  filter(edad >= 18 & edad <= 65) %>%
  # Nadie puede dormir más de 24 horas al día
  filter(horas_sueno <= 24)

# Revisamos cuántos registros perdimos por ser aberrantes
nrow(datos_crudos) - nrow(datos_filtrados)
## [1] 20

4.2 Estandarización y Transformación a Factores

La plataforma RStudio trata el texto libre como simples “caracteres”. Para el rigor metodológico, debemos convertirlos en variables cualitativas estructuradas (factores). Usaremos mutate() para transformar las columnas y case_when() para unificar el texto.

datos_limpios <- datos_filtrados %>%
  mutate(
    # 1. Estandarizamos la variable sexo
    sexo_limpio = case_when(
      sexo %in% c("Masculino", "Masc") ~ "Masculino",
      sexo %in% c("Femenino", "F", "Mujer") ~ "Femenino",
      TRUE ~ sexo
    ),
    # Lo convertimos formalmente a factor
    sexo_limpio = as.factor(sexo_limpio),
    
    # 2. Curamos la variable de redes sociales
    # Convertimos la respuesta "no se" en un dato perdido oficial (NA)
    uso_redes_noche = na_if(uso_redes_noche, "no se"),
    # Asignamos orden lógico (ordinal) a las categorías
    uso_redes_noche = factor(uso_redes_noche, 
                             levels = c("Nunca", "A veces", "Frecuentemente", "Siempre"),
                             ordered = TRUE)
  ) %>%
  # Seleccionamos solo las columnas curadas para nuestro análisis final
  select(id_paciente, edad, sexo_limpio, nivel_estres, horas_sueno, uso_redes_noche)

# Verificamos la limpieza final
summary(datos_limpios)
##   id_paciente         edad          sexo_limpio  nivel_estres     horas_sueno 
##  Min.   : 2.00   Min.   :18.00   Femenino :17   Min.   : 1.000   Min.   :3.0  
##  1st Qu.:10.75   1st Qu.:19.00   Masculino:13   1st Qu.: 3.000   1st Qu.:4.0  
##  Median :26.00   Median :21.00                  Median : 6.000   Median :6.5  
##  Mean   :24.83   Mean   :20.97                  Mean   : 5.467   Mean   :6.1  
##  3rd Qu.:35.75   3rd Qu.:22.75                  3rd Qu.: 8.000   3rd Qu.:8.0  
##  Max.   :49.00   Max.   :25.00                  Max.   :10.000   Max.   :9.0  
##        uso_redes_noche
##  Nunca         :7     
##  A veces       :8     
##  Frecuentemente:7     
##  Siempre       :3     
##  NA's          :5     
## 

5. Consolidación y Exportación Ética

Nuestra base de datos ahora es metodológicamente robusta. El último paso es exportar este producto depurado, documentando nuestras decisiones para garantizar la reproducibilidad de la investigación.

# Guardamos la base de datos limpia en nuestro entorno de trabajo
write.csv(datos_limpios, "datos_clinicos_limpios.csv", row.names = FALSE)

¡Excelente trabajo! Han transformado una base de datos vulnerable a sesgos en un insumo estadístico confiable. Rendericen este documento (botón Knit) para asegurar que su árbol de decisiones algorítmicas se ejecute sin errores.