1. RScripts vs. RStudio Projects

Antes de escribir código, organizaremos nuestro entorno de trabajo.

  1. RStudio Project: No guarden los archivos sueltos. Al crear un “Project”, se crea una carpeta independiente donde los archivos saben dónde buscar la información usando rutas relativas.
  2. Scripts: Un script (como un archivo R o RMarkdown) es un documento que contiene todas las órdenes necesarias para nuestro trabajo, garantizando reproducibilidad si otras personas ejecutan nuestro proyecto.
  3. Estructura recomendada: Para proyectos de investigación organizaremos los archivos de RMarkdown y la base de datos en una misma carpeta.

2. Conexión: La Ética del Dato Clínico

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.


3. Construcción: Conceptos Básicos de R

3.1. R como calculadora y operaciones

# 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
10 - 5
## [1] 5
3 * 4
## [1] 12
15 / 3
## [1] 5
2^3 # Exponente
## [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.

# Crear una variable (Asignación)
mi_edad <- 20

# Reglas para nombrar: usen nombres cortos y descriptivos, en minúsculas y sin espacios. Utilicen guiones bajos (_) para separar palabras.
peso_kg <- 70
altura_m <- 1.75

3.3. Vectores y Secuencias

Un vector guarda conjuntos de datos del mismo tipo en un solo objeto. Se crean usando c() o secuencias.

# Vectores: 
edades <- c(19, 21, 20, 22, 18)

# Secuencias: 
secuencia_id <- 1:5 # Crea números del 1 al 5

3.4 Operaciones de Vectores y Datos Faltantes (NA)

# Las operaciones se aplican a cada elemento del vector:
edades_mas_dos <- edades + 2

# Funciones Descriptivas Básicas:
mean(edades)
## [1] 20
min(edades)
## [1] 18
max(edades)
## [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

4. Tidyverse y Manipulación de Datos

Instalaremos y cargaremos la librería principal para nuestro trabajo.

#install.packages("tidyverse")
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

4.1. Creación de la Base de Datos Ficticia

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

4.2. El Pipe |> (o %>%)

El operador pipe |> (o %>%) conecta instrucciones, pasando el resultado del bloque de código anterior como el primer argumento del siguiente bloque.

4.3. Flujo de Trabajo (Workflow) con Tidyverse

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     
##                        
## 

4.4 Resumir (Summarise)

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

5. Exportación / Importación de Archivos

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")

6. Actividad Práctica y Reflexión

¡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?