2  La Sintaxis DT[i, j, by]

En este capítulo dominarás
  • La estructura fundamental DT[i, j, by]
  • Filtrado avanzado de filas con i
  • Selección y transformación de columnas con j
  • Agrupaciones poderosas con by
  • Combinación efectiva de los tres componentes

2.1 La Sintaxis DT[i, j, by]: El Corazón de data.table

Todo en data.table gira alrededor de esta estructura simple pero poderosa. Piensa en ella como una pregunta en tres partes que le haces a tus datos:

# La estructura universal
DT[i,           j,              by]
   ↓            ↓               ↓
 ¿Dónde?    ¿Qué hacer?    ¿Agrupado por?
(filtros)   (seleccionar/   (variables de
           transformar)      agrupación)

Cada componente es opcional, lo que hace la sintaxis extremadamente flexible.

2.2 El Componente i: Selección y Filtrado de Filas

2.2.1 1. Filtrado Básico por Condiciones

# Empleados con salario > 45000
empleados[salario > 45000]
#>       id nombre departamento salario años_exp fecha_ingreso
#>    <int> <char>       <char>   <num>    <num>        <Date>
#> 1:     4 Carlos           IT   50000        8    2015-09-20
#> 2:     6  Pedro           IT   55000       10    2013-04-12
#> 3:     8 Miguel    Marketing   47000        7    2017-08-15
#> 4:    10  Jorge         RRHH   48000        9    2014-12-01
#> 5:    12  Diego         RRHH   52000        8    2016-01-25
# Múltiples condiciones: IT con más de 5 años de experiencia
empleados[departamento == "IT" & años_exp > 5]
#>       id nombre departamento salario años_exp fecha_ingreso
#>    <int> <char>       <char>   <num>    <num>        <Date>
#> 1:     4 Carlos           IT   50000        8    2015-09-20
#> 2:     5  Lucía           IT   45000        6    2018-11-05
#> 3:     6  Pedro           IT   55000       10    2013-04-12
# Usando %in% para múltiples valores
empleados[departamento %in% c("Ventas", "Marketing")]
#>       id nombre departamento salario años_exp fecha_ingreso
#>    <int> <char>       <char>   <num>    <num>        <Date>
#> 1:     1    Ana       Ventas   35000        2    2022-01-15
#> 2:     2   Juan       Ventas   42000        5    2019-03-10
#> 3:     3  María       Ventas   38000        3    2021-07-01
#> 4:     7 Isabel    Marketing   40000        4    2020-02-28
#> 5:     8 Miguel    Marketing   47000        7    2017-08-15
#> 6:     9 Carmen    Marketing   41000        3    2021-10-03

2.2.2 2. Filtrado por Posición

# Primeras 3 filas
empleados[1:3]
#>       id nombre departamento salario años_exp fecha_ingreso
#>    <int> <char>       <char>   <num>    <num>        <Date>
#> 1:     1    Ana       Ventas   35000        2    2022-01-15
#> 2:     2   Juan       Ventas   42000        5    2019-03-10
#> 3:     3  María       Ventas   38000        3    2021-07-01

# Última fila usando .N (número total de filas)
empleados[.N]
#>       id nombre departamento salario años_exp fecha_ingreso
#>    <int> <char>       <char>   <num>    <num>        <Date>
#> 1:    12  Diego         RRHH   52000        8    2016-01-25

# Últimas 3 filas
empleados[(.N-2):.N]
#>       id nombre departamento salario años_exp fecha_ingreso
#>    <int> <char>       <char>   <num>    <num>        <Date>
#> 1:    10  Jorge         RRHH   48000        9    2014-12-01
#> 2:    11  Laura         RRHH   44000        5    2019-06-18
#> 3:    12  Diego         RRHH   52000        8    2016-01-25

2.2.3 3. Filtrado por Orden

