1  Introducción a data.table

En este capítulo aprenderás
  • La filosofía y ventajas de data.table
  • Diferencias fundamentales con data.frame y dplyr
  • Tu primer data.table en acción
  • Configuración del entorno de trabajo

1.1 La Historia de data.table

data.table nació en 2006 de la frustración de Matt Dowle con la velocidad de operaciones sobre data.frame para datasets grandes. Su visión era simple pero revolucionaria: ¿por qué conformarse con segundos cuando puedes tener milisegundos?

Hoy, más de 15 años después, data.table es la referencia mundial para manipulación de datos de alta performance en R, usado por empresas como Facebook, Netflix, y miles de científicos de datos alrededor del mundo.

1.2 ¿Qué hace a data.table especial?

1.2.1 1. Es un data.frame mejorado 🔧

Un data.table es un data.frame. Esto significa:

# Creamos un data.table
dt <- data.table(x = 1:3, y = c("A", "B", "C"))

# Verificamos su herencia
class(dt)
#> [1] "data.table" "data.frame"
is.data.frame(dt)
#> [1] TRUE

Esta compatibilidad es crucial: puedes usar un data.table en cualquier lugar que espere un data.frame sin problemas.

1.2.2 2. Modificación por referencia 🧠

La característica más distintiva de data.table es su capacidad de modificar objetos por referencia, sin crear copias.

# R base: crea una copia completa
df <- data.frame(x = 1:1000000, y = rnorm(1000000))
df$z <- df$x + df$y  # ¡Copia completa en memoria!

# data.table: modificación in-place
dt <- data.table(x = 1:1000000, y = rnorm(1000000))
dt[, z := x + y]     # ¡Sin copias! Modificación directa
💡 Implicaciones de la Modificación por Referencia
  • Ventaja: Velocidad extrema y uso eficiente de memoria
  • Cuidado: Los cambios son permanentes, no hay “deshacer” automático
  • Solución: Usa copy() cuando necesites preservar el original

1.2.3 3. Sintaxis consistente y potente ✍️

Todo en data.table se reduce a una estructura simple pero poderosa:

# La estructura universal de data.table
DT[i, j, by]

# i = filas (WHERE)    - "¿Qué filas me interesan?"
# j = columnas (WHAT)  - "¿Qué quiero hacer?"  
# by = grupos (BY)     - "¿Agrupado por qué?"

1.3 Comparación: data.table vs. dplyr vs. R base

Veamos cómo se comparan las tres principales formas de manipular datos en R:

1.3.1 Tarea: Calcular la media de valor por categoria

# R base: verbose y lento
resultado_base <- aggregate(valor ~ categoria, data = datos, FUN = mean)
head(resultado_base)
#>   categoria    valor
#> 1         A 100.0053
#> 2         B 100.0914
#> 3         C 100.1848
#> 4         D 100.0232
#> 5         E 100.0879
# dplyr: legible pero más lento para datos grandes
resultado_dplyr <- datos %>%
  group_by(categoria) %>%
  summarise(media_valor = mean(valor), .groups = 'drop')

head(resultado_dplyr)
#> # A tibble: 5 × 2
#>   categoria media_valor
#>   <chr>           <dbl>
#> 1 A                100.
#> 2 B                100.
#> 3 C                100.
#> 4 D                100.
#> 5 E                100.
# data.table: conciso y ultra rápido
resultado_dt <- datos_dt[, .(media_valor = mean(valor)), by = categoria]
head(resultado_dt)
#>    categoria media_valor
#>       <char>       <num>
#> 1:         C    100.1848
#> 2:         B    100.0914
#> 3:         D    100.0232
#> 4:         A    100.0053
#> 5:         E    100.0879

1.3.2 Comparación de Rendimiento

