Preparando datos para su análisis en investigación en salud
Author
Agoralab
Published
Invalid Date
Importación y limpieza de datos
Bienvenida y repaso
En las dos primeras semanas, nos hemos familiarizado con el entorno de R, Quarto y las estructuras básicas de datos. Ahora estamos listos para comenzar a trabajar con datos reales, lo que implica importarlos desde diferentes fuentes y prepararlos para su análisis.
La importación y limpieza de datos son pasos fundamentales en cualquier proyecto de análisis, especialmente en ciencias de la salud donde la calidad de los datos puede afectar significativamente las conclusiones científicas.
Objetivos de la sesión
Al finalizar esta sesión, serás capaz de:
Importar datos desde diversos formatos (CSV, Excel, otros)
Inspeccionar un conjunto de datos recién importado para identificar posibles problemas
Limpiar y transformar datos para hacerlos más adecuados para el análisis
Manejar valores faltantes, categorías inconsistentes y otros problemas comunes
Comprender el concepto de datos “tidy” (ordenados) y su importancia
Utilizar funciones básicas de dplyr para filtrar y seleccionar datos
Importación de datos
Uno de los primeros pasos en cualquier análisis es cargar datos desde archivos externos. R ofrece múltiples opciones para importar datos desde distintos formatos.
El flujo de trabajo para datos en investigación sanitaria
En investigación en salud, es común seguir este flujo de trabajo para manejar datos:
Recolección - Obtención de datos (estudios clínicos, registros electrónicos, encuestas, etc.)
Importación - Lectura de los datos en R desde su formato original
Limpieza - Corrección de inconsistencias, valores atípicos, datos faltantes
Transformación - Reestructuración de los datos para el análisis
Análisis - Aplicación de métodos estadísticos para responder preguntas de investigación
Comunicación - Presentación de resultados en informes, artículos o visualizaciones
En esta sesión, nos centraremos en los pasos 2, 3 y 4, que suelen representar hasta el 80% del tiempo en un proyecto de análisis.
Importación desde archivos CSV
Los archivos CSV (Comma-Separated Values) son uno de los formatos más comunes para almacenar datos tabulares. Podemos cargarlos usando funciones de R base o del paquete readr (parte del tidyverse):
# Usando R base# datos_base <- read.csv("ruta/al/archivo.csv")# Usando readr (más rápido y con mejores defaults)library(readr)# Ejemplo con datos incluidos en R# Primero, guardamos el dataset mtcars como un CSV para este ejemplowrite_csv(mtcars, "mtcars.csv")# Luego lo leemos para mostrar cómo funciona la importacióndatos_csv<-read_csv("mtcars.csv")# Veamos los primeros registroshead(datos_csv)
Parámetros útiles para importar CSV en investigación médica
Cuando trabajas con datos de estudios clínicos o sanitarios, estos parámetros pueden ser muy útiles:
datos<-read_csv("datos_clinicos.csv",# Para datos con códigos especiales para valores faltantes na =c("", "NA", "NULL", "missing", "-999"),# Para saltarse líneas de cabecera no relevantes skip =2,# Para especificar el tipo de cada columna (evita inferencias incorrectas) col_types =cols( id_paciente =col_character(), edad =col_integer(), grupo_tratamiento =col_factor(levels =c("Control", "Intervención")), fecha_ingreso =col_date(format ="%d/%m/%Y")),# Para datos con separador decimal con coma (habitual en países hispanohablantes) locale =locale(decimal_mark =","))
Especificar explícitamente estos parámetros puede prevenir muchos errores comunes en la importación de datos.
Importación desde archivos Excel
Los archivos Excel son muy comunes en contextos de investigación en salud. El paquete readxl facilita su importación:
Code
# Cargamos el paquete readxllibrary(readxl)# Para este ejemplo, creamos un archivo Excel temporal# (En la práctica, trabajarías con tus propios archivos)# Creemos un pequeño datasetdatos_ejemplo<-data.frame( id =1:5, paciente =c("A", "B", "C", "D", "E"), edad =c(45, 52, 67, 38, 71), presion_sistolica =c(120, 135, 142, 118, 155), presion_diastolica =c(80, 85, 95, 75, 90))# Guardar como Excellibrary(writexl)write_xlsx(datos_ejemplo, "datos_clinicos.xlsx")# Leer archivo Exceldatos_excel<-read_excel("datos_clinicos.xlsx")head(datos_excel)
Code
# También puedes especificar qué hoja leer en un libro con múltiples hojas# datos_excel2 <- read_excel("archivo.xlsx", sheet = "Datos 2023")# O leer un rango específico# datos_excel3 <- read_excel("archivo.xlsx", range = "A1:D20")
Otros formatos de importación
R puede trabajar con muchos otros formatos:
Code
# Archivos de SPSS (comunes en investigación clínica)# library(haven)# datos_spss <- read_sav("archivo.sav")# Archivos de Stata# datos_stata <- read_dta("archivo.dta")# Archivos de SAS# datos_sas <- read_sas("archivo.sas7bdat")# Archivos JSON (comunes en datos de APIs)# library(jsonlite)# datos_json <- fromJSON("archivo.json")# Bases de datos SQL# library(DBI)# library(RSQLite)# conn <- dbConnect(SQLite(), "mi_base.sqlite")# datos_sql <- dbGetQuery(conn, "SELECT * FROM mi_tabla")# dbDisconnect(conn)
Mejores prácticas para importación de datos
Al importar datos, considera estas recomendaciones:
Guarda los datos crudos sin modificar: Mantén siempre una copia de los datos originales.
Usa rutas relativas: Mejor que absolutas, para facilitar la reproducibilidad.
Especifica el encoding si es necesario: Para manejar caracteres especiales (ej. acentos).
Documenta la fuente de los datos: Incluye información sobre procedencia y fecha.
Code
# Especificar encoding (ej. para datos con acentos en español)# datos_acentos <- read_csv("datos_español.csv", locale = locale(encoding = "ISO-8859-1"))# Usar el paquete here para rutas relativas (muy recomendable)# library(here)# datos <- read_csv(here("datos", "clinicos", "pacientes_2023.csv"))
Inspección inicial del dataset
Una vez importados los datos, el siguiente paso es explorarlos para entender su estructura y detectar posibles problemas.
Nunca confíes en datos crudos
En investigación clínica y epidemiológica, los datos crudos casi siempre tienen problemas que podrían afectar tus análisis:
Valores faltantes o codificados de manera inconsistente
Errores de entrada de datos (ej. presión arterial registrada como 1600 en lugar de 160)
Inconsistencias en unidades (mg/dL vs mmol/L)
Duplicados (el mismo paciente registrado varias veces)
Categorías mal escritas (“Masculino”, “masculino”, “M”, “Varón”, “V” para el mismo concepto)
Datos implausibles (edad de 200 años, IMC de 0.5)
Dedicar tiempo a una exploración exhaustiva inicial puede ahorrarte interpretaciones erróneas y conclusiones incorrectas más adelante.
Vamos a trabajar con un conjunto de datos de ejemplo sobre pacientes:
Code
# Creemos un conjunto de datos más completo para nuestra exploraciónset.seed(123)# Para reproducibilidaddatos_pacientes<-data.frame( id =1:100, edad =sample(18:90, 100, replace =TRUE), sexo =sample(c("M", "m", "Masculino", "F", "f", "Femenino", NA), 100, replace =TRUE), peso =round(rnorm(100, mean =70, sd =15), 1), altura =round(rnorm(100, mean =1.65, sd =0.1), 2), presion_sistolica =sample(c(100:200, NA), 100, replace =TRUE), presion_diastolica =sample(c(60:120, NA), 100, replace =TRUE), fumador =sample(c("Sí", "Si", "S", "No", "N", "NS/NC", NA), 100, replace =TRUE), diagnostico =sample(c("Hipertensión", "Diabetes", "EPOC", "Asma", "Sano", NA), 100, replace =TRUE), fecha_ingreso =sample(seq(as.Date("2023-01-01"), as.Date("2023-12-31"), by ="day"), 100, replace =TRUE))# Introducir algunos errores para ejercicio de limpiezadatos_pacientes$peso[sample(1:100, 5)]<-NA# Algunos NAdatos_pacientes$peso[sample(1:100, 3)]<--99# Código para "no medido"datos_pacientes$altura[sample(1:100, 3)]<-0# Error: altura imposibledatos_pacientes$presion_sistolica[sample(1:100, 3)]<-0# Error
id edad sexo peso
Min. : 1.00 Min. :21.00 Length:100 Min. :-99.00
1st Qu.: 25.75 1st Qu.:38.75 Class :character 1st Qu.: 56.98
Median : 50.50 Median :51.00 Mode :character Median : 68.20
Mean : 50.50 Mean :52.63 Mean : 64.17
3rd Qu.: 75.25 3rd Qu.:67.25 3rd Qu.: 80.45
Max. :100.00 Max. :89.00 Max. :110.60
NA's :4
altura presion_sistolica presion_diastolica fumador
Min. :0.000 Min. : 0.0 Min. : 60.00 Length:100
1st Qu.:1.570 1st Qu.:123.2 1st Qu.: 70.00 Class :character
Median :1.650 Median :152.0 Median : 87.50 Mode :character
Mean :1.597 Mean :147.0 Mean : 88.29
3rd Qu.:1.722 3rd Qu.:177.0 3rd Qu.:103.25
Max. :1.900 Max. :200.0 Max. :120.00
NA's :2
diagnostico fecha_ingreso
Length:100 Min. :2023-01-02
Class :character 1st Qu.:2023-03-27
Mode :character Median :2023-06-12
Mean :2023-06-20
3rd Qu.:2023-09-23
Max. :2023-12-30
Code
# Para una exploración más detallada, podemos usar el paquete skimrlibrary(skimr)skim(datos_pacientes)
Data summary
Name
datos_pacientes
Number of rows
100
Number of columns
10
_______________________
Column type frequency:
character
3
Date
1
numeric
6
________________________
Group variables
None
Variable type: character
skim_variable
n_missing
complete_rate
min
max
empty
n_unique
whitespace
sexo
11
0.89
1
9
0
6
0
fumador
19
0.81
1
5
0
6
0
diagnostico
15
0.85
4
12
0
5
0
Variable type: Date
skim_variable
n_missing
complete_rate
min
max
median
n_unique
fecha_ingreso
0
1
2023-01-02
2023-12-30
2023-06-12
86
Variable type: numeric
skim_variable
n_missing
complete_rate
mean
sd
p0
p25
p50
p75
p100
hist
id
0
1.00
50.50
29.01
1
25.75
50.50
75.25
100.0
▇▇▇▇▇
edad
0
1.00
52.63
19.63
21
38.75
51.00
67.25
89.0
▇▇▇▆▆
peso
4
0.96
64.17
33.58
-99
56.98
68.20
80.45
110.6
▁▁▁▇▇
altura
0
1.00
1.60
0.30
0
1.57
1.65
1.72
1.9
▁▁▁▂▇
presion_sistolica
2
0.98
146.97
39.30
0
123.25
152.00
177.00
200.0
▁▁▃▇▇
presion_diastolica
0
1.00
88.29
18.59
60
70.00
87.50
103.25
120.0
▇▆▅▃▆
Identificación de problemas comunes
A partir de la exploración, podemos identificar varios problemas típicos:
Valores faltantes (NA): Presentes en varias columnas
Códigos especiales: Valores como -99 que representan “no medido”
Valores imposibles: Como alturas de 0 metros
Inconsistencia en categorías: Diferentes formas de registrar el mismo valor (ej. “Sí”/“Si”/“S”)
Tipos de datos incorrectos: Variables que deberían ser factores pero están como caracteres
Limpieza de datos
Ahora abordaremos cada uno de estos problemas:
1. Manejo de valores faltantes (NA)
Code
# Contar NA por columnacolSums(is.na(datos_pacientes))
id edad sexo peso
0 0 11 4
altura presion_sistolica presion_diastolica fumador
0 2 0 19
diagnostico fecha_ingreso
15 0
Code
# Identificar filas con NA en la columna de pesowhich(is.na(datos_pacientes$peso))
[1] 16 30 51 89
Code
# Opciones para manejar NA:# 1. Eliminar filas con NA (use with caution!)datos_sin_na<-na.omit(datos_pacientes)dim(datos_sin_na)# Verificar cuántas filas quedan
[1] 56 10
Code
# 2. Reemplazar NA con un valor (imputación simple)# Aquí reemplazamos NA en presión sistólica con la mediamedia_sistolica<-mean(datos_pacientes$presion_sistolica, na.rm =TRUE)datos_pacientes$presion_sistolica_imp<-ifelse(is.na(datos_pacientes$presion_sistolica),media_sistolica,datos_pacientes$presion_sistolica)# Verificar que no hay NA en la nueva columnasum(is.na(datos_pacientes$presion_sistolica_imp))
[1] 0
La importancia de entender el mecanismo de los datos faltantes
En bioestadística, se distinguen tres tipos de mecanismos de valores faltantes:
MCAR (Missing Completely At Random): La probabilidad de que un valor falte no depende de ninguna variable. Este es el escenario ideal pero poco común.
MAR (Missing At Random): La probabilidad de datos faltantes depende de variables observadas. Por ejemplo, los pacientes de mayor edad pueden tener más valores faltantes en pruebas físicas.
MNAR (Missing Not At Random): La probabilidad de datos faltantes depende del valor que falta. Por ejemplo, las personas con valores extremos de presión arterial pueden abandonar el estudio.
El tipo de mecanismo de datos faltantes determina qué estrategias de manejo son apropiadas. La eliminación de casos completos solo es segura bajo MCAR, mientras que para MAR se puede usar imputación múltiple, y para MNAR se necesitan modelos específicos.
2. Corrección de valores imposibles o códigos especiales
Code
# Reemplazar códigos especiales (-99) en peso con NAdatos_pacientes$peso_limpio<-ifelse(datos_pacientes$peso==-99, NA, datos_pacientes$peso)# Identificar y corregir alturas imposiblestable(datos_pacientes$altura==0)# Ver cuántos valores son 0
FALSE TRUE
97 3
Code
datos_pacientes$altura_limpia<-ifelse(datos_pacientes$altura<=0, NA, datos_pacientes$altura)# Lo mismo para presión arterialdatos_pacientes$presion_sistolica_limpia<-ifelse(datos_pacientes$presion_sistolica<=0|datos_pacientes$presion_sistolica>250, NA, datos_pacientes$presion_sistolica)# Verificar cambiossummary(datos_pacientes[, c("altura", "altura_limpia", "presion_sistolica", "presion_sistolica_limpia")])
altura altura_limpia presion_sistolica presion_sistolica_limpia
Min. :0.000 Min. :1.440 Min. : 0.0 Min. :100.0
1st Qu.:1.570 1st Qu.:1.570 1st Qu.:123.2 1st Qu.:127.5
Median :1.650 Median :1.660 Median :152.0 Median :152.0
Mean :1.597 Mean :1.646 Mean :147.0 Mean :151.6
3rd Qu.:1.722 3rd Qu.:1.730 3rd Qu.:177.0 3rd Qu.:177.5
Max. :1.900 Max. :1.900 Max. :200.0 Max. :200.0
NA's :3 NA's :2 NA's :5
3. Estandarización de categorías inconsistentes
Un problema muy común en datos de salud es la inconsistencia en cómo se registran las categorías:
Code
# Ver las diferentes categorías en sexo y fumadortable(datos_pacientes$sexo, useNA ="ifany")
f F Femenino m M Masculino <NA>
19 13 13 15 18 11 11
stringdist: Para encontrar coincidencias aproximadas
library(stringdist)# Encuentra el valor estándar más cercanoestandarizar<-function(valor, estandares){if(is.na(valor))return(NA)i<-which.min(stringdist(valor, estandares))return(estandares[i])}
janitor: Para limpiar nombres de variables
library(janitor)datos_limpios<-clean_names(datos)# estandariza nombres de columnas
4. Conversión a tipos de datos adecuados
Es importante asegurarse de que las variables tengan el tipo de dato correcto:
Code
# Convertir diagnóstico a factor (variable categórica)datos_pacientes$diagnostico<-factor(datos_pacientes$diagnostico)# Asegurarse de que fecha_ingreso es tipo fechaclass(datos_pacientes$fecha_ingreso)
[1] "Date"
Code
# En caso de que no fuera tipo fecha:# datos_pacientes$fecha_ingreso <- as.Date(datos_pacientes$fecha_ingreso)# Verificar la estructura actualizadastr(datos_pacientes[, c("diagnostico", "fecha_ingreso")])
A menudo necesitamos crear nuevas variables basadas en las existentes:
Code
# Crear variable IMC a partir de peso y alturadatos_pacientes$imc<-datos_pacientes$peso_limpio/(datos_pacientes$altura_limpia^2)# Categorizar el IMC según criterios estándardatos_pacientes$categoria_imc<-cut(datos_pacientes$imc, breaks =c(0, 18.5, 25, 30, Inf), labels =c("Bajo peso", "Normal", "Sobrepeso", "Obesidad"), right =FALSE)# Crear variable de mes de ingreso (útil para análisis temporales)datos_pacientes$mes_ingreso<-format(datos_pacientes$fecha_ingreso, "%m")datos_pacientes$mes_ingreso<-factor(datos_pacientes$mes_ingreso, levels =1:12, labels =month.abb)# Abreviaturas de meses# Verificar las nuevas variablessummary(datos_pacientes[, c("imc", "categoria_imc", "mes_ingreso")])
imc categoria_imc mes_ingreso
Min. : 8.236 Bajo peso:10 Oct : 9
1st Qu.:20.209 Normal :36 Nov : 6
Median :24.416 Sobrepeso:18 Dec : 6
Mean :25.845 Obesidad :26 Jan : 0
3rd Qu.:30.751 NA's :10 Feb : 0
Max. :49.540 (Other): 0
NA's :10 NA's :79
Datos “tidy” (ordenados)
El concepto de datos “tidy” o datos ordenados es fundamental en el análisis moderno con R. Según este principio, un conjunto de datos está ordenado cuando:
Cada variable forma una columna
Cada observación forma una fila
Cada tipo de unidad observacional forma una tabla
¿Por qué es crucial el formato “tidy” en investigación sanitaria?
El formato “tidy” es particularmente valioso en análisis de datos sanitarios por varias razones:
Facilita el análisis longitudinal: Datos de seguimiento de pacientes a lo largo del tiempo son más manejables en formato largo.
Mejora la integración de datos: Facilita la combinación de datos de diferentes fuentes (historias clínicas, registros de laboratorio, etc.).
Simplifica las visualizaciones: Es más directo crear gráficos de múltiples variables con ggplot2 usando datos “tidy”.
Optimiza el modelado estadístico: La mayoría de funciones de modelado en R (lm(), glm(), survival::coxph()) esperan datos en formato “tidy”.
Reduce errores de análisis: La estructura consistente minimiza confusiones sobre qué representa cada valor.
Veamos un ejemplo de datos que no están en formato “tidy” y cómo transformarlos:
Code
# Datos no tidy (wide format) - Mediciones de presión arterial en diferentes visitaspacientes_wide<-data.frame( id =1:5, nombre =c("Ana", "Beto", "Carmen", "Diego", "Elena"), visita1_sistolica =c(120, 135, 142, 118, 155), visita1_diastolica =c(80, 85, 95, 75, 90), visita2_sistolica =c(118, 133, 145, 120, 150), visita2_diastolica =c(78, 84, 97, 78, 88))pacientes_wide
Estos datos no están en formato “tidy” porque tenemos múltiples variables (presión sistólica y diastólica de diferentes visitas) esparcidas en varias columnas. Transformemos a formato “tidy”:
Ahora cada fila representa una observación única: un paciente, en una visita específica, con un tipo específico de medición. Este formato facilita muchos análisis.
También podemos volver al formato original si es necesario:
dplyr es un paquete del tidyverse que proporciona un conjunto de funciones para manipular fácilmente data frames. Dos de sus funciones más básicas son filter() para seleccionar filas y select() para seleccionar columnas.
Filtrar filas con filter()
Code
library(dplyr)# Seleccionar solo pacientes mayores de 65 añospacientes_mayores<-datos_pacientes%>%filter(edad>65)# Ver cuántos pacientes cumplen el criterionrow(pacientes_mayores)
[1] 28
Code
# Filtros con múltiples condicionespacientes_riesgo<-datos_pacientes%>%filter(edad>50&fumador_std=="Sí"&presion_sistolica_limpia>140)# Ver cuántos pacientes están en este grupo de riesgonrow(pacientes_riesgo)
[1] 8
Code
# Excluir pacientes con valores faltantes en diagnósticopacientes_diagnostico_completo<-datos_pacientes%>%filter(!is.na(diagnostico))nrow(pacientes_diagnostico_completo)
[1] 85
Seleccionar columnas con select()
Code
# Seleccionar solo algunas columnas de interésdatos_simplificados<-datos_pacientes%>%select(id, edad, sexo_std, diagnostico, imc, categoria_imc)head(datos_simplificados)
Code
# Seleccionar columnas por patróncolumnas_presion<-datos_pacientes%>%select(id, contains("presion"))head(columnas_presion)
Filtrar solo los casos confirmados de marzo a mayo de 2020
Agregar casos por país y fecha
Crear una variable para indicar si el país está en América Latina
Identificar los 10 países con más casos acumulados en ese periodo
Aproximación paso a paso a problemas de datos complejos
Cuando te enfrentes a tareas de manipulación de datos complejas:
Comienza con subconjuntos pequeños: Prueba tu código con un pequeño subconjunto de datos para verificar que funciona como esperas.
Divide la tarea en pasos más pequeños: Resuelve y verifica cada subtarea antes de pasar a la siguiente.
Encadena operaciones con el pipe (%>%) cuando sea posible: Esto hace que tu código sea más legible y evita la creación de objetos intermedios.
Guarda checkpoints intermedios: En análisis complejos, guarda versiones intermedias de tus datos (ej. datos_filtrados, datos_agregados) para poder revisar y depurar.
Haz verificaciones de sanidad: Confirma que tus transformaciones producen los resultados esperados (totales correctos, número de filas adecuado, etc.).
Code
# 1. Filtrar casos confirmados de marzo a mayo 2020covid_confirmados<-coronavirus%>%filter(type=="confirmed",date>=as.Date("2020-03-01"),date<=as.Date("2020-05-31"))# 2. Agregar casos por país y fechacovid_por_pais<-covid_confirmados%>%group_by(country, date)%>%summarise(casos_diarios =sum(cases, na.rm =TRUE), .groups ="drop")# 3. Crear variable para América Latinapaises_latam<-c("Brazil", "Mexico", "Argentina", "Chile", "Colombia", "Peru", "Ecuador", "Bolivia", "Venezuela", "Uruguay","Paraguay", "Cuba", "Dominican Republic", "Panama", "Costa Rica", "Guatemala", "Honduras", "El Salvador", "Nicaragua")covid_por_pais<-covid_por_pais%>%mutate(es_latam =country%in%paises_latam)# 4. Identificar los 10 países con más casos acumuladostop10_paises<-covid_por_pais%>%group_by(country)%>%summarise(casos_totales =sum(casos_diarios, na.rm =TRUE))%>%arrange(desc(casos_totales))%>%slice_head(n =10)# Ver los resultadostop10_paises
Exportación de datos limpios
Después de limpiar y transformar los datos, a menudo queremos guardarlos para análisis posteriores:
Code
# Exportar a CSVwrite_csv(datos_pacientes, "datos_pacientes_limpios.csv")# Exportar a Excelwritexl::write_xlsx(datos_pacientes, "datos_pacientes_limpios.xlsx")# Exportar a formato RDS (específico de R, mantiene tipos de datos)saveRDS(datos_pacientes, "datos_pacientes_limpios.rds")
Tip
El formato RDS es ideal para almacenar objetos de R porque preserva la estructura exacta y los tipos de datos, incluyendo factores y atributos. Para compartir datos con personas que no usan R, los formatos CSV o Excel son más adecuados.
Documentación de la limpieza de datos
En investigación clínica y epidemiológica, es crucial documentar todos los pasos de limpieza y transformación para garantizar la reproducibilidad y transparencia:
Crea un “diario de limpieza”: Documenta cada decisión tomada (por ejemplo, “Se eliminaron valores de presión arterial sistólica <60 y >250 mmHg por considerarse errores de entrada”).
Guarda los datos en múltiples etapas:
Datos crudos originales (nunca modificados)
Datos después de la limpieza básica
Datos finales para análisis
Script reproducible: Asegúrate de que todo tu proceso de limpieza está en scripts que pueden ser ejecutados de principio a fin para recrear los datos finales.
Versiona tus datos: Usa control de versiones (como Git) o al menos nombra tus archivos con fechas (ej. datos_clinicos_limpios_20250319.csv).
Estos pasos no solo mejoran la calidad de la investigación, sino que también son cada vez más requeridos por revistas científicas y organismos financiadores.
Recursos adicionales
Para profundizar en la importación y limpieza de datos:
Realizar operaciones más complejas de reestructuración de datos
Conclusiones
En esta sesión hemos aprendido:
Cómo importar datos desde diferentes formatos (CSV, Excel)
Técnicas para inspeccionar y comprender la estructura de un dataset
Métodos para identificar y corregir problemas comunes en datos crudos
El concepto de datos “tidy” y cómo transformar entre formatos
Funciones básicas de dplyr para filtrar y seleccionar datos
La limpieza de datos, aunque a veces tediosa, es un paso crucial que puede determinar la validez de todo el análisis posterior. En investigación en salud, donde las decisiones basadas en datos pueden afectar la vida de las personas, es especialmente importante garantizar la calidad de los datos antes de proceder con análisis estadísticos.
¡Nos vemos en la próxima sesión!
Source Code
---title: "Semana 3: Importación y limpieza de datos"subtitle: "Preparando datos para su análisis en investigación en salud"author: "Agoralab"date: "19 de marzo, 2025"format: html: toc: true code-fold: show code-tools: true df-print: paged code-link: trueexecute: warning: false message: false---# Importación y limpieza de datos## Bienvenida y repasoEn las dos primeras semanas, nos hemos familiarizado con el entorno de R, Quarto y las estructuras básicas de datos. Ahora estamos listos para comenzar a trabajar con datos reales, lo que implica importarlos desde diferentes fuentes y prepararlos para su análisis.La importación y limpieza de datos son pasos fundamentales en cualquier proyecto de análisis, especialmente en ciencias de la salud donde la calidad de los datos puede afectar significativamente las conclusiones científicas.## Objetivos de la sesiónAl finalizar esta sesión, serás capaz de:1. Importar datos desde diversos formatos (CSV, Excel, otros)2. Inspeccionar un conjunto de datos recién importado para identificar posibles problemas3. Limpiar y transformar datos para hacerlos más adecuados para el análisis 4. Manejar valores faltantes, categorías inconsistentes y otros problemas comunes5. Comprender el concepto de datos "tidy" (ordenados) y su importancia6. Utilizar funciones básicas de dplyr para filtrar y seleccionar datos## Importación de datosUno de los primeros pasos en cualquier análisis es cargar datos desde archivos externos. R ofrece múltiples opciones para importar datos desde distintos formatos.::: {.callout-note}## El flujo de trabajo para datos en investigación sanitariaEn investigación en salud, es común seguir este flujo de trabajo para manejar datos:1. **Recolección** - Obtención de datos (estudios clínicos, registros electrónicos, encuestas, etc.)2. **Importación** - Lectura de los datos en R desde su formato original3. **Limpieza** - Corrección de inconsistencias, valores atípicos, datos faltantes4. **Transformación** - Reestructuración de los datos para el análisis5. **Análisis** - Aplicación de métodos estadísticos para responder preguntas de investigación6. **Comunicación** - Presentación de resultados en informes, artículos o visualizacionesEn esta sesión, nos centraremos en los pasos 2, 3 y 4, que suelen representar hasta el 80% del tiempo en un proyecto de análisis.:::### Importación desde archivos CSVLos archivos CSV (Comma-Separated Values) son uno de los formatos más comunes para almacenar datos tabulares. Podemos cargarlos usando funciones de R base o del paquete readr (parte del tidyverse):```{r, message=FALSE, warning=FALSE, results='hide'}# Instalamos los paquetes necesarios si no están disponiblesif (!requireNamespace("readr", quietly = TRUE)) { install.packages("readr")}if (!requireNamespace("writexl", quietly = TRUE)) { install.packages("writexl")}if (!requireNamespace("readxl", quietly = TRUE)) { install.packages("readxl")}if (!requireNamespace("skimr", quietly = TRUE)) { install.packages("skimr")}if (!requireNamespace("dplyr", quietly = TRUE)) { install.packages("dplyr")}if (!requireNamespace("tidyr", quietly = TRUE)) { install.packages("tidyr")}if (!requireNamespace("coronavirus", quietly = TRUE)) { install.packages("coronavirus")}``````{r}# Usando R base# datos_base <- read.csv("ruta/al/archivo.csv")# Usando readr (más rápido y con mejores defaults)library(readr)# Ejemplo con datos incluidos en R# Primero, guardamos el dataset mtcars como un CSV para este ejemplowrite_csv(mtcars, "mtcars.csv")# Luego lo leemos para mostrar cómo funciona la importacióndatos_csv <-read_csv("mtcars.csv")# Veamos los primeros registroshead(datos_csv)```Ventajas de usar `read_csv()` de readr vs. `read.csv()` de base R:- Más rápido para archivos grandes- No convierte strings a factores automáticamente- Usa tibbles (un tipo mejorado de data frame)- Mejores mensajes de error y progreso- Detecta automáticamente el tipo de columnas::: {.callout-tip}## Parámetros útiles para importar CSV en investigación médicaCuando trabajas con datos de estudios clínicos o sanitarios, estos parámetros pueden ser muy útiles:```rdatos <-read_csv("datos_clinicos.csv",# Para datos con códigos especiales para valores faltantesna =c("", "NA", "NULL", "missing", "-999"),# Para saltarse líneas de cabecera no relevantesskip =2,# Para especificar el tipo de cada columna (evita inferencias incorrectas)col_types =cols(id_paciente =col_character(),edad =col_integer(),grupo_tratamiento =col_factor(levels =c("Control", "Intervención")),fecha_ingreso =col_date(format ="%d/%m/%Y") ),# Para datos con separador decimal con coma (habitual en países hispanohablantes)locale =locale(decimal_mark =","))```Especificar explícitamente estos parámetros puede prevenir muchos errores comunes en la importación de datos.:::### Importación desde archivos ExcelLos archivos Excel son muy comunes en contextos de investigación en salud. El paquete readxl facilita su importación:```{r}# Cargamos el paquete readxllibrary(readxl)# Para este ejemplo, creamos un archivo Excel temporal# (En la práctica, trabajarías con tus propios archivos)# Creemos un pequeño datasetdatos_ejemplo <-data.frame(id =1:5,paciente =c("A", "B", "C", "D", "E"),edad =c(45, 52, 67, 38, 71),presion_sistolica =c(120, 135, 142, 118, 155),presion_diastolica =c(80, 85, 95, 75, 90))# Guardar como Excellibrary(writexl)write_xlsx(datos_ejemplo, "datos_clinicos.xlsx")# Leer archivo Exceldatos_excel <-read_excel("datos_clinicos.xlsx")head(datos_excel)# También puedes especificar qué hoja leer en un libro con múltiples hojas# datos_excel2 <- read_excel("archivo.xlsx", sheet = "Datos 2023")# O leer un rango específico# datos_excel3 <- read_excel("archivo.xlsx", range = "A1:D20")```### Otros formatos de importaciónR puede trabajar con muchos otros formatos:```{r, eval=FALSE}# Archivos de SPSS (comunes en investigación clínica)# library(haven)# datos_spss <- read_sav("archivo.sav")# Archivos de Stata# datos_stata <- read_dta("archivo.dta")# Archivos de SAS# datos_sas <- read_sas("archivo.sas7bdat")# Archivos JSON (comunes en datos de APIs)# library(jsonlite)# datos_json <- fromJSON("archivo.json")# Bases de datos SQL# library(DBI)# library(RSQLite)# conn <- dbConnect(SQLite(), "mi_base.sqlite")# datos_sql <- dbGetQuery(conn, "SELECT * FROM mi_tabla")# dbDisconnect(conn)```### Mejores prácticas para importación de datosAl importar datos, considera estas recomendaciones:1. **Guarda los datos crudos sin modificar**: Mantén siempre una copia de los datos originales.2. **Usa rutas relativas**: Mejor que absolutas, para facilitar la reproducibilidad.3. **Especifica el encoding si es necesario**: Para manejar caracteres especiales (ej. acentos).4. **Documenta la fuente de los datos**: Incluye información sobre procedencia y fecha.```{r, eval=FALSE}# Especificar encoding (ej. para datos con acentos en español)# datos_acentos <- read_csv("datos_español.csv", locale = locale(encoding = "ISO-8859-1"))# Usar el paquete here para rutas relativas (muy recomendable)# library(here)# datos <- read_csv(here("datos", "clinicos", "pacientes_2023.csv"))```## Inspección inicial del datasetUna vez importados los datos, el siguiente paso es explorarlos para entender su estructura y detectar posibles problemas.::: {.callout-important}## Nunca confíes en datos crudosEn investigación clínica y epidemiológica, los datos crudos casi siempre tienen problemas que podrían afectar tus análisis:- Valores faltantes o codificados de manera inconsistente- Errores de entrada de datos (ej. presión arterial registrada como 1600 en lugar de 160)- Inconsistencias en unidades (mg/dL vs mmol/L)- Duplicados (el mismo paciente registrado varias veces)- Categorías mal escritas ("Masculino", "masculino", "M", "Varón", "V" para el mismo concepto)- Datos implausibles (edad de 200 años, IMC de 0.5)Dedicar tiempo a una exploración exhaustiva inicial puede ahorrarte interpretaciones erróneas y conclusiones incorrectas más adelante.:::Vamos a trabajar con un conjunto de datos de ejemplo sobre pacientes:```{r}# Creemos un conjunto de datos más completo para nuestra exploraciónset.seed(123) # Para reproducibilidaddatos_pacientes <-data.frame(id =1:100,edad =sample(18:90, 100, replace =TRUE),sexo =sample(c("M", "m", "Masculino", "F", "f", "Femenino", NA), 100, replace =TRUE),peso =round(rnorm(100, mean =70, sd =15), 1),altura =round(rnorm(100, mean =1.65, sd =0.1), 2),presion_sistolica =sample(c(100:200, NA), 100, replace =TRUE),presion_diastolica =sample(c(60:120, NA), 100, replace =TRUE),fumador =sample(c("Sí", "Si", "S", "No", "N", "NS/NC", NA), 100, replace =TRUE),diagnostico =sample(c("Hipertensión", "Diabetes", "EPOC", "Asma", "Sano", NA), 100, replace =TRUE),fecha_ingreso =sample(seq(as.Date("2023-01-01"), as.Date("2023-12-31"), by ="day"), 100, replace =TRUE))# Introducir algunos errores para ejercicio de limpiezadatos_pacientes$peso[sample(1:100, 5)] <-NA# Algunos NAdatos_pacientes$peso[sample(1:100, 3)] <--99# Código para "no medido"datos_pacientes$altura[sample(1:100, 3)] <-0# Error: altura imposibledatos_pacientes$presion_sistolica[sample(1:100, 3)] <-0# Error```Ahora exploremos este conjunto de datos:```{r}# Primeras filashead(datos_pacientes)# Dimensiones: filas y columnasdim(datos_pacientes)# Estructura del dataframestr(datos_pacientes)# Resumen estadísticosummary(datos_pacientes)# Para una exploración más detallada, podemos usar el paquete skimrlibrary(skimr)skim(datos_pacientes)```### Identificación de problemas comunesA partir de la exploración, podemos identificar varios problemas típicos:1. **Valores faltantes (NA)**: Presentes en varias columnas2. **Códigos especiales**: Valores como -99 que representan "no medido"3. **Valores imposibles**: Como alturas de 0 metros4. **Inconsistencia en categorías**: Diferentes formas de registrar el mismo valor (ej. "Sí"/"Si"/"S")5. **Tipos de datos incorrectos**: Variables que deberían ser factores pero están como caracteres## Limpieza de datosAhora abordaremos cada uno de estos problemas:### 1. Manejo de valores faltantes (NA)```{r}# Contar NA por columnacolSums(is.na(datos_pacientes))# Identificar filas con NA en la columna de pesowhich(is.na(datos_pacientes$peso))# Opciones para manejar NA:# 1. Eliminar filas con NA (use with caution!)datos_sin_na <-na.omit(datos_pacientes)dim(datos_sin_na) # Verificar cuántas filas quedan# 2. Reemplazar NA con un valor (imputación simple)# Aquí reemplazamos NA en presión sistólica con la mediamedia_sistolica <-mean(datos_pacientes$presion_sistolica, na.rm =TRUE)datos_pacientes$presion_sistolica_imp <-ifelse(is.na(datos_pacientes$presion_sistolica), media_sistolica, datos_pacientes$presion_sistolica)# Verificar que no hay NA en la nueva columnasum(is.na(datos_pacientes$presion_sistolica_imp))```::: {.callout-note}## La importancia de entender el mecanismo de los datos faltantesEn bioestadística, se distinguen tres tipos de mecanismos de valores faltantes:1. **MCAR (Missing Completely At Random)**: La probabilidad de que un valor falte no depende de ninguna variable. Este es el escenario ideal pero poco común.2. **MAR (Missing At Random)**: La probabilidad de datos faltantes depende de variables observadas. Por ejemplo, los pacientes de mayor edad pueden tener más valores faltantes en pruebas físicas.3. **MNAR (Missing Not At Random)**: La probabilidad de datos faltantes depende del valor que falta. Por ejemplo, las personas con valores extremos de presión arterial pueden abandonar el estudio.El tipo de mecanismo de datos faltantes determina qué estrategias de manejo son apropiadas. La eliminación de casos completos solo es segura bajo MCAR, mientras que para MAR se puede usar imputación múltiple, y para MNAR se necesitan modelos específicos.:::### 2. Corrección de valores imposibles o códigos especiales```{r}# Reemplazar códigos especiales (-99) en peso con NAdatos_pacientes$peso_limpio <-ifelse(datos_pacientes$peso ==-99, NA, datos_pacientes$peso)# Identificar y corregir alturas imposiblestable(datos_pacientes$altura ==0) # Ver cuántos valores son 0datos_pacientes$altura_limpia <-ifelse(datos_pacientes$altura <=0, NA, datos_pacientes$altura)# Lo mismo para presión arterialdatos_pacientes$presion_sistolica_limpia <-ifelse( datos_pacientes$presion_sistolica <=0| datos_pacientes$presion_sistolica >250, NA, datos_pacientes$presion_sistolica)# Verificar cambiossummary(datos_pacientes[, c("altura", "altura_limpia", "presion_sistolica", "presion_sistolica_limpia")])```### 3. Estandarización de categorías inconsistentesUn problema muy común en datos de salud es la inconsistencia en cómo se registran las categorías:```{r}# Ver las diferentes categorías en sexo y fumadortable(datos_pacientes$sexo, useNA ="ifany")table(datos_pacientes$fumador, useNA ="ifany")# Estandarizar la variable sexodatos_pacientes$sexo_std <-toupper(substr(datos_pacientes$sexo, 1, 1))datos_pacientes$sexo_std[!(datos_pacientes$sexo_std %in%c("M", "F"))] <-NAdatos_pacientes$sexo_std <-factor(datos_pacientes$sexo_std, levels =c("F", "M"))# Estandarizar la variable fumadordatos_pacientes$fumador_std <-toupper(substr(datos_pacientes$fumador, 1, 1))datos_pacientes$fumador_std[!(datos_pacientes$fumador_std %in%c("S", "N"))] <-NAdatos_pacientes$fumador_std <-factor(datos_pacientes$fumador_std, levels =c("N", "S"), labels =c("No", "Sí"))# Verificar las nuevas variables estandarizadastable(datos_pacientes$sexo_std, useNA ="ifany")table(datos_pacientes$fumador_std, useNA ="ifany")```::: {.callout-tip}## Herramientas para estandarizar variables categóricasAdemás de los métodos básicos mostrados, existen paquetes que facilitan la estandarización:1. **forcats**: Especializado en manipulación de factores```rlibrary(forcats)# Unir niveles similares datos$diagnostico <-fct_collapse(datos$diagnostico,"Cardiovascular"=c("Hipertensión", "Insuficiencia cardíaca", "Cardiopatía"),"Respiratorio"=c("EPOC", "Asma", "Neumonía") )```2. **stringdist**: Para encontrar coincidencias aproximadas```rlibrary(stringdist)# Encuentra el valor estándar más cercano estandarizar <-function(valor, estandares) {if (is.na(valor)) return(NA) i <-which.min(stringdist(valor, estandares))return(estandares[i]) }```3. **janitor**: Para limpiar nombres de variables```rlibrary(janitor) datos_limpios <-clean_names(datos) # estandariza nombres de columnas```:::### 4. Conversión a tipos de datos adecuadosEs importante asegurarse de que las variables tengan el tipo de dato correcto:```{r}# Convertir diagnóstico a factor (variable categórica)datos_pacientes$diagnostico <-factor(datos_pacientes$diagnostico)# Asegurarse de que fecha_ingreso es tipo fechaclass(datos_pacientes$fecha_ingreso)# En caso de que no fuera tipo fecha:# datos_pacientes$fecha_ingreso <- as.Date(datos_pacientes$fecha_ingreso)# Verificar la estructura actualizadastr(datos_pacientes[, c("diagnostico", "fecha_ingreso")])```### 5. Creación de variables derivadasA menudo necesitamos crear nuevas variables basadas en las existentes:```{r}# Crear variable IMC a partir de peso y alturadatos_pacientes$imc <- datos_pacientes$peso_limpio / (datos_pacientes$altura_limpia^2)# Categorizar el IMC según criterios estándardatos_pacientes$categoria_imc <-cut( datos_pacientes$imc,breaks =c(0, 18.5, 25, 30, Inf),labels =c("Bajo peso", "Normal", "Sobrepeso", "Obesidad"),right =FALSE)# Crear variable de mes de ingreso (útil para análisis temporales)datos_pacientes$mes_ingreso <-format(datos_pacientes$fecha_ingreso, "%m")datos_pacientes$mes_ingreso <-factor(datos_pacientes$mes_ingreso, levels =1:12,labels = month.abb) # Abreviaturas de meses# Verificar las nuevas variablessummary(datos_pacientes[, c("imc", "categoria_imc", "mes_ingreso")])```## Datos "tidy" (ordenados)El concepto de datos "tidy" o datos ordenados es fundamental en el análisis moderno con R. Según este principio, un conjunto de datos está ordenado cuando:1. Cada variable forma una columna2. Cada observación forma una fila3. Cada tipo de unidad observacional forma una tabla::: {.callout-note}## ¿Por qué es crucial el formato "tidy" en investigación sanitaria?El formato "tidy" es particularmente valioso en análisis de datos sanitarios por varias razones:1. **Facilita el análisis longitudinal**: Datos de seguimiento de pacientes a lo largo del tiempo son más manejables en formato largo.2. **Mejora la integración de datos**: Facilita la combinación de datos de diferentes fuentes (historias clínicas, registros de laboratorio, etc.).3. **Simplifica las visualizaciones**: Es más directo crear gráficos de múltiples variables con ggplot2 usando datos "tidy".4. **Optimiza el modelado estadístico**: La mayoría de funciones de modelado en R (lm(), glm(), survival::coxph()) esperan datos en formato "tidy".5. **Reduce errores de análisis**: La estructura consistente minimiza confusiones sobre qué representa cada valor.:::Veamos un ejemplo de datos que no están en formato "tidy" y cómo transformarlos:```{r}# Datos no tidy (wide format) - Mediciones de presión arterial en diferentes visitaspacientes_wide <-data.frame(id =1:5,nombre =c("Ana", "Beto", "Carmen", "Diego", "Elena"),visita1_sistolica =c(120, 135, 142, 118, 155),visita1_diastolica =c(80, 85, 95, 75, 90),visita2_sistolica =c(118, 133, 145, 120, 150),visita2_diastolica =c(78, 84, 97, 78, 88))pacientes_wide```Estos datos no están en formato "tidy" porque tenemos múltiples variables (presión sistólica y diastólica de diferentes visitas) esparcidas en varias columnas. Transformemos a formato "tidy":```{r}# Transformar a formato tidy (long format)library(tidyr)pacientes_long <- pacientes_wide %>%pivot_longer(cols =starts_with("visita"),names_to =c("visita", "tipo_presion"),names_pattern ="visita(.*)_(.*)",values_to ="valor" )pacientes_long```Ahora cada fila representa una observación única: un paciente, en una visita específica, con un tipo específico de medición. Este formato facilita muchos análisis.También podemos volver al formato original si es necesario:```{r}# Volver al formato widepacientes_wide_again <- pacientes_long %>%pivot_wider(names_from =c(visita, tipo_presion),names_prefix ="visita",names_sep ="_",values_from = valor )head(pacientes_wide_again)```## Introducción a dplyr para manipulación de datosdplyr es un paquete del tidyverse que proporciona un conjunto de funciones para manipular fácilmente data frames. Dos de sus funciones más básicas son `filter()` para seleccionar filas y `select()` para seleccionar columnas.### Filtrar filas con filter()```{r}library(dplyr)# Seleccionar solo pacientes mayores de 65 añospacientes_mayores <- datos_pacientes %>%filter(edad >65)# Ver cuántos pacientes cumplen el criterionrow(pacientes_mayores)# Filtros con múltiples condicionespacientes_riesgo <- datos_pacientes %>%filter(edad >50& fumador_std =="Sí"& presion_sistolica_limpia >140)# Ver cuántos pacientes están en este grupo de riesgonrow(pacientes_riesgo)# Excluir pacientes con valores faltantes en diagnósticopacientes_diagnostico_completo <- datos_pacientes %>%filter(!is.na(diagnostico))nrow(pacientes_diagnostico_completo)```### Seleccionar columnas con select()```{r}# Seleccionar solo algunas columnas de interésdatos_simplificados <- datos_pacientes %>%select(id, edad, sexo_std, diagnostico, imc, categoria_imc)head(datos_simplificados)# Seleccionar columnas por patróncolumnas_presion <- datos_pacientes %>%select(id, contains("presion"))head(columnas_presion)# Excluir columnasdatos_sin_originales <- datos_pacientes %>%select(-sexo, -fumador, -peso, -altura, -presion_sistolica, -presion_diastolica)# Ver qué columnas quedaronnames(datos_sin_originales)```### Combinar filter y selectPodemos encadenar operaciones con el operador pipe `%>%`:```{r}# Pacientes obesos con hipertensión, solo columnas relevantespacientes_obesos_hipertension <- datos_pacientes %>%filter(categoria_imc =="Obesidad"& diagnostico =="Hipertensión") %>%select(id, edad, sexo_std, imc, presion_sistolica_limpia, presion_diastolica)head(pacientes_obesos_hipertension)```## Ejercicio prácticoVamos a trabajar con datos de COVID-19 como ejemplo de un conjunto de datos real. Para este ejercicio, usaremos datos resumidos por país:```{r, message=FALSE, warning=FALSE, results='hide'}# Cargar datos de COVID-19 desde el paquete coronavirusif (!requireNamespace("coronavirus", quietly = TRUE)) { install.packages("coronavirus")}library(coronavirus)``````{r}# Usar datos del paquetedata(coronavirus)# Ver estructuraglimpse(coronavirus)```### Ejercicio: Preparar dataset para análisisRealiza las siguientes tareas:1. Filtrar solo los casos confirmados de marzo a mayo de 20202. Agregar casos por país y fecha3. Crear una variable para indicar si el país está en América Latina4. Identificar los 10 países con más casos acumulados en ese periodo::: {.callout-tip}## Aproximación paso a paso a problemas de datos complejosCuando te enfrentes a tareas de manipulación de datos complejas:1. **Comienza con subconjuntos pequeños**: Prueba tu código con un pequeño subconjunto de datos para verificar que funciona como esperas.2. **Divide la tarea en pasos más pequeños**: Resuelve y verifica cada subtarea antes de pasar a la siguiente.3. **Encadena operaciones con el pipe (`%>%`) cuando sea posible**: Esto hace que tu código sea más legible y evita la creación de objetos intermedios.4. **Guarda checkpoints intermedios**: En análisis complejos, guarda versiones intermedias de tus datos (ej. `datos_filtrados`, `datos_agregados`) para poder revisar y depurar.5. **Haz verificaciones de sanidad**: Confirma que tus transformaciones producen los resultados esperados (totales correctos, número de filas adecuado, etc.).:::```{r}# 1. Filtrar casos confirmados de marzo a mayo 2020covid_confirmados <- coronavirus %>%filter( type =="confirmed", date >=as.Date("2020-03-01"), date <=as.Date("2020-05-31") )# 2. Agregar casos por país y fechacovid_por_pais <- covid_confirmados %>%group_by(country, date) %>%summarise(casos_diarios =sum(cases, na.rm =TRUE), .groups ="drop")# 3. Crear variable para América Latinapaises_latam <-c("Brazil", "Mexico", "Argentina", "Chile", "Colombia", "Peru", "Ecuador", "Bolivia", "Venezuela", "Uruguay","Paraguay", "Cuba", "Dominican Republic", "Panama", "Costa Rica", "Guatemala", "Honduras", "El Salvador", "Nicaragua")covid_por_pais <- covid_por_pais %>%mutate(es_latam = country %in% paises_latam)# 4. Identificar los 10 países con más casos acumuladostop10_paises <- covid_por_pais %>%group_by(country) %>%summarise(casos_totales =sum(casos_diarios, na.rm =TRUE)) %>%arrange(desc(casos_totales)) %>%slice_head(n =10)# Ver los resultadostop10_paises```## Exportación de datos limpiosDespués de limpiar y transformar los datos, a menudo queremos guardarlos para análisis posteriores:```{r}# Exportar a CSVwrite_csv(datos_pacientes, "datos_pacientes_limpios.csv")# Exportar a Excelwritexl::write_xlsx(datos_pacientes, "datos_pacientes_limpios.xlsx")# Exportar a formato RDS (específico de R, mantiene tipos de datos)saveRDS(datos_pacientes, "datos_pacientes_limpios.rds")```::: {.callout-tip}El formato RDS es ideal para almacenar objetos de R porque preserva la estructura exacta y los tipos de datos, incluyendo factores y atributos. Para compartir datos con personas que no usan R, los formatos CSV o Excel son más adecuados.:::::: {.callout-important}## Documentación de la limpieza de datosEn investigación clínica y epidemiológica, es crucial documentar todos los pasos de limpieza y transformación para garantizar la reproducibilidad y transparencia:1. **Crea un "diario de limpieza"**: Documenta cada decisión tomada (por ejemplo, "Se eliminaron valores de presión arterial sistólica <60 y >250 mmHg por considerarse errores de entrada").2. **Guarda los datos en múltiples etapas**: - Datos crudos originales (nunca modificados) - Datos después de la limpieza básica - Datos finales para análisis3. **Script reproducible**: Asegúrate de que todo tu proceso de limpieza está en scripts que pueden ser ejecutados de principio a fin para recrear los datos finales.4. **Versiona tus datos**: Usa control de versiones (como Git) o al menos nombra tus archivos con fechas (ej. `datos_clinicos_limpios_20250319.csv`).Estos pasos no solo mejoran la calidad de la investigación, sino que también son cada vez más requeridos por revistas científicas y organismos financiadores.:::## Recursos adicionalesPara profundizar en la importación y limpieza de datos:- [R for Data Science - Capítulo 5](https://r4ds.had.co.nz/transform.html) - Transformación de datos- [R para Ciencia de Datos - Capítulo 5](https://es.r4ds.hadley.nz/transform.html) - Versión en español- [Data Wrangling Cheatsheet](https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf)- [Introduction to Data Cleaning with R](https://cran.r-project.org/doc/contrib/de_Jonge+van_der_Loo-Introduction_to_data_cleaning_with_R.pdf)## Para la próxima sesiónEn la próxima sesión profundizaremos en la manipulación de datos con el tidyverse, especialmente con dplyr y tidyr:- Usar `mutate()` para crear y transformar variables- Resumir datos con `summarise()` y `group_by()`- Unir tablas con funciones de join- Realizar operaciones más complejas de reestructuración de datos## ConclusionesEn esta sesión hemos aprendido:- Cómo importar datos desde diferentes formatos (CSV, Excel)- Técnicas para inspeccionar y comprender la estructura de un dataset- Métodos para identificar y corregir problemas comunes en datos crudos- El concepto de datos "tidy" y cómo transformar entre formatos- Funciones básicas de dplyr para filtrar y seleccionar datosLa limpieza de datos, aunque a veces tediosa, es un paso crucial que puede determinar la validez de todo el análisis posterior. En investigación en salud, donde las decisiones basadas en datos pueden afectar la vida de las personas, es especialmente importante garantizar la calidad de los datos antes de proceder con análisis estadísticos.¡Nos vemos en la próxima sesión!