# Ordenar por salario (ascendente)
empleados[order(salario)]
#>        id nombre departamento salario años_exp fecha_ingreso
#>     <int> <char>       <char>   <num>    <num>        <Date>
#>  1:     1    Ana       Ventas   35000        2    2022-01-15
#>  2:     3  María       Ventas   38000        3    2021-07-01
#>  3:     7 Isabel    Marketing   40000        4    2020-02-28
#>  4:     9 Carmen    Marketing   41000        3    2021-10-03
#>  5:     2   Juan       Ventas   42000        5    2019-03-10
#> ---                                                         
#>  8:     8 Miguel    Marketing   47000        7    2017-08-15
#>  9:    10  Jorge         RRHH   48000        9    2014-12-01
#> 10:     4 Carlos           IT   50000        8    2015-09-20
#> 11:    12  Diego         RRHH   52000        8    2016-01-25
#> 12:     6  Pedro           IT   55000       10    2013-04-12

# Ordenar por salario descendente
empleados[order(-salario)]
#>        id nombre departamento salario años_exp fecha_ingreso
#>     <int> <char>       <char>   <num>    <num>        <Date>
#>  1:     6  Pedro           IT   55000       10    2013-04-12
#>  2:    12  Diego         RRHH   52000        8    2016-01-25
#>  3:     4 Carlos           IT   50000        8    2015-09-20
#>  4:    10  Jorge         RRHH   48000        9    2014-12-01
#>  5:     8 Miguel    Marketing   47000        7    2017-08-15
#> ---                                                         
#>  8:     2   Juan       Ventas   42000        5    2019-03-10
#>  9:     9 Carmen    Marketing   41000        3    2021-10-03
#> 10:     7 Isabel    Marketing   40000        4    2020-02-28
#> 11:     3  María       Ventas   38000        3    2021-07-01
#> 12:     1    Ana       Ventas   35000        2    2022-01-15

# Ordenar por múltiples columnas
empleados[order(departamento, -salario)]
#>        id nombre departamento salario años_exp fecha_ingreso
#>     <int> <char>       <char>   <num>    <num>        <Date>
#>  1:     6  Pedro           IT   55000       10    2013-04-12
#>  2:     4 Carlos           IT   50000        8    2015-09-20
#>  3:     5  Lucía           IT   45000        6    2018-11-05
#>  4:     8 Miguel    Marketing   47000        7    2017-08-15
#>  5:     9 Carmen    Marketing   41000        3    2021-10-03
#> ---                                                         
#>  8:    10  Jorge         RRHH   48000        9    2014-12-01
#>  9:    11  Laura         RRHH   44000        5    2019-06-18
#> 10:     2   Juan       Ventas   42000        5    2019-03-10
#> 11:     3  María       Ventas   38000        3    2021-07-01
#> 12:     1    Ana       Ventas   35000        2    2022-01-15
💡 Consejo: Filtrado Eficiente

data.table optimiza automáticamente muchas operaciones de filtrado. Para datasets enormes, considera usar setkey() para acelerar filtros repetitivos (lo veremos en capítulos posteriores).

2.3 El Componente j: El Poder de la Transformación

2.3.1 1. Selección Simple de Columnas

# Seleccionar una columna (devuelve vector)
empleados[, salario]
#>  [1] 35000 42000 38000 50000 45000 55000 40000 47000 41000 48000 44000 52000

# Seleccionar una columna (devolver data.table)
empleados[, .(salario)]
#>     salario
#>       <num>
#>  1:   35000
#>  2:   42000
#>  3:   38000
#>  4:   50000
#>  5:   45000
#> ---        
#>  8:   47000
#>  9:   41000
#> 10:   48000
#> 11:   44000
#> 12:   52000

