R
Tutorial R: tidyr para limpieza de valores faltantes
Aprende a detectar, eliminar e imputar valores faltantes en R con tidyr para mejorar la calidad de tus datos.
Aprende R y certifícateDetección y diagnóstico de NAs
Cuando trabajamos con datos reales, es común encontrarnos con valores faltantes (también conocidos como NAs en R). Antes de aplicar cualquier técnica de limpieza o análisis, necesitamos identificar y comprender la naturaleza de estos valores ausentes en nuestros datos.
El paquete tidyr, parte del ecosistema tidyverse, ofrece herramientas específicas para trabajar con valores faltantes de manera eficiente. Comenzaremos explorando cómo detectar y diagnosticar estos valores en nuestros conjuntos de datos.
Identificación básica de NAs
R representa los valores faltantes con el símbolo especial NA
. Para detectar si un valor es NA, podemos usar la función is.na()
:
# Vector con valores faltantes
edades <- c(25, NA, 30, NA, 22)
# Detectar cuáles son NA
is.na(edades)
Esta función devuelve un vector lógico donde TRUE
indica la presencia de un NA:
# Resultado
[1] FALSE TRUE FALSE TRUE FALSE
Para contar el número total de valores faltantes en un vector, podemos combinar is.na()
con la función sum()
:
# Contar NAs en el vector
sum(is.na(edades))
Esto nos devolverá 2
, indicando que hay dos valores faltantes en nuestro vector.
Diagnóstico de NAs en data frames
Cuando trabajamos con data frames, necesitamos herramientas más completas para diagnosticar los valores faltantes. Veamos un ejemplo con un conjunto de datos ficticio:
# Crear un data frame de ejemplo
datos <- data.frame(
id = 1:5,
nombre = c("Ana", "Carlos", NA, "Elena", "David"),
edad = c(25, NA, 30, NA, 22),
ciudad = c("Madrid", "Barcelona", NA, "Sevilla", "Valencia")
)
# Mostrar el data frame
datos
Para diagnosticar los valores faltantes en este data frame, podemos usar varias técnicas:
- 1. Verificar la presencia de NAs en todo el data frame:
# ¿Hay algún NA en el data frame?
any(is.na(datos))
- 2. Contar el número total de NAs:
# Total de NAs en el data frame
sum(is.na(datos))
- 3. Contar NAs por columna:
# NAs por columna
colSums(is.na(datos))
Esto nos mostrará un resumen como:
id nombre edad ciudad
0 1 2 1
- 4. Contar NAs por fila:
# NAs por fila
rowSums(is.na(datos))
Visualización de patrones de NAs
Para entender mejor la distribución de los valores faltantes, podemos crear visualizaciones. El paquete naniar
es especialmente útil para esto:
# Instalar si es necesario
# install.packages("naniar")
library(naniar)
# Visualizar el patrón de NAs
vis_miss(datos)
Esta función crea un gráfico de calor donde las celdas negras representan valores faltantes, permitiéndonos identificar patrones visualmente.
Análisis de la estructura de NAs
Es importante entender si los valores faltantes siguen algún patrón o si están distribuidos aleatoriamente:
# Examinar la estructura de los NAs
library(tidyr)
# Identificar filas con algún NA
datos %>%
mutate(tiene_na = if_any(everything(), is.na)) %>%
count(tiene_na)
También podemos examinar si hay correlación entre los valores faltantes de diferentes columnas:
# Correlación entre NAs de diferentes columnas
gg_miss_upset(datos)
Diagnóstico avanzado con tidyr
El paquete tidyr nos permite realizar diagnósticos más específicos:
library(tidyr)
library(dplyr)
# Identificar filas completas vs incompletas
datos %>%
group_by(complete.cases(datos)) %>%
tally()
También podemos filtrar para ver solo las filas con valores faltantes:
# Ver solo filas con algún NA
datos %>%
filter(!complete.cases(.))
O examinar los datos que faltan en una columna específica:
# Examinar filas donde falta la edad
datos %>%
filter(is.na(edad))
Comprobación de valores especiales
A veces, los valores faltantes no están codificados como NA
, sino como cadenas especiales como "N/A", "Missing" o códigos numéricos como -999. Podemos detectarlos así:
# Crear datos con valores especiales
datos_especiales <- data.frame(
id = 1:5,
valor = c(10, -999, 30, "Missing", 50)
)
# Convertir valores especiales a NA
datos_limpios <- datos_especiales %>%
mutate(valor = na_if(valor, "Missing"),
valor = na_if(valor, -999))
# Verificar la conversión
is.na(datos_limpios$valor)
Resumen estadístico con NAs
Al realizar análisis estadísticos, es útil entender cómo los NAs afectan nuestros resultados:
# Resumen estadístico que muestra NAs
summary(datos)
Esta función nos mostrará estadísticas básicas para cada columna, incluyendo el recuento de valores faltantes.
Para un análisis más detallado, podemos usar:
# Estadísticas por grupo, considerando NAs
datos %>%
group_by(ciudad) %>%
summarise(
n_registros = n(),
n_completos = sum(!is.na(edad)),
porcentaje_completo = mean(!is.na(edad)) * 100
)
La detección y diagnóstico adecuado de los valores faltantes es el primer paso crucial antes de decidir cómo tratarlos. En las siguientes secciones, exploraremos métodos para eliminar o imputar estos valores según nuestras necesidades analíticas.
Eliminación selectiva con drop_na() y filter()
Una vez que hemos identificado los valores faltantes en nuestros datos, a menudo necesitamos eliminarlos para poder realizar ciertos análisis. El paquete tidyr nos ofrece herramientas específicas para eliminar filas con valores NA de manera selectiva y controlada.
La eliminación de valores faltantes debe realizarse con criterio, ya que puede afectar significativamente el tamaño y la representatividad de nuestros datos. Veamos las principales funciones para esta tarea.
La función drop_na()
La función drop_na()
de tidyr nos permite eliminar filas que contienen valores NA, ya sea en todo el conjunto de datos o en columnas específicas:
library(tidyr)
library(dplyr)
# Crear un data frame de ejemplo
datos <- data.frame(
id = 1:6,
nombre = c("Ana", "Carlos", NA, "Elena", "David", "Lucía"),
edad = c(25, NA, 30, NA, 22, 35),
ciudad = c("Madrid", "Barcelona", NA, "Sevilla", "Valencia", NA),
salario = c(2500, 3000, NA, 2800, 2700, 3200)
)
# Mostrar los datos originales
datos
Eliminar filas con NA en cualquier columna
Para eliminar todas las filas que contengan al menos un valor NA en cualquier columna:
# Eliminar filas con cualquier NA
datos_completos <- datos %>%
drop_na()
# Ver resultado
datos_completos
Este enfoque es restrictivo y puede eliminar muchas observaciones si tenemos valores faltantes dispersos en varias columnas.
Eliminar filas con NA en columnas específicas
Una estrategia más selectiva es eliminar filas con NA solo en las columnas que son críticas para nuestro análisis:
# Eliminar filas donde falte la edad o el nombre
datos_filtrados <- datos %>%
drop_na(edad, nombre)
# Ver resultado
datos_filtrados
Este código elimina solo las filas donde faltan valores en las columnas edad
o nombre
, manteniendo aquellas donde pueden faltar datos en otras columnas como ciudad
o salario
.
Uso de filter() para control avanzado
Mientras que drop_na()
es ideal para casos simples, la función filter()
de dplyr nos ofrece un control más preciso sobre qué filas eliminar:
# Eliminar solo filas donde falte el salario
datos_con_salario <- datos %>%
filter(!is.na(salario))
# Ver resultado
datos_con_salario
La verdadera potencia de filter()
aparece cuando necesitamos aplicar condiciones complejas:
# Mantener filas donde:
# - El nombre no es NA, Y
# - O bien la edad no es NA, o bien la ciudad no es NA
datos_filtrados_complejos <- datos %>%
filter(
!is.na(nombre) &
(!is.na(edad) | !is.na(ciudad))
)
# Ver resultado
datos_filtrados_complejos
Este enfoque nos permite implementar reglas de negocio específicas sobre qué observaciones conservar.
Comparación entre drop_na() y filter()
Veamos una comparación directa entre ambos métodos:
- drop_na(): Más conciso y legible para casos simples
- filter(): Más flexible para condiciones complejas
# Mismo resultado con ambos métodos
# Usando drop_na()
resultado1 <- datos %>%
drop_na(edad)
# Usando filter()
resultado2 <- datos %>%
filter(!is.na(edad))
# Verificar que son idénticos
identical(resultado1, resultado2)
Estrategias para minimizar la pérdida de datos
Al eliminar filas con valores faltantes, corremos el riesgo de reducir excesivamente nuestro conjunto de datos. Algunas estrategias para minimizar este problema:
- 1. Eliminar solo en columnas críticas: Identifica qué variables son esenciales para tu análisis específico.
# Si solo necesitamos nombre y edad para nuestro análisis
analisis_demografico <- datos %>%
drop_na(nombre, edad) %>%
select(nombre, edad)
- 2. Calcular el porcentaje de datos que se conservan:
# Verificar cuántos datos conservamos
datos %>%
mutate(fila_completa = complete.cases(.)) %>%
summarise(
total_filas = n(),
filas_completas = sum(fila_completa),
porcentaje_conservado = mean(fila_completa) * 100
)
- 3. Eliminar columnas con demasiados NAs antes de eliminar filas:
# Identificar columnas con más de 30% de NAs
umbral_na <- 0.3
columnas_a_mantener <- datos %>%
summarise(across(everything(), ~mean(is.na(.)) < umbral_na)) %>%
select_if(~. == TRUE) %>%
names()
# Conservar solo esas columnas y luego eliminar filas con NA
datos_filtrados <- datos %>%
select(all_of(columnas_a_mantener)) %>%
drop_na()
Documentación de la eliminación de datos
Es una buena práctica documentar cuántos datos se eliminaron y por qué razones:
# Análisis de eliminación de datos
resumen_eliminacion <- datos %>%
summarise(
filas_originales = n(),
filas_sin_nombre = sum(is.na(nombre)),
filas_sin_edad = sum(is.na(edad)),
filas_sin_ciudad = sum(is.na(ciudad)),
filas_sin_salario = sum(is.na(salario))
)
print(resumen_eliminacion)
# Calcular cuántas filas quedarían después de drop_na()
filas_restantes <- sum(complete.cases(datos))
cat("Filas restantes después de drop_na():", filas_restantes,
"(", round(filas_restantes/nrow(datos)*100, 1), "%)")
Eliminación selectiva en grupos
A veces necesitamos aplicar diferentes criterios de eliminación según grupos en nuestros datos:
# Crear datos con grupos
datos_grupos <- datos %>%
mutate(departamento = c("Ventas", "IT", "Ventas", "RRHH", "IT", "RRHH"))
# Eliminar NAs en salario solo para departamento de Ventas
datos_filtrados_por_grupo <- datos_grupos %>%
group_by(departamento) %>%
group_modify(~{
if(first(.x$departamento) == "Ventas") {
return(drop_na(.x, salario))
} else {
return(.x)
}
}) %>%
ungroup()
La eliminación selectiva de valores faltantes es solo una de las estrategias disponibles para manejar NAs. En la siguiente sección, exploraremos métodos de imputación que nos permiten reemplazar los valores faltantes en lugar de eliminarlos, conservando así más información de nuestro conjunto de datos original.
Imputación con replace_na(), fill() y approx()
Cuando trabajamos con datos reales, eliminar filas con valores faltantes no siempre es la mejor estrategia, ya que podemos perder información valiosa. La imputación es una técnica alternativa que consiste en reemplazar los valores NA con estimaciones razonables, permitiéndonos conservar la estructura original de nuestros datos.
El paquete tidyr ofrece varias funciones específicas para imputar valores faltantes, cada una con diferentes enfoques y aplicaciones. Veamos las principales técnicas de imputación disponibles.
Imputación con valores constantes usando replace_na()
La función replace_na()
nos permite sustituir valores NA con un valor constante específico:
library(tidyr)
library(dplyr)
# Crear un data frame de ejemplo
datos <- data.frame(
id = 1:5,
nombre = c("Ana", NA, "Carlos", "Diana", NA),
edad = c(25, NA, 30, NA, 22),
ventas = c(1200, 950, NA, 1500, 1100)
)
# Mostrar los datos originales
datos
Para reemplazar los NA en una sola columna:
# Reemplazar NA en la columna edad con 28 (promedio hipotético)
datos_imputados <- datos %>%
replace_na(list(edad = 28))
# Ver resultado
datos_imputados
También podemos reemplazar valores NA en múltiples columnas simultáneamente:
# Reemplazar NA en varias columnas con valores diferentes
datos_imputados <- datos %>%
replace_na(list(
nombre = "Sin nombre",
edad = 28,
ventas = 1000
))
# Ver resultado
datos_imputados
Esta técnica es útil cuando tenemos un valor predeterminado lógico para sustituir los datos faltantes, como usar ceros para cantidades no registradas o la media para variables numéricas.
Imputación con valores calculados
En lugar de usar constantes arbitrarias, podemos calcular valores de reemplazo basados en los datos existentes:
# Calcular la media de edad y ventas (excluyendo NAs)
media_edad <- mean(datos$edad, na.rm = TRUE)
media_ventas <- mean(datos$ventas, na.rm = TRUE)
# Imputar con las medias calculadas
datos_imputados <- datos %>%
replace_na(list(
edad = media_edad,
ventas = media_ventas
))
# Ver resultado
datos_imputados
Podemos integrar este cálculo en un flujo de trabajo más compacto:
# Calcular e imputar en un solo flujo
datos_imputados <- datos %>%
mutate(
edad = replace_na(edad, mean(edad, na.rm = TRUE)),
ventas = replace_na(ventas, mean(ventas, na.rm = TRUE))
)
Propagación de valores con fill()
La función fill()
utiliza un enfoque diferente: propaga el último valor no-NA para rellenar los valores faltantes. Esto es especialmente útil para datos temporales o secuenciales:
# Crear datos con valores faltantes en serie temporal
datos_tiempo <- data.frame(
fecha = as.Date(c("2023-01-01", "2023-01-02", "2023-01-03", "2023-01-04", "2023-01-05")),
temperatura = c(22, NA, NA, 25, NA),
humedad = c(NA, 65, 70, NA, 75)
)
# Mostrar datos originales
datos_tiempo
Podemos rellenar los valores faltantes propagando el último valor válido hacia abajo:
# Rellenar NAs con el último valor no-NA (hacia abajo)
datos_rellenados <- datos_tiempo %>%
fill(temperatura, humedad)
# Ver resultado
datos_rellenados
Por defecto, fill()
propaga valores hacia abajo (.direction = "down"
), pero también podemos propagar hacia arriba o en ambas direcciones:
# Rellenar NAs con el siguiente valor no-NA (hacia arriba)
datos_rellenados_arriba <- datos_tiempo %>%
fill(temperatura, humedad, .direction = "up")
# Rellenar en ambas direcciones
datos_rellenados_ambos <- datos_tiempo %>%
fill(temperatura, humedad, .direction = "downup")
Esta técnica es ideal para series temporales donde los valores tienden a cambiar gradualmente, como datos meteorológicos, financieros o de sensores.
Interpolación con approx()
Para datos numéricos, especialmente series temporales, la interpolación puede proporcionar estimaciones más precisas que simplemente propagar el último valor. La función approx()
del paquete base de R nos permite realizar interpolación lineal:
# Crear datos con valores faltantes en serie temporal
datos_serie <- data.frame(
tiempo = 1:10,
valor = c(5, 7, NA, NA, 12, 13, NA, 15, NA, 20)
)
# Mostrar datos originales
datos_serie
Podemos usar approx()
para interpolar los valores faltantes:
# Identificar posiciones con valores no-NA
posiciones_validas <- which(!is.na(datos_serie$valor))
valores_validos <- datos_serie$valor[posiciones_validas]
tiempos_validos <- datos_serie$tiempo[posiciones_validas]
# Realizar interpolación lineal
interpolacion <- approx(
x = tiempos_validos,
y = valores_validos,
xout = datos_serie$tiempo
)
# Reemplazar la columna original con los valores interpolados
datos_serie$valor_interpolado <- interpolacion$y
# Ver resultado
datos_serie
Para integrar esto en un flujo de trabajo tidyverse, podemos crear una función auxiliar:
# Función para interpolar NAs en una columna
interpolar_na <- function(x, pos = seq_along(x)) {
if(all(is.na(x))) return(x) # Si todos son NA, devolver como está
validos <- !is.na(x)
approx(pos[validos], x[validos], pos)$y
}
# Aplicar a nuestros datos
datos_interpolados <- datos_serie %>%
mutate(valor_interpolado = interpolar_na(valor))
La interpolación lineal asume una relación lineal entre puntos consecutivos, lo que puede ser razonable para muchos tipos de datos continuos.
Combinando estrategias de imputación
En la práctica, a menudo necesitamos combinar diferentes estrategias de imputación según el tipo de variable y el contexto:
# Datos con diferentes tipos de variables
datos_mixtos <- data.frame(
id = 1:6,
categoria = c("A", NA, "B", "A", NA, "C"),
valor1 = c(10, NA, 30, NA, 50, NA),
valor2 = c(NA, 15, NA, 35, NA, 55)
)
# Aplicar diferentes estrategias de imputación
datos_procesados <- datos_mixtos %>%
# Para categorías: usar valor más frecuente
mutate(categoria = replace_na(categoria, names(which.max(table(categoria))))) %>%
# Para valor1: usar interpolación
mutate(valor1 = interpolar_na(valor1)) %>%
# Para valor2: propagar valores
fill(valor2, .direction = "updown")
Imputación condicional
A veces queremos aplicar diferentes estrategias de imputación según ciertas condiciones o grupos en nuestros datos:
# Datos con grupos
datos_grupos <- data.frame(
grupo = rep(c("X", "Y"), each = 4),
valor = c(10, NA, 30, NA, 100, NA, 300, NA)
)
# Imputación diferente por grupo
datos_imputados_grupos <- datos_grupos %>%
group_by(grupo) %>%
mutate(
# Imputar con la media del grupo
valor_imputado = replace_na(valor, mean(valor, na.rm = TRUE))
) %>%
ungroup()
Consideraciones para la imputación
Al realizar imputaciones, debemos tener en cuenta algunas consideraciones importantes:
- Transparencia: Es recomendable mantener las columnas originales junto con las imputadas para poder rastrear los cambios.
datos_transparentes <- datos %>%
mutate(
edad_original = edad,
edad = replace_na(edad, mean(edad, na.rm = TRUE))
)
- Validación: Verificar que los valores imputados sean razonables y no distorsionen los análisis posteriores.
# Comparar distribuciones antes y después de imputar
summary(datos$edad) # Original con NAs
summary(datos_imputados$edad) # Después de imputar
- Documentación: Registrar qué método de imputación se utilizó para cada variable.
# Crear un registro de imputaciones
registro_imputacion <- tibble(
variable = c("edad", "ventas", "nombre"),
metodo = c("media", "media", "constante"),
valor_imputado = c(media_edad, media_ventas, "Sin nombre"),
n_imputados = c(sum(is.na(datos$edad)), sum(is.na(datos$ventas)), sum(is.na(datos$nombre)))
)
La imputación de valores faltantes es una técnica poderosa que nos permite conservar la estructura de nuestros datos mientras manejamos los valores NA. Sin embargo, es importante recordar que los valores imputados son estimaciones y no datos reales, por lo que debemos ser cautelosos al interpretar los resultados de análisis basados en datos imputados.
Otros ejercicios de programación de R
Evalúa tus conocimientos de esta lección tidyr para limpieza de valores faltantes con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Todas las lecciones de R
Accede a todas las lecciones de R y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Instalación De R Y Rstudio
Introducción Y Entorno
Introducción A R
Introducción Y Entorno
Operadores
Sintaxis
Estructuras De Datos
Sintaxis
Funciones
Sintaxis
Estructuras De Control Iterativo
Sintaxis
Scopes Y Closures
Sintaxis
Estructuras De Control Condicional
Sintaxis
Funciones Anónimas
Sintaxis
Tipos De Datos Y Variables
Sintaxis
Sistema R6: Clases Referenciales Y Encapsulamiento
Programación Orientada A Objetos
Sistema S4: Clases Formales Y Validación
Programación Orientada A Objetos
Herencia Y Polimorfismo En R
Programación Orientada A Objetos
Sistemas De Oop En R
Programación Orientada A Objetos
Sistema S3: Clases Implícitas Y Métodos Genéricos
Programación Orientada A Objetos
Tidyverse Para Transformación De Datos
Manipulación De Datos
Lubridate Para Fechas Y Tiempo
Manipulación De Datos
Group_by Y Summarize Para Agrupación Y Resumen
Manipulación De Datos
Stringr Para Expresiones Regulares
Manipulación De Datos
Tidyr Para Limpieza De Valores Faltantes
Manipulación De Datos
Joins En R Para Combinación Y Relaciones De Tablas
Manipulación De Datos
Pivot_longer Y Pivot_wider Para Reestructuración
Manipulación De Datos
Mutate Y Transmute Para Transformación
Manipulación De Datos
Dplyr Para Filtrado Y Selección
Manipulación De Datos
Readr Y Read.csv Para Importar Datos
Manipulación De Datos
Gráficos Bivariantes En R
Visualización De Datos
Gráficos Univariantes En R
Visualización De Datos
Facetas En Ggplot2
Visualización De Datos
Personalización Y Temas
Visualización De Datos
Ggplot2 Para Visualización De Datos
Visualización De Datos
Gráficos Multivariantes En R
Visualización De Datos
Correlación En R
Estadística
Regresión Lineal En R
Estadística
Pruebas De Hipótesis En R
Estadística
Anova En R
Estadística
Estadística Descriptiva En R
Estadística
En esta lección
Objetivos de aprendizaje de esta lección
- Identificar y diagnosticar valores faltantes en vectores y data frames.
- Visualizar patrones y estructuras de valores faltantes para comprender su distribución.
- Aplicar técnicas de eliminación selectiva de filas con valores NA usando drop_na() y filter().
- Realizar imputación de valores faltantes mediante replace_na(), fill() e interpolación con approx().
- Implementar estrategias combinadas y condicionales para el manejo adecuado de NAs en análisis de datos.