# Comparamos velocidad (solo con muestra para el ejemplo)
if(nrow(datos) > 10000) {
  muestra <- slice_sample(datos, n = 10000)
  muestra_dt <- as.data.table(muestra)
  
  benchmark_resultado <- microbenchmark(
    "R Base" = aggregate(valor ~ categoria, data = muestra, FUN = mean),
    "dplyr" = muestra %>% group_by(categoria) %>% summarise(media = mean(valor), .groups = 'drop'),
    "data.table" = muestra_dt[, .(media = mean(valor)), by = categoria],
    times = 50
  )
  
  print(benchmark_resultado)
}
#> Unit: microseconds
#>        expr      min       lq      mean   median       uq      max neval
#>      R Base 2937.830 2993.684 3099.1619 3030.267 3085.305 4299.811    50
#>       dplyr 2419.343 2545.087 2810.3213 2608.751 2823.386 6635.326    50
#>  data.table  822.574  900.339  948.0281  931.908  968.456 1207.432    50
📊 Interpretando los Resultados

En datasets grandes (millones de filas), data.table puede ser 10-100x más rápido que R base y 2-10x más rápido que dplyr. La diferencia se amplifica con operaciones más complejas.

1.4 Tu Primer data.table

1.4.1 Creación Básica

Existen varias formas de crear un data.table:

# 1. Desde cero
mi_dt <- data.table(
  nombre = c("Ana", "Juan", "María", "Carlos"),
  edad = c(25, 30, 28, 35),
  ciudad = c("Madrid", "Barcelona", "Sevilla", "Valencia"),
  salario = c(35000, 42000, 38000, 50000)
)

print(mi_dt)
#>    nombre  edad    ciudad salario
#>    <char> <num>    <char>   <num>
#> 1:    Ana    25    Madrid   35000
#> 2:   Juan    30 Barcelona   42000
#> 3:  María    28   Sevilla   38000
#> 4: Carlos    35  Valencia   50000
# 2. Convirtiendo un data.frame existente
iris_dt <- as.data.table(iris)
print(head(iris_dt))
#>    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#>           <num>       <num>        <num>       <num>  <fctr>
#> 1:          5.1         3.5          1.4         0.2  setosa
#> 2:          4.9         3.0          1.4         0.2  setosa
#> 3:          4.7         3.2          1.3         0.2  setosa
#> 4:          4.6         3.1          1.5         0.2  setosa
#> 5:          5.0         3.6          1.4         0.2  setosa
#> 6:          5.4         3.9          1.7         0.4  setosa
# 3. Leyendo un archivo (súper rápido)
datos_csv <- fread("mi_archivo.csv")

1.4.2 Explorando tu data.table

# Información básica
dim(iris_dt)         # Dimensiones
#> [1] 150   5
names(iris_dt)       # Nombres de columnas
#> [1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width"  "Species"
str(iris_dt)         # Estructura
#> Classes 'data.table' and 'data.frame':   150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
#>  - attr(*, ".internal.selfref")=<externalptr>
# Estadísticas rápidas por grupo
iris_dt[, .N, by = Species]  # Conteo por especie
#>       Species     N
#>        <fctr> <int>
#> 1:     setosa    50
#> 2: versicolor    50
#> 3:  virginica    50

1.5 Configuración del Entorno

Para aprovechar al máximo data.table, configuremos nuestro entorno:

# 1. Configurar número de threads (para aprovechar múltiples núcleos)
setDTthreads(0)  # 0 = usar todos los núcleos disponibles
getDTthreads()   # Verificar configuración
#> [1] 4

# 2. Opciones de visualización
options(datatable.print.nrows = 10)     # Mostrar 10 filas
options(datatable.print.class = TRUE)   # Mostrar clases de columnas

# 3. Modo verboso para aprendizaje (opcional)
# options(datatable.verbose = TRUE)  # Descomenta para debug
🚀 Consejo de Performance