# Múltiples columnas
empleados[, .(nombre, salario, departamento)]
#>     nombre salario departamento
#>     <char>   <num>       <char>
#>  1:    Ana   35000       Ventas
#>  2:   Juan   42000       Ventas
#>  3:  María   38000       Ventas
#>  4: Carlos   50000           IT
#>  5:  Lucía   45000           IT
#> ---                            
#>  8: Miguel   47000    Marketing
#>  9: Carmen   41000    Marketing
#> 10:  Jorge   48000         RRHH
#> 11:  Laura   44000         RRHH
#> 12:  Diego   52000         RRHH

2.3.2 2. Creación de Nuevas Columnas

# Crear columna calculada
empleados[, .(nombre, salario, salario_anual = salario * 12)]
#>     nombre salario salario_anual
#>     <char>   <num>         <num>
#>  1:    Ana   35000        420000
#>  2:   Juan   42000        504000
#>  3:  María   38000        456000
#>  4: Carlos   50000        600000
#>  5:  Lucía   45000        540000
#> ---                             
#>  8: Miguel   47000        564000
#>  9: Carmen   41000        492000
#> 10:  Jorge   48000        576000
#> 11:  Laura   44000        528000
#> 12:  Diego   52000        624000

# Múltiples cálculos
empleados[, .(
  nombre,
  salario_mensual = salario,
  salario_anual = salario * 12,
  salario_por_año_exp = round(salario / años_exp, 0)
)]
#>     nombre salario_mensual salario_anual salario_por_año_exp
#>     <char>           <num>         <num>               <num>
#>  1:    Ana           35000        420000               17500
#>  2:   Juan           42000        504000                8400
#>  3:  María           38000        456000               12667
#>  4: Carlos           50000        600000                6250
#>  5:  Lucía           45000        540000                7500
#> ---                                                         
#>  8: Miguel           47000        564000                6714
#>  9: Carmen           41000        492000               13667
#> 10:  Jorge           48000        576000                5333
#> 11:  Laura           44000        528000                8800
#> 12:  Diego           52000        624000                6500

2.3.3 3. Funciones de Agregación

# Estadísticas básicas
empleados[, .(
  salario_promedio = mean(salario),
  salario_mediana = median(salario),
  salario_max = max(salario),
  salario_min = min(salario),
  total_empleados = .N
)]
#>    salario_promedio salario_mediana salario_max salario_min total_empleados
#>               <num>           <num>       <num>       <num>           <int>
#> 1:            44750           44500       55000       35000              12

2.3.4 4. El Símbolo Especial .N

.N es una variable especial que representa el número de filas en el grupo actual:

# Contar total de filas
empleados[, .N]
#> [1] 12

# Contar empleados por departamento
empleados[, .N, by = departamento]
#>    departamento     N
#>          <char> <int>
#> 1:       Ventas     3
#> 2:           IT     3
#> 3:    Marketing     3
#> 4:         RRHH     3

# Usar .N en cálculos
empleados[, .(
  empleados_total = .N,
  salario_promedio = mean(salario)
)]
#>    empleados_total salario_promedio
#>              <int>            <num>
#> 1:              12            44750

2.4 El Componente by: Agrupaciones Poderosas

2.4.1 1. Agrupación Simple

# Estadísticas por departamento
empleados[, .(
  empleados = .N,
  salario_promedio = round(mean(salario), 0),
  salario_total = sum(salario)
), by = departamento]
#>    departamento empleados salario_promedio salario_total
#>          <char>     <int>            <num>         <num>
#> 1:       Ventas         3            38333        115000
#> 2:           IT         3            50000        150000
#> 3:    Marketing         3            42667        128000
#> 4:         RRHH         3            48000        144000

2.4.2 2. Agrupación por Múltiples Variables

# Crear categorías de experiencia para el ejemplo
empleados[, categoria_exp := ifelse(años_exp <= 5, "Junior", "Senior")]

