R

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ícate

Detecció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.

Aprende R online

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

R

Introducción Y Entorno

Introducción A R

R

Introducción Y Entorno

Operadores

R

Sintaxis

Estructuras De Datos

R

Sintaxis

Funciones

R

Sintaxis

Estructuras De Control Iterativo

R

Sintaxis

Scopes Y Closures

R

Sintaxis

Estructuras De Control Condicional

R

Sintaxis

Funciones Anónimas

R

Sintaxis

Tipos De Datos Y Variables

R

Sintaxis

Sistema R6: Clases Referenciales Y Encapsulamiento

R

Programación Orientada A Objetos

Sistema S4: Clases Formales Y Validación

R

Programación Orientada A Objetos

Herencia Y Polimorfismo En R

R

Programación Orientada A Objetos

Sistemas De Oop En R

R

Programación Orientada A Objetos

Sistema S3: Clases Implícitas Y Métodos Genéricos

R

Programación Orientada A Objetos

Tidyverse Para Transformación De Datos

R

Manipulación De Datos

Lubridate Para Fechas Y Tiempo

R

Manipulación De Datos

Group_by Y Summarize Para Agrupación Y Resumen

R

Manipulación De Datos

Stringr Para Expresiones Regulares

R

Manipulación De Datos

Tidyr Para Limpieza De Valores Faltantes

R

Manipulación De Datos

Joins En R Para Combinación Y Relaciones De Tablas

R

Manipulación De Datos

Pivot_longer Y Pivot_wider Para Reestructuración

R

Manipulación De Datos

Mutate Y Transmute Para Transformación

R

Manipulación De Datos

Dplyr Para Filtrado Y Selección

R

Manipulación De Datos

Readr Y Read.csv Para Importar Datos

R

Manipulación De Datos

Gráficos Bivariantes En R

R

Visualización De Datos

Gráficos Univariantes En R

R

Visualización De Datos

Facetas En Ggplot2

R

Visualización De Datos

Personalización Y Temas

R

Visualización De Datos

Ggplot2 Para Visualización De Datos

R

Visualización De Datos

Gráficos Multivariantes En R

R

Visualización De Datos

Correlación En R

R

Estadística

Regresión Lineal En R

R

Estadística

Pruebas De Hipótesis En R

R

Estadística

Anova En R

R

Estadística

Estadística Descriptiva En R

R

Estadística

Accede GRATIS a R y certifícate

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.