Antes de escribir código, organizaremos nuestro entorno de trabajo.
La “curación” de datos no es una simple manipulación informática, sino una profunda responsabilidad ética y científica. Un error de digitación en un expediente clínico o en una base de datos epidemiológica nacional puede alterar directamente un diagnóstico o distorsionar las políticas de salud pública.
Hoy usaremos herramientas básicas y del paquete
tidyverse para generar bases de datos analíticas limpias y
robustas a partir de datos crudos.
# Comentarios: el símbolo (#) se utiliza para hacer anotaciones. R ignorará el texto a la derecha del # al correr el código.
# Operaciones Aritméticas Básicas:
2 + 2 ## [1] 4
## [1] 5
## [1] 12
## [1] 5
## [1] 8
3.2. Asignación y Nombres de Objetos R funciona mediante la creación de objetos. Usamos el operador <- para guardar información.
Un vector guarda conjuntos de datos del mismo tipo en un solo objeto. Se crean usando c() o secuencias.
# Las operaciones se aplican a cada elemento del vector:
edades_mas_dos <- edades + 2
# Funciones Descriptivas Básicas:
mean(edades)## [1] 20
## [1] 18
## [1] 22
# Manejo de NA (Not Available/Dato perdido)
# Muchos pacientes omiten respuestas en encuestas clínicas. R los representa como NA.
edades_con_na <- c(20, 22, NA, 19, 21)
# Calcular promedios con NA retorna NA por defecto. Hay que indicarle a R que excluya los NA.
mean(edades_con_na, na.rm = TRUE)## [1] 20.5
Instalaremos y cargaremos la librería principal para nuestro trabajo.
## 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
Para este tutorial, simularemos una base de datos de pacientes que combina variables de sus proyectos, tales como calidad de sueño, estrés académico, y uso nocturno de redes sociales. El objetivo final es tener datos “Tidy” (cada variable es una columna, y cada fila un paciente). Esta matriz se la conocerá como “Formato Ancho”, a diferencia del “Formato Largo”, que usualmente utilizamos para realizar análisis de datos temporales (pivot_longer() y pivot_wider()).
set.seed(2026)
datos_crudos <- data.frame(
id_paciente = 1:50,
edad = sample(c(18:25, 199, -5), 50, replace = TRUE), # Inconsistencias
sexo_bio = sample(c("Masculino", "Femenino", "Masc", "F", "Mujer"), 50, replace = TRUE), # Errores de estandarización
nivel_estres = sample(c(1:10, 55), 50, replace = TRUE), # Valor aberrante
horas_sueno = sample(c(3:9, 25, NA), 50, replace = TRUE),
uso_redes_noche = sample(c("Menos de 1 hora", "1-2 horas", "3-4 horas", "Más de 6 horas", "no se"), 50, replace = TRUE)
)
# Revisamos estadísticas básicas:
summary(datos_crudos)## id_paciente edad sexo_bio 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 : 6.00
## Mean :25.50 Mean : 48.10 Mean :10.34
## 3rd Qu.:37.75 3rd Qu.: 24.75 3rd Qu.: 8.00
## Max. :50.00 Max. :199.00 Max. :55.00
##
## horas_sueno uso_redes_noche
## Min. : 3.000 Length:50
## 1st Qu.: 4.000 Class :character
## Median : 7.000 Mode :character
## Mean : 8.222
## 3rd Qu.: 8.000
## Max. :25.000
## NA's :5
El operador pipe |> (o %>%) conecta instrucciones, pasando el resultado del bloque de código anterior como el primer argumento del siguiente bloque.
Construiremos un algoritmo de curación paso a paso. En este bloque usaremos:
filter() para eliminar datos imposibles (edades
ilógicas, niveles de estrés fuera de rango y horas de sueño
aberrantes).
rename() para estandarizar el nombre de las
variables.
mutate() y case_when() para corregir
valores categóricos de sexo biológico.
Convertir la variable ordinal de uso de redes sociales a formato categórico.
Convertir respuestas “no se” a NA.
select() para ordenar y guardar únicamente las columnas
validadas.
datos_limpios <- datos_crudos |>
# 1. filter(): Identificación y remoción de inconsistencias biológicas y valores aberrantes.
filter(edad >= 18 & edad <= 65) |>
filter(nivel_estres <= 10) |>
filter(horas_sueno <= 24 | is.na(horas_sueno)) |> # Omitimos el valor 25 pero preservamos pacientes sin registros.
# 2. rename(): Renombramos la variable original sexo_bio a un formato más sencillo
rename(sexo = sexo_bio) |>
# 3. mutate() y case_when(): Estandarización de categorías con múltiples errores de escritura
mutate(
sexo_limpio = case_when(
sexo %in% c("Masculino", "Masc") ~ "Masculino",
sexo %in% c("Femenino", "F", "Mujer") ~ "Femenino",
TRUE ~ sexo
),
sexo_limpio = as.factor(sexo_limpio), # Transformamos a variables cualitativas estructuradas (factor)
# 4. Datos perdidos y categorización ordinal de redes sociales
uso_redes_noche = na_if(uso_redes_noche, "no se"),
uso_redes_noche = factor(uso_redes_noche,
levels = c("Menos de 1 hora", "1-2 horas", "3-4 horas", "Más de 6 horas"),
ordered = TRUE)
) |>
# 5. Arrange() para ordenarlos de mayor a menor edad y agrupar con group_by()
arrange(desc(edad)) |>
# 6. Mantenemos la base de datos limpia.
select(id_paciente, edad, sexo_limpio, nivel_estres, horas_sueno, uso_redes_noche)
summary(datos_limpios)## id_paciente edad sexo_limpio nivel_estres horas_sueno
## Min. : 2.00 Min. :18.0 Femenino :19 Min. : 2.000 Min. :3.000
## 1st Qu.:12.50 1st Qu.:19.0 Masculino:12 1st Qu.: 3.000 1st Qu.:4.000
## Median :22.00 Median :21.0 Median : 6.000 Median :7.000
## Mean :24.48 Mean :21.0 Mean : 5.581 Mean :6.185
## 3rd Qu.:36.50 3rd Qu.:22.5 3rd Qu.: 8.000 3rd Qu.:8.000
## Max. :50.00 Max. :25.0 Max. :10.000 Max. :9.000
## NA's :4
## uso_redes_noche
## Menos de 1 hora:6
## 1-2 horas :9
## 3-4 horas :6
## Más de 6 horas :7
## NA's :3
##
##
La base de datos se puede agrupar para calcular resúmenes de estadística. Por ejemplo, podemos agrupar pacientes por sexo para evaluar su promedio de horas de sueño.
datos_limpios |>
group_by(sexo_limpio) |>
summarise(
promedio_sueno = mean(horas_sueno, na.rm = TRUE),
n_pacientes = n()
)## # A tibble: 2 × 3
## sexo_limpio promedio_sueno n_pacientes
## <fct> <dbl> <int>
## 1 Femenino 6.47 19
## 2 Masculino 5.7 12
Al iniciar o terminar un flujo de trabajo, necesitan poder manipular su entorno de archivos.
# Cargar librerías
library(readr)
library(readxl)
library(writexl)
# 1. CSV
# Importar CSV: read_csv("ruta_del_archivo.csv")
# Exportar CSV (nuestra base curada y lista):
write_csv(datos_limpios, "datos_clinicos_limpios.csv")
# 2. Excel (.xlsx)
# Importar Excel: read_excel("ruta_del_archivo.xlsx")
# Exportar base de datos a Excel:
write_xlsx(datos_limpios, "datos_clinicos_limpios.xlsx")
# 3. Exportar múltiples hojas a Excel
# Si quisieran guardar la data cruda original y la limpia en un mismo archivo, guardan el objeto como una lista
lista_hojas <- list(Crudos = datos_crudos, Limpios = datos_limpios)
write_xlsx(lista_hojas, "reporte_consolidado.xlsx")¡Felicidades! Han convertido una base de datos vulnerable a sesgos en un insumo listo para el análisis, sin corromper sus variables primarias. Como evidencia de su auditoría y curación, guarden los resultados en su carpeta y rendericen un documento de prueba.
Reflexionemos en grupos de trabajo: ¿Cómo el control automatizado que hemos creado ayuda a garantizar la reproducibilidad cuando recopilen más encuestas desde KoboToolbox?