# Agrupar por departamento y categoría de experiencia
empleados[, .(
  empleados = .N,
  salario_promedio = round(mean(salario), 0)
), by = .(departamento, categoria_exp)]
#>    departamento categoria_exp empleados salario_promedio
#>          <char>        <char>     <int>            <num>
#> 1:       Ventas        Junior         3            38333
#> 2:           IT        Senior         3            50000
#> 3:    Marketing        Junior         2            40500
#> 4:    Marketing        Senior         1            47000
#> 5:         RRHH        Senior         2            50000
#> 6:         RRHH        Junior         1            44000

2.4.3 3. Agrupación con Expresiones

# Agrupar por rangos de salario calculados sobre la marcha
empleados[, .(
  empleados = .N,
  salario_promedio = round(mean(salario), 0)
), by = .(rango_salario = ifelse(salario > 45000, "Alto", "Medio-Bajo"))]
#>    rango_salario empleados salario_promedio
#>           <char>     <int>            <num>
#> 1:    Medio-Bajo         7            40714
#> 2:          Alto         5            50400

2.5 Combinando los Tres Componentes

La verdadera potencia surge cuando combinas i, j, y by:

# Filtrar empleados con > 4 años exp, calcular stats por departamento
empleados[años_exp > 4, .(
  empleados_experimentados = .N,
  salario_promedio = round(mean(salario), 0),
  años_exp_promedio = round(mean(años_exp), 1)
), by = departamento]
#>    departamento empleados_experimentados salario_promedio años_exp_promedio
#>          <char>                    <int>            <num>             <num>
#> 1:       Ventas                        1            42000               5.0
#> 2:           IT                        3            50000               8.0
#> 3:    Marketing                        1            47000               7.0
#> 4:         RRHH                        3            48000               7.3

2.6 Ejercicios Prácticos

🏋️ Ejercicio 1: Dominar la Sintaxis Básica

Usando el dataset empleados, resuelve:

  1. Filtrado: Empleados de “Ventas” o “IT” con salario > 40000
  2. Agregación: Por cada departamento, calcula: empleados totales, salario promedio, y años de experiencia máximo
  3. Combinado: Encuentra empleados con más años de experiencia que el promedio de su departamento
# 1. Filtrado avanzado
empleados_filtrados <- empleados[
  departamento %in% c("Ventas", "IT") & salario > 40000
]
print(empleados_filtrados)
#>       id nombre departamento salario años_exp fecha_ingreso categoria_exp
#>    <int> <char>       <char>   <num>    <num>        <Date>        <char>
#> 1:     2   Juan       Ventas   42000        5    2019-03-10        Junior
#> 2:     4 Carlos           IT   50000        8    2015-09-20        Senior
#> 3:     5  Lucía           IT   45000        6    2018-11-05        Senior
#> 4:     6  Pedro           IT   55000       10    2013-04-12        Senior

# 2. Agregación por departamento
stats_dept <- empleados[, .(
  total_empleados = .N,
  salario_promedio = round(mean(salario), 0),
  años_exp_maximo = max(años_exp)
), by = departamento]
print(stats_dept)
#>    departamento total_empleados salario_promedio años_exp_maximo
#>          <char>           <int>            <num>           <num>
#> 1:       Ventas               3            38333               5
#> 2:           IT               3            50000              10
#> 3:    Marketing               3            42667               7
#> 4:         RRHH               3            48000               9

# 3. Empleados por encima del promedio de experiencia en su departamento
empleados_promedio_exp <- empleados[, exp_promedio_dept := mean(años_exp), by = departamento][
  años_exp > exp_promedio_dept, 
  .(nombre, departamento, años_exp, exp_promedio_dept)
]
print(empleados_promedio_exp)
#>    nombre departamento años_exp exp_promedio_dept
#>    <char>       <char>    <num>             <num>
#> 1:   Juan       Ventas        5          3.333333
#> 2:  Pedro           IT       10          8.000000
#> 3: Miguel    Marketing        7          4.666667
#> 4:  Jorge         RRHH        9          7.333333
#> 5:  Diego         RRHH        8          7.333333

2.7 Patrones Comunes y Mejores Prácticas

2.7.1 1. Piensa en SQL

