Data frames en R: manipulación de datos con base R y data.table de alto rendimiento

El data frame es la estructura central del análisis de datos en R. Piensa en él como una tabla de base de datos en memoria: filas son observaciones, columnas son variables, y cada columna puede tener un tipo diferente (numérico, texto, fecha, lógico). Trabajar bien con data frames, tanto con base R como con paquetes de alto rendimiento como data.table, marca la diferencia entre un análisis que escala y uno que se atasca con datos grandes.

Data frames en base R

Crear un data frame en base R es directo:

# Crear data frame
df <- data.frame(
  nombre  = c("Ana", "Luis", "Marta", "Pedro"),
  edad    = c(28, 35, 31, 42),
  ciudad  = c("Madrid", "Barcelona", "Sevilla", "Valencia"),
  activo  = c(TRUE, FALSE, TRUE, TRUE),
  stringsAsFactors = FALSE  # por defecto FALSE desde R 4.0
)

# Acceso a columnas
df$nombre        # vector con los nombres
df[["edad"]]     # equivalente, más programático
df[, "ciudad"]   # con indexación matricial

# Acceso a filas
df[1, ]          # primera fila completa
df[df$activo, ]  # filas donde activo es TRUE
df[df$edad > 30, c("nombre", "edad")]  # filas y columnas seleccionadas

Base R tiene funciones de manipulación propias que conviene conocer aunque luego uses tidyverse o data.table: subset(), merge(), aggregate(), reshape(), tapply(). Son más verbosas pero no tienen dependencias externas y funcionan igual en cualquier entorno R.

Operaciones básicas con base R

# Añadir columna
df$score <- c(85, 72, 91, 68)

# Filtrar y ordenar
mayores_30 <- subset(df, edad > 30, select = c(nombre, edad, score))
ordenado   <- df[order(df$edad, decreasing = TRUE), ]

# Agregar por categoría
aggregate(score ~ activo, data = df, FUN = mean)

# Merge de dos tablas
departamentos <- data.frame(
  nombre     = c("Ana", "Marta", "Pedro"),
  depto      = c("IT", "Marketing", "IT")
)
merge(df, departamentos, by = "nombre", all.x = TRUE)

data.table: rendimiento para datos grandes

Cuando tienes más de un millón de filas, dplyr empieza a hacerse notar. data.table es el paquete de referencia para datos grandes en R: usa modificación por referencia (sin copiar la tabla completa en memoria), índices automáticos y una sintaxis compacta que recuerda a SQL. En benchmarks con 10-50 millones de filas es consistentemente más rápido que dplyr, a veces por un factor de 5 a 10.

library(data.table)

# Convertir data.frame a data.table
dt <- as.data.table(df)

# O crear directamente
dt <- data.table(
  id      = 1:1e6,
  grupo   = sample(letters[1:5], 1e6, replace = TRUE),
  valor   = rnorm(1e6, mean = 100, sd = 15)
)

La sintaxis de data.table sigue el patrón dt[i, j, by] donde i filtra filas, j opera sobre columnas y by agrupa:

# Filtrar filas (i)
dt[grupo == "a"]
dt[valor > 110]

# Seleccionar y calcular columnas (j)
dt[, .(grupo, valor)]                    # seleccionar
dt[, .(media = mean(valor), n = .N)]    # calcular
dt[, valor_norm := valor / max(valor)]  # añadir columna por referencia

# Agrupar (by)
resumen <- dt[, .(
  media = mean(valor),
  sd    = sd(valor),
  n     = .N
), by = grupo]

# Todo junto: filtrar + calcular + agrupar
dt[valor > 100,
   .(media_alta = mean(valor), count = .N),
   by = grupo][order(media_alta, decreasing = TRUE)]

Modificación por referencia: la gran ventaja

En R estándar, cuando modificas un objeto se crea una copia. Con data.table, el operador := modifica la tabla en su lugar sin copiarla, lo que ahorra memoria y tiempo en tablas grandes:

# Añadir varias columnas sin copiar la tabla
dt[, `:=`(
  valor_doble  = valor * 2,
  grupo_upper  = toupper(grupo),
  categoria    = ifelse(valor > 100, "alta", "baja")
)]

# Eliminar una columna por referencia
dt[, valor_doble := NULL]

# Claves para joins rápidos
setkey(dt, grupo)
dt["a"]  # filtrado ultra-rápido usando índice

Joins con data.table

# Crear dos tablas para join
pedidos <- data.table(
  cliente_id = c(1, 2, 1, 3, 2),
  producto   = c("A", "B", "C", "A", "A"),
  importe    = c(100, 200, 150, 300, 120)
)

clientes <- data.table(
  cliente_id = 1:3,
  nombre     = c("Ana", "Luis", "Marta"),
  region     = c("Norte", "Sur", "Norte")
)

setkey(pedidos,  cliente_id)
setkey(clientes, cliente_id)

# Left join
resultado <- clientes[pedidos]

# Calcular total por cliente y región
resultado[, .(total = sum(importe)), by = .(nombre, region)]

Cuándo usar cada uno

Base R está bien para conjuntos pequeños (hasta unas decenas de miles de filas) y cuando quieres cero dependencias. dplyr es la mejor opción para análisis exploratorio donde la legibilidad del código importa más que los microsegundos. data.table entra cuando los datos superan el millón de filas o cuando necesitas la modificación por referencia para no quedarte sin RAM.

Los tres pueden coexistir en el mismo proyecto. Un flujo habitual es leer y limpiar con dplyr, hacer los cálculos pesados con data.table y pasar el resultado de vuelta a tibble para visualizar con ggplot2. Para más sobre manipulación de datos puedes ver el artículo sobre tidyverse con dplyr y ggplot2, y para comparar con análisis SQL integrado echa un vistazo a DuckDB embebido.

Imagen: Pexels / Mikhail Nilov

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP