# Veamos qué contiene .SD
empleados_avanzado[, {
cat("Grupo:", unique(departamento), "\n")
cat("Columnas en .SD:", names(.SD), "\n")
cat("Filas en .SD:", nrow(.SD), "\n\n")
# Devolver algo para que funcione el data.table
.N
}, by = departamento]
#> Grupo: Ventas
#> Columnas en .SD: id nombre nivel salario_base bonus años_exp certificaciones proyectos_completados rating_performance fecha_ingreso activo salario_total experiencia_categoria productividad valor_empleado
#> Filas en .SD: 4
#>
#> Grupo: IT
#> Columnas en .SD: id nombre nivel salario_base bonus años_exp certificaciones proyectos_completados rating_performance fecha_ingreso activo salario_total experiencia_categoria productividad valor_empleado
#> Filas en .SD: 4
#>
#> Grupo: Marketing
#> Columnas en .SD: id nombre nivel salario_base bonus años_exp certificaciones proyectos_completados rating_performance fecha_ingreso activo salario_total experiencia_categoria productividad valor_empleado
#> Filas en .SD: 4
#>
#> Grupo: RRHH
#> Columnas en .SD: id nombre nivel salario_base bonus años_exp certificaciones proyectos_completados rating_performance fecha_ingreso activo salario_total experiencia_categoria productividad valor_empleado
#> Filas en .SD: 4
#>
#> Grupo: Finanzas
#> Columnas en .SD: id nombre nivel salario_base bonus años_exp certificaciones proyectos_completados rating_performance fecha_ingreso activo salario_total experiencia_categoria productividad valor_empleado
#> Filas en .SD: 4
#> departamento V1
#> <char> <int>
#> 1: Ventas 4
#> 2: IT 4
#> 3: Marketing 4
#> 4: RRHH 4
#> 5: Finanzas 43 Símbolos Especiales en data.table
3.1 El Símbolo .SD: Subset of Data
.SD es quizás el símbolo más poderoso de data.table. Contiene todas las columnas del grupo actual (excepto las variables de agrupación) como un data.table en sí mismo.
3.1.1 1. Conceptos Fundamentales de .SD
# Ejemplo práctico: estadísticas de salario por departamento
stats_salarios <- empleados_avanzado[, {
list(
empleados = .N,
salario_promedio = round(mean(.SD$salario_total), 0),
salario_mediana = round(median(.SD$salario_total), 0),
salario_max = max(.SD$salario_total),
salario_min = min(.SD$salario_total),
rango_salario = max(.SD$salario_total) - min(.SD$salario_total)
)
}, by = departamento]
print(stats_salarios)
#> departamento empleados salario_promedio salario_mediana salario_max
#> <char> <int> <num> <num> <num>
#> 1: Ventas 4 52175 52750 70200
#> 2: IT 4 52200 53350 63300
#> 3: Marketing 4 55900 57200 60200
#> 4: RRHH 4 51550 54100 60600
#> 5: Finanzas 4 57875 56600 71800
#> salario_min rango_salario
#> <num> <num>
#> 1: 33000 37200
#> 2: 38800 24500
#> 3: 49000 11200
#> 4: 37400 23200
#> 5: 46500 253003.1.2 2. Aplicar Funciones con lapply(.SD, ...)
El verdadero poder de .SD surge cuando lo combinas con lapply() para aplicar funciones a múltiples columnas:
# Aplicar mean() a todas las columnas numéricas por departamento
medias_por_dept <- empleados_avanzado[,
lapply(.SD, mean, na.rm = TRUE),
by = departamento,
.SDcols = is.numeric # Solo columnas numéricas
]
print(medias_por_dept)
#> departamento id salario_base bonus años_exp certificaciones
#> <char> <num> <num> <num> <num> <num>
#> 1: Ventas 2.5 47475 4700 11.75 2.75
#> 2: IT 6.5 45625 6575 11.25 1.00
#> 3: Marketing 10.5 49075 6825 3.00 4.75
#> 4: RRHH 14.5 44575 6975 9.25 3.25
#> 5: Finanzas 18.5 51050 6825 4.75 1.75
#> proyectos_completados rating_performance salario_total productividad
#> <num> <num> <num> <num>
#> 1: 11.25 3.850 52175 1.1900
#> 2: 35.00 4.150 52200 3.1975
#> 3: 43.75 4.425 55900 24.6075
#> 4: 32.50 4.625 51550 11.7425
#> 5: 38.00 3.675 57875 11.3625
#> valor_empleado
#> <num>
#> 1: 2443.035
#> 2: 2415.500
#> 3: 727.840
#> 4: 2051.960
#> 5: 1157.883# Múltiples estadísticas por grupo usando funciones personalizadas
estadisticas_avanzadas <- empleados_avanzado[,
lapply(.SD, function(x) {
if(is.numeric(x)) {
list(
media = round(mean(x, na.rm = TRUE), 2),
mediana = round(median(x, na.rm = TRUE), 2),
desv_std = round(sd(x, na.rm = TRUE), 2),
cv = round(sd(x, na.rm = TRUE) / mean(x, na.rm = TRUE), 3)
)
} else {
list(valores_unicos = length(unique(x)))
}
}),
by = departamento,
.SDcols = c("salario_total", "años_exp", "rating_performance")
]
print(estadisticas_avanzadas)
#> departamento salario_total años_exp rating_performance
#> <char> <list> <list> <list>
#> 1: Ventas 52175 11.75 3.85
#> 2: Ventas 52750 12 3.7
#> 3: Ventas 17803.44 2.99 0.7
#> 4: Ventas 0.341 0.254 0.181
#> 5: IT 52200 11.25 4.15
#> ---
#> 16: RRHH 0.194 0.62 0.065
#> 17: Finanzas 57875 4.75 3.67
#> 18: Finanzas 56600 4.5 3.55
#> 19: Finanzas 13198.07 2.5 0.45
#> 20: Finanzas 0.228 0.526 0.1223.1.3 3. Transformaciones Complejas con .SD
# Normalizar columnas dentro de cada departamento (Z-score)
empleados_normalizados <- empleados_avanzado[,
c(.SD[, 1:2], lapply(.SD[, -(1:2)], function(x) {
if(is.numeric(x) && length(unique(x)) > 1) {
round((x - mean(x)) / sd(x), 3)
} else {
x
}
})),
by = departamento
][order(departamento, id)]
# Mostrar solo algunas columnas para claridad
print(empleados_normalizados[, .(departamento, nombre, salario_total, años_exp, rating_performance)])
#> departamento nombre salario_total años_exp rating_performance
#> <char> <char> <num> <num> <num>
#> 1: Finanzas Empleado_Q -0.862 -0.300 -0.833
#> 2: Finanzas Empleado_R 0.654 0.100 0.056
#> 3: Finanzas Empleado_S 1.055 1.300 1.389
#> 4: Finanzas Empleado_T -0.847 -1.100 -0.611
#> 5: IT Empleado_E 0.742 -1.306 -0.691
#> ---
#> 16: RRHH Empleado_P -1.416 0.479 -1.088
#> 17: Ventas Empleado_A 1.012 -1.256 0.072
#> 18: Ventas Empleado_B -0.605 -0.251 -0.503
#> 19: Ventas Empleado_C -1.077 0.419 -0.935
#> 20: Ventas Empleado_D 0.670 1.088 1.3663.2 Control de Columnas con .SDcols
.SDcols te permite especificar exactamente qué columnas debe incluir .SD.
3.2.1 1. Formas de Especificar .SDcols
# Por nombres de columna
por_nombres <- empleados_avanzado[,
lapply(.SD, mean),
by = departamento,
.SDcols = c("salario_total", "años_exp", "rating_performance")
]
# Por patrones
por_patrones <- empleados_avanzado[,
lapply(.SD, max),
by = departamento,
.SDcols = patterns("salario|rating")
]
# Por tipo de datos
por_tipo <- empleados_avanzado[,
lapply(.SD, function(x) length(unique(x))),
by = departamento,
.SDcols = is.character
]
print("Por nombres:")
#> [1] "Por nombres:"
print(por_nombres)
#> departamento salario_total años_exp rating_performance
#> <char> <num> <num> <num>
#> 1: Ventas 52175 11.75 3.850
#> 2: IT 52200 11.25 4.150
#> 3: Marketing 55900 3.00 4.425
#> 4: RRHH 51550 9.25 4.625
#> 5: Finanzas 57875 4.75 3.675
print("\nPor patrones:")
#> [1] "\nPor patrones:"
print(por_patrones)
#> departamento salario_base rating_performance salario_total
#> <char> <num> <num> <num>
#> 1: Ventas 65000 4.8 70200
#> 2: IT 58600 4.8 63300
#> 3: Marketing 55400 5.0 60200
#> 4: RRHH 53300 5.0 60600
#> 5: Finanzas 68500 4.3 71800
print("\nPor tipo (character):")
#> [1] "\nPor tipo (character):"
print(por_tipo)
#> departamento nombre departamento nivel
#> <char> <int> <int> <int>
#> 1: Ventas 4 1 4
#> 2: IT 4 1 4
#> 3: Marketing 4 1 4
#> 4: RRHH 4 1 4
#> 5: Finanzas 4 1 43.2.2 2. Casos de Uso Avanzados de .SDcols
# Análisis de correlaciones por departamento
correlaciones_dept <- empleados_avanzado[activo == TRUE, # Solo empleados activos
{
# Seleccionar solo columnas numéricas con variabilidad
cols_numericas <- sapply(.SD, function(x) is.numeric(x) && var(x, na.rm = TRUE) > 0)
nombres_cols_validas <- names(cols_numericas)[cols_numericas]
if(length(nombres_cols_validas) >= 2) {
cor_matrix <- cor(.SD[, nombres_cols_validas, with = FALSE])
# Extraer correlación específica: salario vs rating (si ambas existen)
if("salario_total" %in% nombres_cols_validas && "rating_performance" %in% nombres_cols_validas) {
correlacion_sal_rating <- round(cor_matrix["salario_total", "rating_performance"], 3)
} else {
correlacion_sal_rating <- NA
}
list(
correlacion_salario_rating = correlacion_sal_rating,
num_variables = length(nombres_cols_validas)
)
} else {
list(correlacion_salario_rating = NA, num_variables = length(nombres_cols_validas))
}
},
by = departamento,
.SDcols = is.numeric
]
print(correlaciones_dept)
#> departamento correlacion_salario_rating num_variables
#> <char> <num> <int>
#> 1: Ventas 0.586 10
#> 2: IT -0.399 10
#> 3: Marketing 0.033 10
#> 4: RRHH 0.845 10
#> 5: Finanzas 0.912 10# Selección dinámica de columnas basada en criterios
columnas_con_variacion <- empleados_avanzado[,
sapply(.SD, function(x) if(is.numeric(x)) var(x, na.rm = TRUE) > 1000 else FALSE),
.SDcols = is.numeric
]
print("Columnas con alta variación:")
#> [1] "Columnas con alta variación:"
print(names(columnas_con_variacion)[columnas_con_variacion])
#> [1] "salario_base" "bonus" "salario_total" "valor_empleado"
# Usar esas columnas para análisis
analisis_alta_variacion <- empleados_avanzado[,
lapply(.SD, function(x) c(min = min(x), max = max(x), rango = max(x) - min(x))),
.SDcols = names(columnas_con_variacion)[columnas_con_variacion]
]
print(analisis_alta_variacion)
#> salario_base bonus salario_total valor_empleado
#> <num> <num> <num> <num>
#> 1: 30600 2400 33000 301.0
#> 2: 68500 12300 71800 4615.2
#> 3: 37900 9900 38800 4314.23.3 El Símbolo .I: Índices de Fila
.I contiene los índices (números de fila) de las observaciones del grupo actual en el data.table original.
3.3.1 1. Usos Básicos de .I
# Encontrar el índice del empleado con mayor salario por departamento
indices_top_salario <- empleados_avanzado[,
.I[which.max(salario_total)],
by = departamento
]
print("Índices de empleados con mayor salario:")
#> [1] "Índices de empleados con mayor salario:"
print(indices_top_salario)
#> departamento V1
#> <char> <int>
#> 1: Ventas 1
#> 2: IT 7
#> 3: Marketing 9
#> 4: RRHH 14
#> 5: Finanzas 19
# Usar esos índices para extraer las filas completas
top_empleados_por_dept <- empleados_avanzado[indices_top_salario$V1]
print("\nEmpleados con mayor salario por departamento:")
#> [1] "\nEmpleados con mayor salario por departamento:"
print(top_empleados_por_dept[, .(departamento, nombre, salario_total)])
#> departamento nombre salario_total
#> <char> <char> <num>
#> 1: Ventas Empleado_A 70200
#> 2: IT Empleado_G 63300
#> 3: Marketing Empleado_I 60200
#> 4: RRHH Empleado_N 60600
#> 5: Finanzas Empleado_S 718003.3.2 2. Casos de Uso Avanzados con .I
# Top N empleados por departamento
top_2_por_dept <- empleados_avanzado[,
.I[order(-salario_total)][1:min(2, .N)], # Top 2 o todos si hay menos de 2
by = departamento
]
empleados_top2 <- empleados_avanzado[top_2_por_dept$V1]
print("Top 2 empleados por departamento:")
#> [1] "Top 2 empleados por departamento:"
print(empleados_top2[, .(departamento, nombre, salario_total)][order(departamento, -salario_total)])
#> departamento nombre salario_total
#> <char> <char> <num>
#> 1: Finanzas Empleado_S 71800
#> 2: Finanzas Empleado_R 66500
#> 3: IT Empleado_G 63300
#> 4: IT Empleado_E 61000
#> 5: Marketing Empleado_I 60200
#> 6: Marketing Empleado_L 59600
#> 7: RRHH Empleado_N 60600
#> 8: RRHH Empleado_O 55600
#> 9: Ventas Empleado_A 70200
#> 10: Ventas Empleado_D 64100# Muestreo estratificado usando .I
set.seed(123)
muestra_estratificada <- empleados_avanzado[,
.I[sample(.N, size = min(2, .N))], # 2 empleados por departamento
by = departamento
]
empleados_muestra <- empleados_avanzado[muestra_estratificada$V1]
print("Muestra estratificada:")
#> [1] "Muestra estratificada:"
print(empleados_muestra[, .(departamento, nombre, años_exp)])
#> departamento nombre años_exp
#> <char> <char> <int>
#> 1: Ventas Empleado_C 13
#> 2: Ventas Empleado_D 15
#> 3: IT Empleado_G 12
#> 4: IT Empleado_F 11
#> 5: Marketing Empleado_K 2
#> 6: Marketing Empleado_J 7
#> 7: RRHH Empleado_N 1
#> 8: RRHH Empleado_P 12
#> 9: Finanzas Empleado_S 8
#> 10: Finanzas Empleado_Q 43.4 El Símbolo .GRP: Identificador de Grupo
.GRP es un contador que asigna un número único e incremental a cada grupo.
3.4.1 1. Usos de .GRP
# Asignar ID único a cada departamento
empleados_con_grupo <- empleados_avanzado[,
.(nombre, departamento, grupo_id = .GRP, empleados_en_grupo = .N),
by = departamento
]
print(empleados_con_grupo)
#> departamento nombre departamento grupo_id empleados_en_grupo
#> <char> <char> <char> <int> <int>
#> 1: Ventas Empleado_A Ventas 1 4
#> 2: Ventas Empleado_B Ventas 1 4
#> 3: Ventas Empleado_C Ventas 1 4
#> 4: Ventas Empleado_D Ventas 1 4
#> 5: IT Empleado_E IT 2 4
#> ---
#> 16: RRHH Empleado_P RRHH 4 4
#> 17: Finanzas Empleado_Q Finanzas 5 4
#> 18: Finanzas Empleado_R Finanzas 5 4
#> 19: Finanzas Empleado_S Finanzas 5 4
#> 20: Finanzas Empleado_T Finanzas 5 4# Análisis de transacciones por grupo de cliente
analisis_grupos <- transacciones[,
.(
grupo_cliente = .GRP,
num_transacciones = .N,
monto_promedio = round(mean(monto_final), 2),
primera_compra = min(fecha),
ultima_compra = max(fecha)
),
by = tipo_cliente
]
print(analisis_grupos)
#> tipo_cliente grupo_cliente num_transacciones monto_promedio primera_compra
#> <char> <int> <int> <num> <Date>
#> 1: Basic 1 502 223.58 2023-01-01
#> 2: Regular 2 297 207.99 2023-01-02
#> 3: Premium 3 201 219.04 2023-01-02
#> ultima_compra
#> <Date>
#> 1: 2023-12-31
#> 2: 2023-12-31
#> 3: 2023-12-303.5 Combinando Símbolos: Casos de Uso Profesionales
3.5.1 1. Análisis de Cohortes
# Análisis de cohortes de empleados por año de ingreso
library(lubridate)
analisis_cohortes <- empleados_avanzado[,
.(
cohorte_id = .GRP,
año_ingreso = year(min(fecha_ingreso)),
empleados_iniciales = .N,
salario_inicial_promedio = round(mean(salario_base), 0),
retencion_actual = round(mean(activo) * 100, 1),
performance_promedio = round(mean(rating_performance), 2),
# Usando .SD para métricas adicionales
experiencia_rango = paste0(min(.SD$años_exp), "-", max(.SD$años_exp), " años")
),
by = .(año_cohorte = year(fecha_ingreso)),
.SDcols = "años_exp"
][order(año_cohorte)]
print(analisis_cohortes)
#> año_cohorte cohorte_id año_ingreso empleados_iniciales
#> <num> <int> <num> <int>
#> 1: 2015 1 2015 1
#> 2: 2016 7 2016 4
#> 3: 2017 8 2017 2
#> 4: 2018 3 2018 3
#> 5: 2019 6 2019 1
#> 6: 2021 2 2021 3
#> 7: 2022 5 2022 3
#> 8: 2023 4 2023 3
#> salario_inicial_promedio retencion_actual performance_promedio
#> <num> <num> <num>
#> 1: 65000 100.0 3.90
#> 2: 50875 100.0 4.30
#> 3: 50550 100.0 4.10
#> 4: 36267 66.7 4.00
#> 5: 58600 100.0 4.30
#> 6: 43533 100.0 4.40
#> 7: 51567 100.0 3.60
#> 8: 42967 100.0 4.43
#> experiencia_rango
#> <char>
#> 1: 8-8 años
#> 2: 1-14 años
#> 3: 2-2 años
#> 4: 7-13 años
#> 5: 12-12 años
#> 6: 1-11 años
#> 7: 4-10 años
#> 8: 11-15 años3.5.2 2. Detección de Outliers por Grupo
# Detectar outliers en salario dentro de cada departamento
outliers_salario <- empleados_avanzado[,
{
Q1 <- quantile(salario_total, 0.25)
Q3 <- quantile(salario_total, 0.75)
IQR <- Q3 - Q1
limite_inferior <- Q1 - 1.5 * IQR
limite_superior <- Q3 + 1.5 * IQR
outliers_indices <- .I[salario_total < limite_inferior | salario_total > limite_superior]
list(
departamento = unique(departamento),
num_outliers = length(outliers_indices),
outliers_ids = if(length(outliers_indices) > 0) list(outliers_indices) else list(integer(0)),
limite_inf = round(limite_inferior, 0),
limite_sup = round(limite_superior, 0)
)
},
by = departamento
]
print(outliers_salario)
#> departamento departamento num_outliers outliers_ids limite_inf limite_sup
#> <char> <char> <int> <list> <num> <num>
#> 1: Ventas Ventas 0 -188 105112
#> 2: IT IT 0 17575 87975
#> 3: Marketing Marketing 0 43750 69350
#> 4: RRHH RRHH 0 36725 68925
#> 5: Finanzas Finanzas 0 14888 99588
# Extraer los outliers reales
outliers_reales <- unique(unlist(outliers_salario$outliers_ids))
if(length(outliers_reales) > 0) {
empleados_outliers <- empleados_avanzado[outliers_reales]
print("\nEmpleados con salarios outliers:")
print(empleados_outliers[, .(nombre, departamento, salario_total)])
}3.5.3 3. Ventana Móvil con .SD
# Análisis de ventana móvil en transacciones
# Primero ordenamos por fecha
transacciones_ordenadas <- transacciones[order(fecha)]
# Crear grupos por mes para simular ventana temporal
ventana_movil <- transacciones_ordenadas[,
{
# Para cada mes, calcular métricas usando .SD
current_data <- .SD
list(
mes = unique(mes),
transacciones_mes = .N,
monto_total = sum(monto_final),
ticket_promedio = round(mean(monto_final), 2),
# Diversidad de productos
productos_unicos = uniqueN(producto_id),
# Análisis de canales usando .SD
canal_dominante = names(sort(table(.SD$canal), decreasing = TRUE))[1],
concentracion_clientes = round(1 - (uniqueN(cliente_id) / .N), 3) # Índice de concentración
)
},
by = .(año = year(fecha), mes),
.SDcols = c("monto_final", "producto_id", "canal", "cliente_id")
][order(año, mes)]
print(ventana_movil)
#> año mes mes transacciones_mes monto_total ticket_promedio
#> <num> <int> <int> <int> <num> <num>
#> 1: 2023 1 1 86 17489.98 203.37
#> 2: 2023 2 2 86 18022.41 209.56
#> 3: 2023 3 3 73 14743.75 201.97
#> 4: 2023 4 4 87 18146.44 208.58
#> 5: 2023 5 5 86 17393.11 202.25
#> ---
#> 8: 2023 8 8 87 18408.98 211.60
#> 9: 2023 9 9 72 17707.42 245.94
#> 10: 2023 10 10 88 19787.56 224.86
#> 11: 2023 11 11 84 18717.37 222.83
#> 12: 2023 12 12 90 20800.00 231.11
#> productos_unicos canal_dominante concentracion_clientes
#> <int> <char> <num>
#> 1: 10 Online 0.337
#> 2: 10 Online 0.326
#> 3: 10 Online 0.315
#> 4: 10 Online 0.253
#> 5: 10 Online 0.395
#> ---
#> 8: 10 Online 0.322
#> 9: 10 Online 0.375
#> 10: 10 Online 0.273
#> 11: 10 Online 0.274
#> 12: 10 Online 0.2673.6 Ejercicios Prácticos
# 1. Estadísticas con .SD por categoría y canal
estadisticas_SD <- transacciones[,
lapply(.SD, function(x) {
if(is.numeric(x)) {
list(
promedio = round(mean(x, na.rm = TRUE), 2),
mediana = round(median(x, na.rm = TRUE), 2),
desv_std = round(sd(x, na.rm = TRUE), 2)
)
} else {
list(valores_unicos = length(unique(x)))
}
}),
by = .(categoria, canal),
.SDcols = c("monto", "monto_final", "descuento")
]
print("1. Estadísticas por categoría y canal:")
#> [1] "1. Estadísticas por categoría y canal:"
print(head(estadisticas_SD, 8))
#> categoria canal monto monto_final descuento
#> <char> <char> <list> <list> <list>
#> 1: Sports Online 262.85 224.49 0.15
#> 2: Sports Online 251.83 219.81 0.15
#> 3: Sports Online 133 116.98 0.09
#> 4: Electronics Mobile 236.1 198.43 0.15
#> 5: Electronics Mobile 191.67 152.54 0.14
#> 6: Electronics Mobile 157.27 130.66 0.09
#> 7: Books Mobile 248.05 210.83 0.16
#> 8: Books Mobile 244.29 208.4 0.17
# 2. Análisis con .SDcols específicas
analisis_montos <- transacciones[,
lapply(.SD, function(x) c(
min = min(x),
max = max(x),
rango = max(x) - min(x),
coef_var = sd(x) / mean(x)
)),
by = categoria,
.SDcols = patterns("monto|descuento")
]
print("\n2. Análisis de montos y descuentos:")
#> [1] "\n2. Análisis de montos y descuentos:"
print(analisis_montos)
#> categoria monto descuento monto_final
#> <char> <num> <num> <num>
#> 1: Sports 13.6900000 0.0000000 11.4996000
#> 2: Sports 498.1800000 0.3000000 465.4980000
#> 3: Sports 484.4900000 0.3000000 453.9984000
#> 4: Sports 0.5180114 0.5658237 0.5377436
#> 5: Electronics 12.2600000 0.0000000 10.4796000
#> ---
#> 12: Books 0.5671143 0.6141504 0.5912270
#> 13: Clothing 10.1700000 0.0000000 9.2547000
#> 14: Clothing 497.7800000 0.3000000 489.6243000
#> 15: Clothing 487.6100000 0.3000000 480.3696000
#> 16: Clothing 0.5622603 0.5388315 0.5733788
# 3. Top 3 transacciones por categoría usando .I
indices_top3 <- transacciones[,
.I[order(-monto_final)][1:min(3, .N)],
by = categoria
]
top3_transacciones <- transacciones[indices_top3$V1]
print("\n3. Top 3 transacciones por categoría:")
#> [1] "\n3. Top 3 transacciones por categoría:"
print(top3_transacciones[, .(categoria, transaction_id, monto_final, producto_id)][order(categoria, -monto_final)])
#> categoria transaction_id monto_final producto_id
#> <char> <int> <num> <char>
#> 1: Books 235 480.2000 D
#> 2: Books 826 479.8431 C
#> 3: Books 545 475.1424 F
#> 4: Clothing 304 489.6243 E
#> 5: Clothing 718 486.0300 I
#> ---
#> 8: Electronics 225 477.9190 E
#> 9: Electronics 636 476.8132 C
#> 10: Sports 148 465.4980 E
#> 11: Sports 776 465.4642 H
#> 12: Sports 73 462.3894 C
# 4. IDs únicos con .GRP
grupos_categoria_canal <- transacciones[,
.(
grupo_id = .GRP,
transacciones = .N,
monto_promedio = round(mean(monto_final), 2)
),
by = .(categoria, canal)
][order(grupo_id)]
print("\n4. IDs de grupos categoría-canal:")
#> [1] "\n4. IDs de grupos categoría-canal:"
print(grupos_categoria_canal)
#> categoria canal grupo_id transacciones monto_promedio
#> <char> <char> <int> <int> <num>
#> 1: Sports Online 1 125 224.49
#> 2: Electronics Mobile 2 49 198.43
#> 3: Books Mobile 3 47 210.83
#> 4: Electronics Online 4 125 228.96
#> 5: Electronics Store 5 77 196.52
#> ---
#> 8: Books Online 8 129 222.99
#> 9: Clothing Store 9 85 200.10
#> 10: Sports Mobile 10 48 237.55
#> 11: Sports Store 11 76 222.88
#> 12: Clothing Mobile 12 41 225.89
# 5. Análisis combinado ultra-avanzado
analisis_completo <- transacciones[,
{
# Usar todos los símbolos en un análisis complejo
grupo_id <- .GRP
num_trans <- .N
# Estadísticas básicas con .SD
stats_basicas <- lapply(.SD[, .(monto_final, descuento)], function(x) {
c(media = mean(x), mediana = median(x))
})
# Top performer con .I
top_transaction_idx <- .I[which.max(monto_final)]
# Análisis de clientes
clientes_unicos <- uniqueN(cliente_id)
cliente_top <- cliente_id[which.max(monto_final)]
list(
grupo_id = grupo_id,
categoria = unique(categoria),
canal = unique(canal),
num_transacciones = num_trans,
monto_promedio = round(stats_basicas$monto_final["media"], 2),
monto_mediana = round(stats_basicas$monto_final["mediana"], 2),
descuento_promedio = round(stats_basicas$descuento["media"], 3),
clientes_unicos = clientes_unicos,
concentracion = round(1 - (clientes_unicos / num_trans), 3),
top_transaction_id = transacciones[top_transaction_idx, transaction_id],
top_cliente_id = cliente_top,
diversidad_productos = uniqueN(producto_id)
)
},
by = .(categoria, canal),
.SDcols = c("monto_final", "descuento", "cliente_id", "producto_id")
][order(-monto_promedio)]
print("\n5. Análisis completo combinando todos los símbolos:")
#> [1] "\n5. Análisis completo combinando todos los símbolos:"
print(analisis_completo)
#> categoria canal grupo_id categoria canal num_transacciones
#> <char> <char> <int> <char> <char> <int>
#> 1: Sports Mobile 10 Sports Mobile 48
#> 2: Electronics Online 4 Electronics Online 125
#> 3: Clothing Mobile 12 Clothing Mobile 41
#> 4: Sports Online 1 Sports Online 125
#> 5: Books Store 7 Books Store 77
#> ---
#> 8: Clothing Online 6 Clothing Online 121
#> 9: Books Mobile 3 Books Mobile 47
#> 10: Clothing Store 9 Clothing Store 85
#> 11: Electronics Mobile 2 Electronics Mobile 49
#> 12: Electronics Store 5 Electronics Store 77
#> monto_promedio monto_mediana descuento_promedio clientes_unicos
#> <num> <num> <num> <int>
#> 1: 237.55 272.55 0.166 39
#> 2: 228.96 235.17 0.155 77
#> 3: 225.89 244.68 0.149 34
#> 4: 224.49 219.81 0.150 76
#> 5: 223.28 214.69 0.133 54
#> ---
#> 8: 215.08 216.92 0.160 66
#> 9: 210.83 208.40 0.164 35
#> 10: 200.10 176.29 0.155 57
#> 11: 198.43 152.54 0.153 33
#> 12: 196.52 185.82 0.139 58
#> concentracion top_transaction_id top_cliente_id diversidad_productos
#> <num> <int> <int> <int>
#> 1: 0.188 776 88 10
#> 2: 0.384 74 74 10
#> 3: 0.171 780 76 10
#> 4: 0.392 148 53 10
#> 5: 0.299 242 57 10
#> ---
#> 8: 0.455 304 41 10
#> 9: 0.255 545 88 10
#> 10: 0.329 718 50 10
#> 11: 0.327 636 98 10
#> 12: 0.247 788 52 10
# Mostrar tabla final formateada para PDF
knitr::kable(
analisis_completo,
caption = "Análisis Completo por Categoría y Canal",
digits = 2,
format.args = list(big.mark = ",")
)| categoria | canal | grupo_id | categoria | canal | num_transacciones | monto_promedio | monto_mediana | descuento_promedio | clientes_unicos | concentracion | top_transaction_id | top_cliente_id | diversidad_productos |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Sports | Mobile | 10 | Sports | Mobile | 48 | 237.55 | 272.55 | 0.17 | 39 | 0.19 | 776 | 88 | 10 |
| Electronics | Online | 4 | Electronics | Online | 125 | 228.96 | 235.17 | 0.16 | 77 | 0.38 | 74 | 74 | 10 |
| Clothing | Mobile | 12 | Clothing | Mobile | 41 | 225.89 | 244.68 | 0.15 | 34 | 0.17 | 780 | 76 | 10 |
| Sports | Online | 1 | Sports | Online | 125 | 224.49 | 219.81 | 0.15 | 76 | 0.39 | 148 | 53 | 10 |
| Books | Store | 7 | Books | Store | 77 | 223.28 | 214.69 | 0.13 | 54 | 0.30 | 242 | 57 | 10 |
| Books | Online | 8 | Books | Online | 129 | 222.99 | 205.71 | 0.14 | 69 | 0.47 | 235 | 99 | 10 |
| Sports | Store | 11 | Sports | Store | 76 | 222.88 | 227.40 | 0.16 | 52 | 0.32 | 73 | 54 | 10 |
| Clothing | Online | 6 | Clothing | Online | 121 | 215.08 | 216.92 | 0.16 | 66 | 0.46 | 304 | 41 | 10 |
| Books | Mobile | 3 | Books | Mobile | 47 | 210.83 | 208.40 | 0.16 | 35 | 0.26 | 545 | 88 | 10 |
| Clothing | Store | 9 | Clothing | Store | 85 | 200.10 | 176.29 | 0.16 | 57 | 0.33 | 718 | 50 | 10 |
| Electronics | Mobile | 2 | Electronics | Mobile | 49 | 198.43 | 152.54 | 0.15 | 33 | 0.33 | 636 | 98 | 10 |
| Electronics | Store | 5 | Electronics | Store | 77 | 196.52 | 185.82 | 0.14 | 58 | 0.25 | 788 | 52 | 10 |
3.7 Patrones Avanzados y Mejores Prácticas
3.7.1 1. Funciones Personalizadas con .SD
# Crear función personalizada para análisis estadístico
analisis_estadistico <- function(dt, by_vars, numeric_cols) {
dt[,
lapply(.SD, function(x) {
if(is.numeric(x) && length(unique(x)) > 1) {
list(
n = length(x),
media = round(mean(x, na.rm = TRUE), 2),
mediana = round(median(x, na.rm = TRUE), 2),
q25 = round(quantile(x, 0.25, na.rm = TRUE), 2),
q75 = round(quantile(x, 0.75, na.rm = TRUE), 2),
desv_std = round(sd(x, na.rm = TRUE), 2),
cv = round(sd(x, na.rm = TRUE) / mean(x, na.rm = TRUE), 3),
asimetria = round((mean(x) - median(x)) / sd(x), 3)
)
} else {
list(valores_unicos = length(unique(x)))
}
}),
by = by_vars,
.SDcols = numeric_cols
]
}
# Aplicar la función
resultado_personalizado <- analisis_estadistico(
empleados_avanzado,
by_vars = "departamento",
numeric_cols = c("salario_total", "años_exp", "rating_performance")
)
print(resultado_personalizado)
#> departamento salario_total años_exp rating_performance
#> <char> <list> <list> <list>
#> 1: Ventas 4 4 4
#> 2: Ventas 52175 11.75 3.85
#> 3: Ventas 52750 12 3.7
#> 4: Ventas 39300 10.25 3.42
#> 5: Ventas 65625 13.5 4.12
#> ---
#> 36: Finanzas 46650 3.5 3.38
#> 37: Finanzas 67825 5.75 3.85
#> 38: Finanzas 13198.07 2.5 0.45
#> 39: Finanzas 0.228 0.526 0.122
#> 40: Finanzas 0.097 0.1 0.2783.7.2 2. Optimización de Performance
# ✅ HACER: Usar .SDcols para limitar columnas
# Más eficiente
empleados[, lapply(.SD, mean), by = dept, .SDcols = c("sal", "exp")]
# ❌ NO HACER: Procesar todas las columnas innecesariamente
# Menos eficiente
empleados[, lapply(.SD, mean), by = dept]
# ✅ HACER: Combinar operaciones en una sola expresión
# Más eficiente
empleados[, {
list(
media_sal = mean(salario),
max_exp = max(años_exp),
grupo_id = .GRP
)
}, by = dept]
# ❌ NO HACER: Múltiples pasadas por los datos
# Menos eficiente
media_sal <- empleados[, mean(salario), by = dept]
max_exp <- empleados[, max(años_exp), by = dept].SDes un mini-data.table con los datos del grupo actual - úsalo conlapply()para operaciones múltiples.SDcolscontrola qué columnas incluye.SD- esencial para performance y precisión.Ite da los índices reales - perfecto para top-N, muestreo y filtrado avanzado.GRPasigna IDs únicos a grupos - útil para tracking y análisis de cohortes- Combinar símbolos permite análisis complejos en una sola expresión
- Performance: Limita
.SDcolsy combina operaciones para máxima eficiencia
Con el dominio de estos símbolos especiales, tienes las herramientas para realizar análisis de datos sofisticados y eficientes. En el próximo módulo exploraremos técnicas de manipulación intermedia como encadenamiento y joins.