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).
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
## 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
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!"
Antes de limpiar, debemos conocer a nuestro “paciente”. En estadística, esto significa auditar la estructura de nuestros datos.
## '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?
Construiremos nuestro algoritmo de limpieza usando el operador “pipe” (%>%). El pipe se lee como “y entonces…”, permitiéndonos encadenar decisiones ló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
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
##
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.