setDTthreads(0) utiliza todos los núcleos de CPU disponibles. En máquinas con muchos núcleos, esto puede acelerar operaciones hasta 4-8x. Para laptops o uso interactivo, considera setDTthreads(2) para mantener el sistema responsive.

1.6 Anatomía de la Sintaxis DT[i, j, by]

La sintaxis de data.table puede parecer intimidante al principio, pero es increíblemente lógica:

# Ejemplo con nuestros datos de empleados
print(mi_dt)
#>    nombre  edad    ciudad salario
#>    <char> <num>    <char>   <num>
#> 1:    Ana    25    Madrid   35000
#> 2:   Juan    30 Barcelona   42000
#> 3:  María    28   Sevilla   38000
#> 4: Carlos    35  Valencia   50000

# i: ¿QUÉ FILAS? (filtrado)
mi_dt[edad > 28]
#>    nombre  edad    ciudad salario
#>    <char> <num>    <char>   <num>
#> 1:   Juan    30 Barcelona   42000
#> 2: Carlos    35  Valencia   50000

# j: ¿QUÉ HACER? (selección/cálculo)
mi_dt[, .(nombre, salario)]
#>    nombre salario
#>    <char>   <num>
#> 1:    Ana   35000
#> 2:   Juan   42000
#> 3:  María   38000
#> 4: Carlos   50000

# by: ¿AGRUPADO POR QUÉ? (agrupación)
mi_dt[, mean(salario), by = ciudad]
#>       ciudad    V1
#>       <char> <num>
#> 1:    Madrid 35000
#> 2: Barcelona 42000
#> 3:   Sevilla 38000
#> 4:  Valencia 50000

1.7 Ejercicio Práctico: Tu Primera Experiencia

🏋️ Ejercicio 1: Explorando los datos de iris

Usando el data.table iris_dt que creamos:

  1. Filtra las filas donde Sepal.Length > 6.0
  2. Calcula la media de Petal.Width para cada Species
  3. Selecciona solo las columnas que empiecen con “Petal”

¡Intenta resolver cada parte antes de ver la solución!

# 1. Filtrar filas
iris_grandes <- iris_dt[Sepal.Length > 6.0]
print(nrow(iris_grandes))
#> [1] 61

# 2. Media de Petal.Width por Species
medias_petal <- iris_dt[, .(media_petal_width = mean(Petal.Width)), by = Species]
print(medias_petal)
#>       Species media_petal_width
#>        <fctr>             <num>
#> 1:     setosa             0.246
#> 2: versicolor             1.326
#> 3:  virginica             2.026

# 3. Seleccionar columnas que empiecen con "Petal"
columnas_petal <- iris_dt[, .SD, .SDcols = patterns("^Petal")]
print(head(columnas_petal))
#>    Petal.Length Petal.Width
#>           <num>       <num>
#> 1:          1.4         0.2
#> 2:          1.4         0.2
#> 3:          1.3         0.2
#> 4:          1.5         0.2
#> 5:          1.4         0.2
#> 6:          1.7         0.4

Explicación: - [Sepal.Length > 6.0] filtra filas por condición lógica - .(media_petal_width = mean(Petal.Width)) crea una nueva columna calculada - patterns("^Petal") usa regex para seleccionar columnas dinámicamente

1.8 Próximos Pasos

Has dado tus primeros pasos en el mundo de data.table. En el siguiente capítulo profundizaremos en cada componente de la sintaxis DT[i, j, by] y descubrirás el verdadero poder de esta herramienta.

1.8.1 Lo que viene

En el Capítulo 1 exploraremos: - Filtrado avanzado de filas (i) - Selección y transformación de columnas (j) - Agrupaciones poderosas (by) - Símbolos especiales como .N, .SD, etc.


🎯 Recuerda

data.table no es solo una herramienta más rápida - es una forma diferente de pensar sobre la manipulación de datos. La inversión inicial en aprender su sintaxis te pagará dividendos durante toda tu carrera como analista de datos.