# Operaciones separadas (tradicional)
paso1 <- empleados[salario > 45000] # Filtrar
paso2 <- paso1[, .(empleados = .N, salario_promedio = mean(salario)), by = departamento] # Agrupar
resultado_separado <- paso2[order(-salario_promedio)] # Ordenar
# Misma operación encadenada
resultado_encadenado <- empleados[
salario > 45000
][
, .(empleados = .N, salario_promedio = round(mean(salario), 0)), by = departamento
][
order(-salario_promedio)
]
print("Resultado con encadenamiento:")
#> [1] "Resultado con encadenamiento:"
print(resultado_encadenado)
#> departamento empleados salario_promedio
#> <char> <int> <num>
#> 1: Ventas 3 74700
#> 2: RRHH 3 68933
#> 3: Finanzas 3 63533
#> 4: Marketing 4 61750
#> 5: IT 3 616334 Encadenamiento de Operaciones (Chaining)
4.1 Conceptos Fundamentales del Encadenamiento
El encadenamiento en data.table permite ejecutar múltiples operaciones secuenciales en una sola expresión usando la sintaxis DT[...][...][...]. Cada conjunto de corchetes opera sobre el resultado del anterior.
4.1.1 1. Encadenamiento Básico
4.1.2 2. Ventajas del Encadenamiento
# Ejemplo que muestra las ventajas
analisis_productividad <- empleados[
activo == TRUE & años_exp >= 3 # Solo empleados activos con experiencia
][
, productividad_ajustada := pmin(productividad, 5) # Ajustar outliers
][
, .(
empleados = .N,
productividad_media = round(mean(productividad_ajustada), 2),
salario_total_grupo = sum(salario_total),
proyectos_totales = sum(proyectos)
), by = .(departamento, nivel)
][
productividad_media > 1.5 # Solo grupos productivos
][
order(departamento, -productividad_media)
][
, ranking := 1:.N # Agregar ranking
]
print(analisis_productividad)
#> departamento nivel empleados productividad_media salario_total_grupo
#> <char> <char> <int> <num> <num>
#> 1: Finanzas Junior 1 5.00 79000
#> 2: IT Senior 1 5.00 63700
#> 3: IT Manager 1 4.00 50300
#> 4: Marketing Lead 1 2.08 64000
#> 5: Marketing Manager 1 1.60 78200
#> 6: RRHH Junior 1 3.00 82500
#> 7: Ventas Junior 1 2.09 89300
#> proyectos_totales ranking
#> <int> <int>
#> 1: 19 1
#> 2: 22 2
#> 3: 24 3
#> 4: 25 4
#> 5: 8 5
#> 6: 12 6
#> 7: 23 7
💡 ¿Por qué encadenar?
- Menos variables temporales - No necesitas almacenar resultados intermedios
- Código más compacto - Múltiples operaciones en una expresión
- Mejor rendimiento - Menos copias en memoria
- Flujo lógico claro - Lee de arriba abajo como una receta
4.2 Comparación con dplyr Pipes
Comparemos las dos aproximaciones principales para operaciones secuenciales:
4.2.1 1. Sintaxis Lado a Lado
# Pipeline complejo con data.table
pipeline_dt <- ventas[
año == 2024 & valor_neto > 1000 # Filtrar ventas importantes de 2024
][
, .(
ventas_totales = sum(valor_neto),
unidades_totales = sum(cantidad),
transacciones = .N,
ticket_promedio = round(mean(valor_neto), 2)
), by = .(region, producto)
][
ventas_totales > 10000 # Solo combinaciones significativas
][
order(region, -ventas_totales)
][
, rank_en_region := rank(-ventas_totales), by = region
][
rank_en_region <= 3 # Top 3 productos por región
]
print(head(pipeline_dt, 12))
#> region producto ventas_totales unidades_totales transacciones
#> <char> <char> <num> <int> <int>
#> 1: Centro Accesorios 1050344.8 1151 199
#> 2: Centro Smartphone 1023300.8 1023 171
#> 3: Centro Tablet 874227.4 916 162
#> 4: Este Smartphone 989853.6 1048 182
#> 5: Este Laptop 941461.4 984 164
#> ---
#> 8: Norte Accesorios 922333.3 1026 168
#> 9: Norte Tablet 909612.5 953 172
#> 10: Oeste Laptop 1161534.8 1270 203
#> 11: Oeste Smartphone 993922.4 1104 178
#> 12: Oeste Accesorios 950027.6 1048 183
#> ticket_promedio rank_en_region
#> <num> <num>
#> 1: 5278.11 1
#> 2: 5984.22 2
#> 3: 5396.47 3
#> 4: 5438.76 1
#> 5: 5740.62 2
#> ---
#> 8: 5490.08 2
#> 9: 5288.44 3
#> 10: 5721.85 1
#> 11: 5583.83 2
#> 12: 5191.41 3# Mismo pipeline con dplyr
pipeline_dplyr <- ventas %>%
filter(año == 2024, valor_neto > 1000) %>%
group_by(region, producto) %>%
summarise(
ventas_totales = sum(valor_neto),
unidades_totales = sum(cantidad),
transacciones = n(),
ticket_promedio = round(mean(valor_neto), 2),
.groups = 'drop'
) %>%
filter(ventas_totales > 10000) %>%
arrange(region, desc(ventas_totales)) %>%
group_by(region) %>%
mutate(rank_en_region = rank(desc(ventas_totales))) %>%
filter(rank_en_region <= 3) %>%
ungroup()
print(head(as.data.table(pipeline_dplyr), 12))
#> region producto ventas_totales unidades_totales transacciones
#> <char> <char> <num> <int> <int>
#> 1: Centro Accesorios 1050344.8 1151 199
#> 2: Centro Smartphone 1023300.8 1023 171
#> 3: Centro Tablet 874227.4 916 162
#> 4: Este Smartphone 989853.6 1048 182
#> 5: Este Laptop 941461.4 984 164
#> ---
#> 8: Norte Accesorios 922333.3 1026 168
#> 9: Norte Tablet 909612.5 953 172
#> 10: Oeste Laptop 1161534.8 1270 203
#> 11: Oeste Smartphone 993922.4 1104 178
#> 12: Oeste Accesorios 950027.6 1048 183
#> ticket_promedio rank_en_region
#> <num> <num>
#> 1: 5278.11 1
#> 2: 5984.22 2
#> 3: 5396.47 3
#> 4: 5438.76 1
#> 5: 5740.62 2
#> ---
#> 8: 5490.08 2
#> 9: 5288.44 3
#> 10: 5721.85 1
#> 11: 5583.83 2
#> 12: 5191.41 34.2.2 2. Benchmark de Performance
# Crear dataset más grande para benchmark significativo
set.seed(123)
ventas_grandes <- data.table(
id = 1:100000,
categoria = sample(LETTERS[1:5], 100000, replace = TRUE),
valor = runif(100000, 10, 1000),
fecha = sample(seq(as.Date("2023-01-01"), as.Date("2024-12-31"), by = "day"), 100000, replace = TRUE)
)
ventas_grandes[, año := year(fecha)]
# Benchmark
benchmark_pipes <- microbenchmark(
"data.table_chain" = ventas_grandes[año == 2024][, .(suma = sum(valor)), by = categoria][order(-suma)],
"dplyr_pipes" = ventas_grandes %>% filter(año == 2024) %>% group_by(categoria) %>% summarise(suma = sum(valor), .groups = 'drop') %>% arrange(desc(suma)),
times = 20
)
print(benchmark_pipes)
#> Unit: milliseconds
#> expr min lq mean median uq max neval
#> data.table_chain 3.392227 3.770979 4.534649 4.212111 4.887797 9.395095 20
#> dplyr_pipes 6.779315 6.970010 8.489753 7.592757 9.959217 13.315547 204.3 Patrones Avanzados de Encadenamiento
4.3.1 1. Encadenamiento con Modificación por Referencia
# Combinar := con encadenamiento para análisis iterativo
empleados_analisis <- copy(empleados)[
, salario_z := scale(salario_total)[,1], by = departamento # Z-score por depto
][
, categoria_performance := cut(salario_z, breaks = c(-Inf, -1, 1, Inf),
labels = c("Bajo", "Medio", "Alto"))
][
, .(
empleados = .N,
salario_promedio = round(mean(salario_total), 0),
performance_dist = paste(table(categoria_performance), collapse = "/")
), by = .(departamento, categoria_performance)
][
order(departamento, categoria_performance)
]
print(empleados_analisis)
#> departamento categoria_performance empleados salario_promedio
#> <char> <fctr> <int> <num>
#> 1: Finanzas Bajo 1 39000
#> 2: Finanzas Medio 3 71167
#> 3: IT Bajo 1 50300
#> 4: IT Medio 2 63500
#> 5: IT Alto 1 72700
#> 6: Marketing Bajo 1 64000
#> 7: Marketing Medio 3 75167
#> 8: RRHH Medio 3 62933
#> 9: RRHH Alto 1 89500
#> 10: Ventas Bajo 1 59100
#> 11: Ventas Medio 3 84667
#> performance_dist
#> <char>
#> 1: 1/0/0
#> 2: 0/1/0
#> 3: 1/0/0
#> 4: 0/1/0
#> 5: 0/0/1
#> 6: 1/0/0
#> 7: 0/1/0
#> 8: 0/1/0
#> 9: 0/0/1
#> 10: 1/0/0
#> 11: 0/1/04.3.2 2. Encadenamiento con Validaciones
# Pipeline con validaciones integradas
pipeline_validado <- ventas[
!is.na(valor_neto) & valor_neto > 0 # Validar datos básicos
][
, .N, by = año # Verificar distribución temporal
][
, {
cat("Distribución por año:\n")
print(.SD)
if(min(N) < 100) warning("Pocos datos en algunos años")
.SD
}
][
# Continuar con el análisis principal
, .(años_con_datos = .N, transacciones_totales = sum(N))
]
#> Distribución por año:
#> año N
#> <int> <int>
#> 1: 2023 4962
#> 2: 2024 5038
print(pipeline_validado)
#> años_con_datos transacciones_totales
#> <int> <int>
#> 1: 2 100004.3.3 3. Encadenamiento con Análisis Exploratorio
# Pipeline de análisis exploratorio
eda_pipeline <- ventas[
sample(.N, 5000) # Muestra para EDA rápido
][
, .(
valores_unicos = uniqueN(cliente_id),
valor_promedio = round(mean(valor_neto), 2),
valor_mediana = round(median(valor_neto), 2),
outliers_superiores = sum(valor_neto > quantile(valor_neto, 0.95))
), by = .(region, categoria)
][
, coef_variacion := round((valor_promedio - valor_mediana) / valor_promedio, 3)
][
order(-valores_unicos)
][
, {
cat("Top regiones-categorías por diversidad de clientes:\n")
print(.SD[1:5])
.SD
}
]
#> Top regiones-categorías por diversidad de clientes:
#> region categoria valores_unicos valor_promedio valor_mediana
#> <char> <char> <int> <num> <num>
#> 1: Oeste Servicios 259 4661.32 3620.13
#> 2: Norte Hardware 255 4513.11 3287.83
#> 3: Oeste Hardware 249 4866.92 3741.86
#> 4: Este Software 247 4632.75 3713.56
#> 5: Centro Software 246 4679.73 3562.80
#> outliers_superiores coef_variacion
#> <int> <num>
#> 1: 19 0.223
#> 2: 18 0.271
#> 3: 18 0.231
#> 4: 18 0.198
#> 5: 17 0.2394.3.4 4. Encadenamiento con Funciones Personalizadas
# Definir función auxiliar
calcular_metricas_avanzadas <- function(valores) {
list(
media = round(mean(valores), 2),
percentil_95 = round(quantile(valores, 0.95), 2),
coef_asimetria = round((mean(valores) - median(valores)) / sd(valores), 3),
outliers_count = sum(valores > (mean(valores) + 2 * sd(valores)))
)
}
# Pipeline con función personalizada
metricas_avanzadas <- ventas[
año == 2024 & !is.na(valor_neto)
][
, calcular_metricas_avanzadas(valor_neto), by = .(region, trimestre)
][
outliers_count > 5 # Solo regiones/trimestres con anomalías
][
order(region, trimestre)
]
print(metricas_avanzadas)
#> region trimestre media percentil_95 coef_asimetria outliers_count
#> <char> <int> <num> <num> <num> <int>
#> 1: Centro 1 4512.27 12113.24 0.276 14
#> 2: Centro 2 4379.47 12860.78 0.303 13
#> 3: Centro 3 4586.36 12705.74 0.346 16
#> 4: Centro 4 5031.66 13807.56 0.272 18
#> 5: Este 1 4647.60 12434.10 0.265 14
#> ---
#> 16: Oeste 4 4629.18 13104.52 0.269 17
#> 17: Sur 1 5657.17 14089.79 0.278 13
#> 18: Sur 2 4854.70 12187.45 0.212 11
#> 19: Sur 3 5142.59 13569.57 0.337 9
#> 20: Sur 4 4893.29 12974.33 0.307 134.4 Técnicas de Debugging en Encadenamientos
4.4.1 1. Inspección Intermedia
# Técnica: usar {} para inspeccionar pasos intermedios
debug_pipeline <- empleados[
salario_total > 50000
][
, {
cat("Después del filtro:", .N, "filas\n")
.SD
}
][
, .(empleados = .N, salario_avg = mean(salario_total)), by = departamento
][
, {
cat("Después de agrupar:", nrow(.SD), "grupos\n")
print(.SD)
.SD
}
][
order(-salario_avg)
]
#> Después del filtro: 19 filas
#> Después de agrupar: 5 grupos
#> departamento empleados salario_avg
#> <char> <int> <num>
#> 1: Ventas 4 78275.00
#> 2: IT 4 62500.00
#> 3: Marketing 4 72375.00
#> 4: RRHH 4 69575.00
#> 5: Finanzas 3 71166.674.4.2 2. Validaciones en Cadena
# Pipeline robusto con validaciones
pipeline_robusto <- ventas[
año %in% 2023:2024 # Años válidos
][
, if(.N == 0) stop("No hay datos después del filtro de años") else .SD
][
!is.na(valor_neto) & valor_neto > 0 # Valores válidos
][
, if(.N < 1000) warning("Pocos datos para análisis confiable") else .SD
][
, .(ventas = sum(valor_neto), transacciones = .N), by = .(año, region)
][
ventas > 0 # Verificar resultados lógicos
]
print(pipeline_robusto)
#> año region ventas transacciones
#> <int> <char> <num> <int>
#> 1: 2023 Sur 4770068 958
#> 2: 2024 Centro 4669677 1005
#> 3: 2024 Norte 4571534 985
#> 4: 2024 Este 4589704 979
#> 5: 2023 Centro 4841287 986
#> 6: 2023 Este 4799598 993
#> 7: 2024 Sur 5136588 996
#> 8: 2024 Oeste 5059483 1073
#> 9: 2023 Norte 4917000 1016
#> 10: 2023 Oeste 4672004 10094.5 Ejercicios Prácticos
💡 Solución del Ejercicio 6
# Pipeline complejo de análisis de ventas
reporte_ventas <- ventas[
año == 2024 & trimestre == 4 # 1. Q4 2024
][
, .( # 2. Métricas por vendedor-región
ventas_totales = sum(valor_neto),
unidades_vendidas = sum(cantidad),
transacciones = .N,
ticket_promedio = round(mean(valor_neto), 2),
mejor_producto = names(sort(table(producto), decreasing = TRUE))[1]
), by = .(vendedor_id, region)
][
ventas_totales > quantile(ventas_totales, 0.8) # 3. Top performers (percentil 80)
][
order(region, -ventas_totales)
][
, ranking_regional := 1:.N, by = region # 4. Ranking por región
][
, .( # 5. Reporte final formateado
Region = region,
Vendedor = paste0("ID_", vendedor_id),
Ranking = ranking_regional,
Ventas = paste0("$", format(round(ventas_totales, 0), big.mark = ",")),
Unidades = format(unidades_vendidas, big.mark = ","),
Transacciones = transacciones,
Ticket_Avg = paste0("$", ticket_promedio),
Top_Producto = mejor_producto,
Performance = ifelse(ranking_regional == 1, "★★★",
ifelse(ranking_regional <= 3, "★★", "★"))
)
][
order(Region, Ranking)
]
print(reporte_ventas)
#> Region Vendedor Ranking Ventas Unidades Transacciones Ticket_Avg
#> <char> <char> <int> <char> <char> <int> <char>
#> 1: Centro ID_21 1 $57,943 62 8 $7242.93
#> 2: Centro ID_23 2 $55,866 56 10 $5586.62
#> 3: Centro ID_22 3 $50,392 63 8 $6299.04
#> 4: Centro ID_9 4 $47,863 44 6 $7977.08
#> 5: Centro ID_24 5 $47,785 37 8 $5973.07
#> ---
#> 46: Sur ID_24 3 $42,631 34 7 $6090.2
#> 47: Sur ID_5 4 $41,461 42 11 $3769.21
#> 48: Sur ID_43 5 $41,168 38 7 $5881.16
#> 49: Sur ID_48 6 $39,735 39 7 $5676.37
#> 50: Sur ID_28 7 $39,045 29 5 $7809.03
#> Top_Producto Performance
#> <char> <char>
#> 1: Laptop ★★★
#> 2: Tablet ★★
#> 3: Desktop ★★
#> 4: Smartphone ★
#> 5: Desktop ★
#> ---
#> 46: Accesorios ★★
#> 47: Tablet ★
#> 48: Accesorios ★
#> 49: Accesorios ★
#> 50: Laptop ★
# Mostrar tabla del reporte formateada para PDF
knitr::kable(
reporte_ventas,
caption = "Reporte de Top Performers Q4 2024",
digits = 2,
format.args = list(big.mark = ",")
)| Region | Vendedor | Ranking | Ventas | Unidades | Transacciones | Ticket_Avg | Top_Producto | Performance |
|---|---|---|---|---|---|---|---|---|
| Centro | ID_21 | 1 | $57,943 | 62 | 8 | $7242.93 | Laptop | ★★★ |
| Centro | ID_23 | 2 | $55,866 | 56 | 10 | $5586.62 | Tablet | ★★ |
| Centro | ID_22 | 3 | $50,392 | 63 | 8 | $6299.04 | Desktop | ★★ |
| Centro | ID_9 | 4 | $47,863 | 44 | 6 | $7977.08 | Smartphone | ★ |
| Centro | ID_24 | 5 | $47,785 | 37 | 8 | $5973.07 | Desktop | ★ |
| Centro | ID_36 | 6 | $45,849 | 44 | 7 | $6549.82 | Accesorios | ★ |
| Centro | ID_12 | 7 | $45,062 | 79 | 11 | $4096.56 | Smartphone | ★ |
| Centro | ID_27 | 8 | $43,315 | 48 | 9 | $4812.83 | Laptop | ★ |
| Centro | ID_28 | 9 | $43,007 | 43 | 8 | $5375.83 | Accesorios | ★ |
| Centro | ID_48 | 10 | $40,534 | 44 | 8 | $5066.81 | Tablet | ★ |
| Centro | ID_38 | 11 | $39,617 | 53 | 9 | $4401.87 | Tablet | ★ |
| Centro | ID_41 | 12 | $38,036 | 64 | 9 | $4226.2 | Tablet | ★ |
| Este | ID_4 | 1 | $60,526 | 80 | 12 | $5043.81 | Smartphone | ★★★ |
| Este | ID_1 | 2 | $59,086 | 49 | 10 | $5908.63 | Desktop | ★★ |
| Este | ID_6 | 3 | $44,048 | 34 | 7 | $6292.61 | Desktop | ★★ |
| Este | ID_45 | 4 | $41,654 | 53 | 7 | $5950.57 | Smartphone | ★ |
| Este | ID_27 | 5 | $41,578 | 39 | 7 | $5939.73 | Desktop | ★ |
| Este | ID_37 | 6 | $41,310 | 49 | 9 | $4590.03 | Laptop | ★ |
| Este | ID_50 | 7 | $40,439 | 33 | 8 | $5054.91 | Desktop | ★ |
| Este | ID_11 | 8 | $38,579 | 41 | 7 | $5511.27 | Desktop | ★ |
| Este | ID_3 | 9 | $38,573 | 45 | 8 | $4821.65 | Laptop | ★ |
| Este | ID_44 | 10 | $37,725 | 46 | 7 | $5389.32 | Laptop | ★ |
| Norte | ID_5 | 1 | $61,248 | 55 | 9 | $6805.35 | Accesorios | ★★★ |
| Norte | ID_10 | 2 | $58,480 | 60 | 10 | $5848.04 | Smartphone | ★★ |
| Norte | ID_40 | 3 | $52,650 | 53 | 8 | $6581.22 | Laptop | ★★ |
| Norte | ID_46 | 4 | $43,763 | 44 | 7 | $6251.83 | Laptop | ★ |
| Norte | ID_6 | 5 | $43,651 | 50 | 12 | $3637.6 | Laptop | ★ |
| Norte | ID_31 | 6 | $41,523 | 37 | 6 | $6920.42 | Laptop | ★ |
| Oeste | ID_36 | 1 | $52,220 | 48 | 6 | $8703.3 | Smartphone | ★★★ |
| Oeste | ID_24 | 2 | $49,463 | 58 | 9 | $5495.85 | Laptop | ★★ |
| Oeste | ID_1 | 3 | $49,336 | 62 | 9 | $5481.81 | Accesorios | ★★ |
| Oeste | ID_43 | 4 | $48,605 | 59 | 11 | $4418.62 | Smartphone | ★ |
| Oeste | ID_48 | 5 | $47,777 | 52 | 9 | $5308.6 | Desktop | ★ |
| Oeste | ID_40 | 6 | $45,381 | 47 | 9 | $5042.38 | Accesorios | ★ |
| Oeste | ID_47 | 7 | $45,205 | 58 | 9 | $5022.72 | Laptop | ★ |
| Oeste | ID_23 | 8 | $44,807 | 55 | 12 | $3733.94 | Laptop | ★ |
| Oeste | ID_32 | 9 | $44,092 | 49 | 8 | $5511.46 | Laptop | ★ |
| Oeste | ID_4 | 10 | $43,620 | 52 | 7 | $6231.43 | Laptop | ★ |
| Oeste | ID_18 | 11 | $43,598 | 36 | 5 | $8719.6 | Desktop | ★ |
| Oeste | ID_12 | 12 | $43,212 | 35 | 7 | $6173.21 | Laptop | ★ |
| Oeste | ID_42 | 13 | $42,199 | 45 | 7 | $6028.39 | Accesorios | ★ |
| Oeste | ID_3 | 14 | $41,547 | 35 | 7 | $5935.25 | Laptop | ★ |
| Oeste | ID_13 | 15 | $39,928 | 32 | 6 | $6654.71 | Smartphone | ★ |
| Sur | ID_3 | 1 | $52,542 | 43 | 7 | $7506.06 | Accesorios | ★★★ |
| Sur | ID_7 | 2 | $45,035 | 39 | 9 | $5003.94 | Tablet | ★★ |
| Sur | ID_24 | 3 | $42,631 | 34 | 7 | $6090.2 | Accesorios | ★★ |
| Sur | ID_5 | 4 | $41,461 | 42 | 11 | $3769.21 | Tablet | ★ |
| Sur | ID_43 | 5 | $41,168 | 38 | 7 | $5881.16 | Accesorios | ★ |
| Sur | ID_48 | 6 | $39,735 | 39 | 7 | $5676.37 | Accesorios | ★ |
| Sur | ID_28 | 7 | $39,045 | 29 | 5 | $7809.03 | Laptop | ★ |
💡 Solución del Ejercicio 7
# Análisis de cohortes con encadenamiento
library(lubridate)
analisis_cohortes <- empleados[
!is.na(fecha_ingreso) # 1. Datos válidos
][
, año_ingreso := year(fecha_ingreso) # Extraer año de cohorte
][
, años_en_empresa := round(as.numeric(Sys.Date() - fecha_ingreso) / 365, 1)
][
, .( # 2. Métricas por cohorte
empleados_iniciales = .N,
empleados_activos = sum(activo),
retencion_pct = round(mean(activo) * 100, 1),
salario_inicial_avg = round(mean(salario), 0),
salario_actual_avg = round(mean(salario_total), 0),
crecimiento_salarial = round(mean(salario_total) / mean(salario) - 1, 2),
años_promedio = round(mean(años_en_empresa), 1),
productividad_avg = round(mean(productividad), 2),
managers_promocionados = sum(nivel == "Manager")
), by = año_ingreso
][
empleados_iniciales >= 2 # Solo cohortes con suficientes datos
][
, `:=`( # 3. Clasificar cohortes
categoria_retencion = cut(retencion_pct,
breaks = c(0, 70, 90, 100),
labels = c("Problemática", "Regular", "Exitosa")),
categoria_crecimiento = ifelse(crecimiento_salarial > 0.15, "Alto",
ifelse(crecimiento_salarial > 0.05, "Medio", "Bajo"))
)
][
order(año_ingreso)
][
, { # 4. Generar insights automáticos
cat("=== INSIGHTS DE COHORTES ===\n\n")
cohorte_mejor <- .SD[which.max(retencion_pct * crecimiento_salarial)]
cohorte_peor <- .SD[which.min(retencion_pct * (1 + crecimiento_salarial))]
cat("🏆 MEJOR COHORTE:", cohorte_mejor$año_ingreso, "\n")
cat(" • Retención:", cohorte_mejor$retencion_pct, "%\n")
cat(" • Crecimiento salarial:", cohorte_mejor$crecimiento_salarial * 100, "%\n\n")
cat("⚠️ COHORTE PROBLEMÁTICA:", cohorte_peor$año_ingreso, "\n")
cat(" • Retención:", cohorte_peor$retencion_pct, "%\n")
cat(" • Crecimiento salarial:", cohorte_peor$crecimiento_salarial * 100, "%\n\n")
cat("📊 RESUMEN GENERAL:\n")
cat(" • Retención promedio:", round(mean(retencion_pct), 1), "%\n")
cat(" • Cohortes exitosas:", sum(categoria_retencion == "Exitosa"), "de", .N, "\n")
cat(" • Managers promocionados:", sum(managers_promocionados), "total\n\n")
.SD
}
]
#> === INSIGHTS DE COHORTES ===
#>
#> 🏆 MEJOR COHORTE: 2022
#> • Retención: 100 %
#> • Crecimiento salarial: 21 %
#>
#> ⚠️ COHORTE PROBLEMÁTICA: 2023
#> • Retención: 66.7 %
#> • Crecimiento salarial: 15 %
#>
#> 📊 RESUMEN GENERAL:
#> • Retención promedio: 83.3 %
#> • Cohortes exitosas: 2 de 5
#> • Managers promocionados: 4 total
print(analisis_cohortes[, .(año_ingreso, empleados_iniciales, retencion_pct,
crecimiento_salarial, categoria_retencion, categoria_crecimiento)])
#> año_ingreso empleados_iniciales retencion_pct crecimiento_salarial
#> <num> <int> <num> <num>
#> 1: 2019 6 83.3 0.10
#> 2: 2020 3 100.0 0.09
#> 3: 2021 3 66.7 0.18
#> 4: 2022 4 100.0 0.21
#> 5: 2023 3 66.7 0.15
#> categoria_retencion categoria_crecimiento
#> <fctr> <char>
#> 1: Regular Medio
#> 2: Exitosa Medio
#> 3: Problemática Alto
#> 4: Exitosa Alto
#> 5: Problemática Medio4.6 Mejores Prácticas para Encadenamiento
4.6.1 1. Legibilidad vs. Performance
# ✅ HACER: Encadenamiento legible con líneas separadas
resultado <- datos[
filtro_simple & condicion_clara
][
, .(metrica1 = func1(col1), metrica2 = func2(col2)), by = grupo
][
order(-metrica1)
][
1:10
]
# ❌ EVITAR: Una línea muy larga
resultado <- datos[filtro & condicion][, .(m1 = f1(c1), m2 = f2(c2)), by = g][order(-m1)][1:10]
# ✅ HACER: Comentarios para pasos complejos
resultado <- datos[
filtro_complejo # Paso 1: filtrar casos válidos
][
, calculo_complejo(), by = grupo # Paso 2: agregaciones por grupo
][
post_procesamiento() # Paso 3: ajustes finales
]4.6.2 2. Cuándo NO usar encadenamiento
# ❌ Evitar: Lógica muy compleja en una cadena
# Mejor usar pasos separados para debugging
paso1 <- datos[filtro_complejo_con_multiples_condiciones]
paso2 <- paso1[, calculo_muy_complejo_con_multiples_funciones(), by = multiples_grupos]
paso3 <- paso2[post_procesamiento_complejo_con_validaciones()]
# ❌ Evitar: Cadenas que modifican el objeto original sin copy()
# Peligroso si necesitas preservar datos originales
datos_originales[, nueva_col := calculo()][filtro][, otra_col := otro_calculo()]
# ✅ Hacer: Usar copy() cuando modifiques
datos_procesados <- copy(datos_originales)[
, nueva_col := calculo()
][
filtro
][
, otra_col := otro_calculo()
]4.6.3 3. Optimización de Performance
# ✅ HACER: Filtrar temprano para reducir datos
datos[
filtro_restrictivo # Reduce datos primero
][
calculo_costoso(), by = grupo # Luego opera en menos datos
]
# ❌ EVITAR: Cálculos costosos antes de filtrar
datos[
calculo_costoso(), by = grupo # Opera en todos los datos
][
filtro_restrictivo # Filtra después
]
# ✅ HACER: Usar .SDcols para limitar columnas en operaciones costosas
datos[
filtro
][
, lapply(.SD, operacion_costosa), by = grupo, .SDcols = columnas_necesarias
]
🎯 Puntos Clave de Este Capítulo
- El encadenamiento hace el código más conciso y eficiente al eliminar variables temporales
- Cada
[...]opera sobre el resultado del anterior, creando un flujo lógico claro - Performance:
data.tablechaining es más rápido quedplyrpipes para la mayoría de operaciones - Legibilidad: Usar líneas separadas y comentarios para encadenamientos complejos
- Debugging: Insertar
{}para inspeccionar resultados intermedios - Filtrar temprano para optimizar performance en cadenas largas
El encadenamiento es una técnica fundamental que, una vez dominada, hace que tu código data.table sea más elegante y eficiente. En el próximo capítulo exploraremos cómo combinar múltiples tablas usando joins.