Si conoces SQL, data.table te resultará familiar:

# SQL: SELECT departamento, AVG(salario) FROM empleados WHERE salario > 40000 GROUP BY departamento
# data.table:
empleados[salario > 40000, .(salario_promedio = mean(salario)), by = departamento]

2.7.2 2. Usa .() para Múltiples Columnas

.() es tu amigo para selecciones y cálculos múltiples:

# ✅ Correcto: múltiples columnas con nombres descriptivos
empleados[, .(
  empleado = nombre,
  dept = departamento,
  sueldo = salario,
  experiencia = años_exp
)]
#>     empleado      dept sueldo experiencia
#>       <char>    <char>  <num>       <num>
#>  1:      Ana    Ventas  35000           2
#>  2:     Juan    Ventas  42000           5
#>  3:    María    Ventas  38000           3
#>  4:   Carlos        IT  50000           8
#>  5:    Lucía        IT  45000           6
#> ---                                      
#>  8:   Miguel Marketing  47000           7
#>  9:   Carmen Marketing  41000           3
#> 10:    Jorge      RRHH  48000           9
#> 11:    Laura      RRHH  44000           5
#> 12:    Diego      RRHH  52000           8

2.7.3 3. Combina Operaciones de Forma Legible

# Ejemplo complejo pero legible
analisis_empleados <- empleados[
  años_exp > 3,                     # i: filtrar por experiencia
  .(                                # j: calcular métricas
    empleados = .N,
    salario_promedio = round(mean(salario), 0),
    experiencia_total = sum(años_exp),
    salario_max = max(salario)
  ),
  by = departamento                 # by: agrupar por departamento
][order(-salario_promedio)]         # ordenar por salario desc

print(analisis_empleados)
#>    departamento empleados salario_promedio experiencia_total salario_max
#>          <char>     <int>            <num>             <num>       <num>
#> 1:           IT         3            50000                24       55000
#> 2:         RRHH         3            48000                22       52000
#> 3:    Marketing         2            43500                11       47000
#> 4:       Ventas         1            42000                 5       42000

2.8 Comparación de Performance

Veamos cómo la sintaxis data.table se compara con alternativas:

# Preparar datos para comparación
library(microbenchmark)
ventas_sample <- ventas[sample(.N, 10000)]  # Muestra para benchmark

# Comparar diferentes aproximaciones
benchmark_sintaxis <- microbenchmark(
  "data.table" = ventas_sample[precio > 100, .(avg_precio = mean(precio)), by = categoria],
  "base R" = aggregate(precio ~ categoria, ventas_sample[ventas_sample$precio > 100, ], mean),
  times = 20
)

print(benchmark_sintaxis)
#> Unit: milliseconds
#>        expr      min       lq     mean   median       uq      max neval
#>  data.table 1.012829 1.175773 1.260604 1.238780 1.365142 1.658243    20
#>      base R 3.681327 3.750626 4.768993 5.181716 5.331475 7.961470    20

2.9 Próximo Capítulo: Símbolos Especiales

En el siguiente capítulo profundizaremos en: - .SD: El subset de datos por grupo - .SDcols: Control granular de columnas - .I y .GRP: Índices y identificadores de grupo - Casos de uso avanzados de estos símbolos


🎯 Puntos Clave de Este Capítulo
  1. DT[i, j, by] es la sintaxis universal - domínala y dominarás data.table
  2. i filtra filas - usa condiciones lógicas, posiciones, u ordenamiento
  3. j selecciona/calcula - usa .() para múltiples columnas con nombres
  4. by agrupa datos - puede usar múltiples variables o expresiones
  5. .N cuenta filas - funciona en contexto general o por grupo
  6. Combina libremente - la flexibilidad es el superpoder de data.table

Con estos fundamentos sólidos de la sintaxis DT[i, j, by], estás listo para explorar los símbolos especiales que hacen de data.table una herramienta verdaderamente poderosa.