8  Remodelación de Datos: melt() y dcast()

En este capítulo dominarás
  • melt(): Transformar datos de ancho a largo (wide to long)
  • dcast(): Transformar datos de largo a ancho (long to wide)
  • Patrones complejos de reshape con múltiples variables
  • Agregaciones durante reshape con funciones personalizadas
  • Casos de uso reales: reportes, visualización, y análisis estadístico
  • Combinación con otras técnicas de data.table

8.1 Conceptos Fundamentales: Wide vs Long

Antes de explorar melt() y dcast(), es crucial entender cuándo y por qué transformar estructuras de datos:

  • Formato Wide (ancho): Cada variable tiene su propia columna. Fácil de leer pero difícil de analizar estadísticamente.
  • Formato Long (largo): Las observaciones se “apilan” en filas. Ideal para análisis estadístico y visualización.
# Ejemplo simple: formato wide
datos_wide_ejemplo <- data.table(
  estudiante = c("Ana", "Juan", "María"),
  matematicas = c(85, 90, 78),
  ciencias = c(88, 85, 92),
  historia = c(82, 88, 89)
)

print("Formato WIDE (típico de Excel):")
#> [1] "Formato WIDE (típico de Excel):"
print(datos_wide_ejemplo)
#>    estudiante matematicas ciencias historia
#>        <char>       <num>    <num>    <num>
#> 1:        Ana          85       88       82
#> 2:       Juan          90       85       88
#> 3:      María          78       92       89

# Convertir a formato long
datos_long_ejemplo <- melt(datos_wide_ejemplo, 
                          id.vars = "estudiante",
                          variable.name = "materia", 
                          value.name = "calificacion")

print("\nFormato LONG (ideal para análisis):")
#> [1] "\nFormato LONG (ideal para análisis):"
print(datos_long_ejemplo)
#>    estudiante     materia calificacion
#>        <char>      <fctr>        <num>
#> 1:        Ana matematicas           85
#> 2:       Juan matematicas           90
#> 3:      María matematicas           78
#> 4:        Ana    ciencias           88
#> 5:       Juan    ciencias           85
#> 6:      María    ciencias           92
#> 7:        Ana    historia           82
#> 8:       Juan    historia           88
#> 9:      María    historia           89

8.2 melt(): De Ancho a Largo

8.2.1 1. melt() Básico

# Transformar datos de ventas trimestrales
ventas_long <- melt(ventas_trimestral_wide,
                   id.vars = c("producto", "categoria", "precio_unitario"),
                   variable.name = "periodo_metrica", 
                   value.name = "valor")

print("Datos transformados a formato largo:")
#> [1] "Datos transformados a formato largo:"
print(head(ventas_long, 12))
#>       producto    categoria precio_unitario  periodo_metrica  valor
#>         <char>       <char>           <num>           <fctr>  <num>
#>  1:     Laptop Computadoras            1200 Q1_2023_unidades    150
#>  2:    Desktop Computadoras             800 Q1_2023_unidades     80
#>  3:     Tablet Computadoras             400 Q1_2023_unidades    200
#>  4: Smartphone      Móviles             600 Q1_2023_unidades    300
#>  5:    Monitor  Periféricos             300 Q1_2023_unidades    120
#> ---                                                                
#>  8:  Impresora      Oficina             200 Q1_2023_unidades     60
#>  9:     Router        Redes             150 Q1_2023_unidades     90
#> 10:     Cámara   Multimedia             250 Q1_2023_unidades     70
#> 11:     Laptop Computadoras            1200  Q1_2023_revenue 180000
#> 12:    Desktop Computadoras             800  Q1_2023_revenue  64000

# Ver la estructura completa
cat("Dimensiones originales:", dim(ventas_trimestral_wide), "\n")
#> Dimensiones originales: 10 13
cat("Dimensiones después de melt:", dim(ventas_long), "\n")
#> Dimensiones después de melt: 100 5

8.2.2 2. Separar Variables Complejas

# Separar periodo y métrica de la variable compuesta
ventas_long_separada <- ventas_long[, `:=`(
  periodo = sub("_unidades|_revenue", "", periodo_metrica),
  metrica = ifelse(grepl("unidades", periodo_metrica), "unidades", "revenue")
)]

print("Datos con variables separadas:")
#> [1] "Datos con variables separadas:"
print(head(ventas_long_separada, 12))
#>       producto    categoria precio_unitario  periodo_metrica  valor periodo
#>         <char>       <char>           <num>           <fctr>  <num>  <char>
#>  1:     Laptop Computadoras            1200 Q1_2023_unidades    150 Q1_2023
#>  2:    Desktop Computadoras             800 Q1_2023_unidades     80 Q1_2023
#>  3:     Tablet Computadoras             400 Q1_2023_unidades    200 Q1_2023
#>  4: Smartphone      Móviles             600 Q1_2023_unidades    300 Q1_2023
#>  5:    Monitor  Periféricos             300 Q1_2023_unidades    120 Q1_2023
#> ---                                                                        
#>  8:  Impresora      Oficina             200 Q1_2023_unidades     60 Q1_2023
#>  9:     Router        Redes             150 Q1_2023_unidades     90 Q1_2023
#> 10:     Cámara   Multimedia             250 Q1_2023_unidades     70 Q1_2023
#> 11:     Laptop Computadoras            1200  Q1_2023_revenue 180000 Q1_2023
#> 12:    Desktop Computadoras             800  Q1_2023_revenue  64000 Q1_2023
#>      metrica
#>       <char>
#>  1: unidades
#>  2: unidades
#>  3: unidades
#>  4: unidades
#>  5: unidades
#> ---         
#>  8: unidades
#>  9: unidades
#> 10: unidades
#> 11:  revenue
#> 12:  revenue

# Limpiar columna temporal
ventas_long_separada[, periodo_metrica := NULL]

8.2.3 3. melt() con Patrones

# Melt usando patrones para múltiples tipos de variables
empleados_long <- melt(empleados_metricas,
                      id.vars = c("empleado_id", "nombre", "departamento", "nivel", "salario_base"),
                      measure = patterns(
                        bonus = "^bonus_",
                        evaluacion = "^evaluacion_", 
                        proyectos = "^proyectos_"
                      ),
                      variable.name = "trimestre",
                      value.name = c("bonus", "evaluacion", "proyectos"))

# Limpiar nombres de trimestre
empleados_long[, trimestre := paste0("Q", trimestre)]

print("Empleados con múltiples métricas en formato largo:")
#> [1] "Empleados con múltiples métricas en formato largo:"
print(head(empleados_long, 12))
#>     empleado_id     nombre departamento   nivel salario_base trimestre bonus
#>          <char>     <char>       <char>  <char>        <num>    <char> <num>
#>  1:     EMP_001 Empleado_A       Ventas  Junior        72700        Q1   378
#>  2:     EMP_002 Empleado_B       Ventas  Senior        49400        Q1   896
#>  3:     EMP_003 Empleado_C       Ventas    Lead        65600        Q1  5443
#>  4:     EMP_004 Empleado_D       Ventas Manager        66400        Q1  6800
#>  5:     EMP_005 Empleado_E       Ventas  Junior        55600        Q1  6379
#> ---                                                                         
#>  8:     EMP_008 Empleado_H           IT Manager        48600        Q1  6525
#>  9:     EMP_009 Empleado_I           IT  Junior        74400        Q1     4
#> 10:     EMP_010 Empleado_J           IT  Senior        40400        Q1   169
#> 11:     EMP_011 Empleado_K    Marketing    Lead        75600        Q1  3529
#> 12:     EMP_012 Empleado_L    Marketing Manager        77900        Q1  2322
#>     evaluacion proyectos
#>          <num>     <int>
#>  1:        3.6         7
#>  2:        4.3         1
#>  3:        3.4         6
#>  4:        4.1         5
#>  5:        3.5         2
#> ---                     
#>  8:        3.1         2
#>  9:        3.5         1
#> 10:        4.6         3
#> 11:        3.2         1
#> 12:        4.2         1

8.2.4 4. melt() Avanzado para Sensores

# Melt complejo para datos de sensores múltiples
sensores_long <- melt(sensores_multiples,
                     id.vars = c("timestamp", "ubicacion"),
                     variable.name = "sensor_completo",
                     value.name = "medicion")[, `:=`(
  # Extraer tipo de sensor y número
  tipo_sensor = sub("_[0-9]+$", "", sensor_completo),
  numero_sensor = as.numeric(sub(".*_", "", sensor_completo))
)][, sensor_completo := NULL]  # Limpiar columna temporal

print("Sensores en formato largo:")
#> [1] "Sensores en formato largo:"
print(head(sensores_long, 15))
#>               timestamp    ubicacion medicion tipo_sensor numero_sensor
#>                  <POSc>       <char>    <num>      <char>         <num>
#>  1: 2024-01-01 00:00:00 Planta_Norte     19.9 sensor_temp             1
#>  2: 2024-01-01 06:00:00 Planta_Norte     19.9 sensor_temp             1
#>  3: 2024-01-01 12:00:00 Planta_Norte     24.5 sensor_temp             1
#>  4: 2024-01-01 18:00:00 Planta_Norte     23.4 sensor_temp             1
#>  5: 2024-01-02 00:00:00 Planta_Norte     24.1 sensor_temp             1
#> ---                                                                    
#> 11: 2024-01-03 12:00:00 Planta_Norte     23.5 sensor_temp             1
#> 12: 2024-01-03 18:00:00 Planta_Norte     23.6 sensor_temp             1
#> 13: 2024-01-04 00:00:00 Planta_Norte     21.1 sensor_temp             1
#> 14: 2024-01-04 06:00:00 Planta_Norte     21.3 sensor_temp             1
#> 15: 2024-01-04 12:00:00 Planta_Norte     20.3 sensor_temp             1

# Estadísticas por tipo de sensor
stats_sensores <- sensores_long[, .(
  mediciones = .N,
  promedio = round(mean(medicion, na.rm = TRUE), 2),
  minimo = round(min(medicion, na.rm = TRUE), 2),
  maximo = round(max(medicion, na.rm = TRUE), 2),
  desviacion = round(sd(medicion, na.rm = TRUE), 2)
), by = .(tipo_sensor, ubicacion)]

print("\nEstadísticas por tipo de sensor y ubicación:")
#> [1] "\nEstadísticas por tipo de sensor y ubicación:"
print(stats_sensores)
#>       tipo_sensor     ubicacion mediciones promedio minimo maximo desviacion
#>            <char>        <char>      <int>    <num>  <num>  <num>      <num>
#> 1:    sensor_temp  Planta_Norte         56    21.08   13.5   27.1       3.38
#> 2:    sensor_temp    Planta_Sur         56    21.15   13.1   26.4       3.31
#> 3:    sensor_temp Planta_Centro         56    21.24   14.5   26.9       3.21
#> 4: sensor_humedad  Planta_Norte         56    64.10   42.9   79.8       8.65
#> 5: sensor_humedad    Planta_Sur         56    64.20   42.6   77.6      10.36
#> 6: sensor_humedad Planta_Centro         56    58.45   40.7   78.6       9.29
#> 7: sensor_presion  Planta_Norte         56  1021.35  999.9 1044.3      11.25
#> 8: sensor_presion    Planta_Sur         56  1007.93  987.8 1033.3      12.23
#> 9: sensor_presion Planta_Centro         56  1020.05  997.3 1040.6      11.55

8.3 dcast(): De Largo a Ancho

8.3.1 1. dcast() Básico

# Reconstruir formato ancho desde formato largo
ventas_reconstruida <- dcast(ventas_long_separada, 
                            producto + categoria + precio_unitario ~ periodo + metrica,
                            value.var = "valor")

print("Datos reconstruidos a formato ancho:")
#> [1] "Datos reconstruidos a formato ancho:"
print(head(ventas_reconstruida))
#> Key: <producto, categoria, precio_unitario>
#>     producto    categoria precio_unitario Q1_2023_revenue Q1_2023_unidades
#>       <char>       <char>           <num>           <num>            <num>
#> 1:    Cámara   Multimedia             250           17500               70
#> 2:   Desktop Computadoras             800           64000               80
#> 3: Impresora      Oficina             200           12000               60
#> 4:    Laptop Computadoras            1200          180000              150
#> 5:   Monitor  Periféricos             300           36000              120
#> 6:     Mouse  Periféricos              25           11250              450
#>    Q1_2024_revenue Q1_2024_unidades Q2_2023_revenue Q2_2023_unidades
#>              <num>            <num>           <num>            <num>
#> 1:           20000               80           18750               75
#> 2:           68000               85           60000               75
#> 3:           15000               75           11000               55
#> 4:          228000              190          216000              180
#> 5:           51000              170           42000              140
#> 6:           13750              550           12000              480
#>    Q3_2023_revenue Q3_2023_unidades Q4_2023_revenue Q4_2023_unidades
#>              <num>            <num>           <num>            <num>
#> 1:           21250               85           22500               90
#> 2:           72000               90           76000               95
#> 3:           14000               70           16000               80
#> 4:          240000              200          264000              220
#> 5:           48000              160           54000              180
#> 6:           12500              500           15000              600

# Verificar que coincide con datos originales
cat("¿Reconstrucción exitosa?", 
    nrow(ventas_reconstruida) == nrow(ventas_trimestral_wide) && 
    ncol(ventas_reconstruida) >= ncol(ventas_trimestral_wide) - 2, "\n")
#> ¿Reconstrucción exitosa? TRUE

8.3.2 2. dcast() con Agregación

# Crear tabla resumen: promedio por producto y año
ventas_resumen_anual <- ventas_long_separada[, 
  año := ifelse(grepl("2023", periodo), "2023", "2024")
][, .(
  valor_promedio = round(mean(valor), 0)
), by = .(producto, categoria, año, metrica)]

# Convertir a formato ancho con agregación
resumen_wide <- dcast(ventas_resumen_anual,
                     producto + categoria ~ año + metrica,
                     value.var = "valor_promedio")

print("Resumen anual en formato ancho:")
#> [1] "Resumen anual en formato ancho:"
print(resumen_wide)
#> Key: <producto, categoria>
#>       producto    categoria 2023_revenue 2023_unidades 2024_revenue
#>         <char>       <char>        <num>         <num>        <num>
#>  1:     Cámara   Multimedia        20000            80        20000
#>  2:    Desktop Computadoras        68000            85        68000
#>  3:  Impresora      Oficina        13250            66        15000
#>  4:     Laptop Computadoras       225000           188       228000
#>  5:    Monitor  Periféricos        45000           150        51000
#>  6:      Mouse  Periféricos        12688           508        13750
#>  7:     Router        Redes        14438            96        15750
#>  8: Smartphone      Móviles       205500           342       228000
#>  9:     Tablet Computadoras        76000           190        80000
#> 10:    Teclado  Periféricos        21250           425        22500
#>     2024_unidades
#>             <num>
#>  1:            80
#>  2:            85
#>  3:            75
#>  4:           190
#>  5:           170
#>  6:           550
#>  7:           105
#>  8:           380
#>  9:           200
#> 10:           450

8.3.3 3. dcast() con Funciones de Agregación Personalizadas

# Análisis completo de empleados por departamento
empleados_analisis <- dcast(empleados_long,
                           departamento + nivel ~ .,
                           value.var = c("bonus", "evaluacion", "proyectos"),
                           fun.aggregate = list(
                             mean = mean,
                             max = max,
                             min = min
                           ))

print("Análisis agregado de empleados:")
#> [1] "Análisis agregado de empleados:"
print(empleados_analisis)
#> Key: <departamento, nivel>
#>     departamento   nivel bonus_mean evaluacion_mean proyectos_mean bonus_max
#>           <char>  <char>      <num>           <num>          <num>     <num>
#>  1:           IT  Junior    2997.50           4.200          3.250      6544
#>  2:           IT    Lead    8586.00           3.750          7.750     14636
#>  3:           IT Manager    5640.25           3.950          4.750      8626
#>  4:           IT  Senior    5855.50           4.350          4.125     12881
#>  5:    Marketing  Junior    5739.75           4.150          4.250      9434
#> ---                                                                         
#> 12:         RRHH  Senior    4420.25           3.275          6.500      7729
#> 13:       Ventas  Junior    5225.00           4.200          5.875     10347
#> 14:       Ventas    Lead    5906.00           4.000          6.250      7472
#> 15:       Ventas Manager    4664.00           3.750          5.250      7673
#> 16:       Ventas  Senior    4700.00           3.975          2.750      9105
#>     evaluacion_max proyectos_max bonus_min evaluacion_min proyectos_min
#>              <num>         <int>     <num>          <num>         <int>
#>  1:            4.9             7         4            3.5             1
#>  2:            4.2            12      4157            3.3             5
#>  3:            4.7             9      2792            3.1             1
#>  4:            5.0             8       169            3.2             1
#>  5:            4.8             9      1410            3.2             2
#> ---                                                                    
#> 12:            3.4            12       431            3.1             1
#> 13:            4.9            12       378            3.5             2
#> 14:            4.7             9      4470            3.3             3
#> 15:            4.1            11       264            3.4             1
#> 16:            4.6             5       896            3.3             1

8.3.4 4. Tablas de Contingencia con dcast()

# Transformar encuesta a formato largo primero
encuesta_long <- melt(encuesta_satisfaccion,
                     id.vars = c("respuesta_id", "cliente_id", "fecha_encuesta", "edad", "genero", "region"),
                     measure.vars = patterns("^satisfaccion_", "gasto_mensual"),
                     variable.name = "aspecto_satisfaccion",
                     value.name = c("puntuacion", "gasto_mensual"))

# Limpiar nombres de aspectos
encuesta_long[, aspecto := sub("satisfaccion_", "", aspecto_satisfaccion)]

# Crear tabla de contingencia: región vs aspecto (promedio de satisfacción)
tabla_contingencia <- dcast(encuesta_long,
                           region ~ aspecto,
                           value.var = "puntuacion", 
                           fun.aggregate = mean)

print("Tabla de contingencia: Satisfacción promedio por región y aspecto:")
#> [1] "Tabla de contingencia: Satisfacción promedio por región y aspecto:"
print(tabla_contingencia)
#> Key: <region>
#>    region        1        2        3        4
#>    <char>    <num>    <num>    <num>    <num>
#> 1:   Este 3.791667 3.875000 3.750000 3.375000
#> 2:  Norte 3.700000 3.500000 3.150000 3.150000
#> 3:  Oeste 3.750000 3.718750 3.062500 3.375000
#> 4:    Sur 4.083333 3.791667 3.208333 3.416667

# Matriz de correlación usando dcast
# Primero, crear datos en formato adecuado
matriz_correlacion_data <- encuesta_long[, .(
  satisfaccion_promedio = mean(puntuacion)
), by = .(cliente_id, aspecto)]

matriz_correlacion_wide <- dcast(matriz_correlacion_data,
                                cliente_id ~ aspecto,
                                value.var = "satisfaccion_promedio")

# Calcular correlaciones
cor_matrix <- cor(matriz_correlacion_wide[, -"cliente_id"], use = "complete.obs")
print("\nMatriz de correlaciones entre aspectos:")
#> [1] "\nMatriz de correlaciones entre aspectos:"
print(round(cor_matrix, 3))
#>        1      2      3      4
#> 1  1.000  0.153  0.228 -0.311
#> 2  0.153  1.000 -0.127 -0.090
#> 3  0.228 -0.127  1.000 -0.175
#> 4 -0.311 -0.090 -0.175  1.000

8.4 Casos de Uso Avanzados

8.4.1 1. Reportes Ejecutivos Dinámicos

# Crear reporte ejecutivo completo combinando melt/dcast
reporte_ejecutivo <- ventas_long_separada[
  # Calcular métricas adicionales
  , `:=`(
    año = ifelse(grepl("2023", periodo), "2023", "2024"),
    trimestre_num = as.numeric(substr(periodo, 2, 2))
  )
][
  # Agrupar y calcular KPIs
  , .(
    valor_total = sum(valor),
    productos_activos = uniqueN(producto)
  ), by = .(categoria, año, metrica)
]

# Crear formato ancho para reporte
reporte_ejecutivo <-  dcast(reporte_ejecutivo, categoria ~ año + metrica, value.var = "valor_total")

print("Reporte Ejecutivo de Ventas:")
#> [1] "Reporte Ejecutivo de Ventas:"
print(reporte_ejecutivo)
#> Key: <categoria>
#>       categoria 2023_revenue 2023_unidades 2024_revenue 2024_unidades
#>          <char>        <num>         <num>        <num>         <num>
#> 1: Computadoras      1476000          1850       376000           475
#> 2:   Multimedia        80000           320        20000            80
#> 3:      Móviles       822000          1370       228000           380
#> 4:      Oficina        53000           265        15000            75
#> 5:  Periféricos       315750          4330        87250          1170
#> 6:        Redes        57750           385        15750           105

# Calcular crecimiento año sobre año
reporte_con_crecimiento <- copy(reporte_ejecutivo)[, `:=`(
  crecimiento_revenue = round((get("2024_revenue") - get("2023_revenue")) / get("2023_revenue") * 100, 1),
  crecimiento_unidades = round((get("2024_unidades") - get("2023_unidades")) / get("2023_unidades") * 100, 1)
)]

print("\nReporte con análisis de crecimiento:")
#> [1] "\nReporte con análisis de crecimiento:"
print(reporte_con_crecimiento[, .(categoria, 
                                 revenue_2023 = `2023_revenue`, 
                                 revenue_2024 = `2024_revenue`,
                                 crecimiento_revenue,
                                 crecimiento_unidades)])
#> Key: <categoria>
#>       categoria revenue_2023 revenue_2024 crecimiento_revenue
#>          <char>        <num>        <num>               <num>
#> 1: Computadoras      1476000       376000               -74.5
#> 2:   Multimedia        80000        20000               -75.0
#> 3:      Móviles       822000       228000               -72.3
#> 4:      Oficina        53000        15000               -71.7
#> 5:  Periféricos       315750        87250               -72.4
#> 6:        Redes        57750        15750               -72.7
#>    crecimiento_unidades
#>                   <num>
#> 1:                -74.3
#> 2:                -75.0
#> 3:                -72.3
#> 4:                -71.7
#> 5:                -73.0
#> 6:                -72.7

8.4.2 2. Dashboard de Sensores en Tiempo Real

# Crear dashboard de estado actual de sensores
estado_actual_sensores <- sensores_long[
  # Obtener última lectura por sensor
  , .SD[.N], by = .(ubicacion, tipo_sensor, numero_sensor)
][
  # Clasificar estado según rangos normales
  , estado := fcase(
    tipo_sensor == "sensor_temp" & (medicion < 15 | medicion > 30), "ALERTA",
    tipo_sensor == "sensor_humedad" & (medicion < 30 | medicion > 80), "ALERTA", 
    tipo_sensor == "sensor_presion" & (medicion < 1000 | medicion > 1030), "ALERTA",
    default = "NORMAL"
  )
]

# Dashboard en formato ancho
dashboard_wide <- dcast(estado_actual_sensores,
                       ubicacion ~ tipo_sensor + numero_sensor,
                       value.var = "medicion")

print("Dashboard de Sensores (valores actuales):")
#> [1] "Dashboard de Sensores (valores actuales):"
print(dashboard_wide)
#> Key: <ubicacion>
#>        ubicacion sensor_humedad_1 sensor_humedad_2 sensor_presion_1
#>           <char>            <num>            <num>            <num>
#> 1: Planta_Centro             60.5             74.1           1006.4
#> 2:  Planta_Norte             42.9             58.0           1012.8
#> 3:    Planta_Sur             71.0             60.6           1015.7
#>    sensor_presion_2 sensor_temp_1 sensor_temp_2
#>               <num>         <num>         <num>
#> 1:            998.2          19.9          25.7
#> 2:            999.9          18.4          25.8
#> 3:           1029.3          19.4          26.0

# Tabla de alertas
alertas_sensores <- estado_actual_sensores[estado == "ALERTA"]
if(nrow(alertas_sensores) > 0) {
  print("\n🚨 ALERTAS ACTIVAS:")
  print(alertas_sensores[, .(ubicacion, tipo_sensor, numero_sensor, medicion, timestamp)])
} else {
  cat("\n✅ Todos los sensores operan en rangos normales\n")
}
#> [1] "\n🚨 ALERTAS ACTIVAS:"
#>        ubicacion    tipo_sensor numero_sensor medicion           timestamp
#>           <char>         <char>         <num>    <num>              <POSc>
#> 1:  Planta_Norte sensor_presion             2    999.9 2024-01-07 18:00:00
#> 2: Planta_Centro sensor_presion             2    998.2 2024-01-07 18:00:00

8.4.3 3. Análisis Multivariable de Encuestas

# Análisis completo de satisfacción por segmentos
analisis_satisfaccion <- encuesta_long[
  # Agregar segmentación demográfica
  , `:=`(
    grupo_edad = cut(edad, breaks = c(0, 30, 45, 60, 100), 
                    labels = c("Joven", "Adulto", "Maduro", "Senior")),
    gasto_categoria = cut(gasto_mensual, breaks = c(0, 100, 300, 500, Inf),
                         labels = c("Bajo", "Medio", "Alto", "Premium"))
  )
][
  # Calcular satisfacción promedio por segmento y aspecto
  , .(
    satisfaccion_promedio = round(mean(puntuacion), 2),
    respuestas = .N
  ), by = .(region, grupo_edad, gasto_categoria, aspecto)
]

# Crear matriz de satisfacción: aspecto vs segmento
matriz_satisfaccion <- dcast(analisis_satisfaccion,
                            region + grupo_edad + gasto_categoria ~ aspecto,
                            value.var = "satisfaccion_promedio",
                            fun.aggregate = mean)

print("Matriz de satisfacción por segmento:")
#> [1] "Matriz de satisfacción por segmento:"
print(head(matriz_satisfaccion, 10))
#> Key: <region, grupo_edad, gasto_categoria>
#>     region grupo_edad gasto_categoria     1     2     3     4
#>     <char>     <fctr>          <fctr> <num> <num> <num> <num>
#>  1:   Este      Joven            <NA>   NaN  4.00  4.00  3.50
#>  2:   Este      Joven            Bajo  4.00   NaN   NaN   NaN
#>  3:   Este      Joven           Medio  2.50   NaN   NaN   NaN
#>  4:   Este      Joven            Alto  4.00   NaN   NaN   NaN
#>  5:   Este      Joven         Premium  2.00   NaN   NaN   NaN
#>  6:   Este     Adulto            <NA>   NaN  3.57  3.14  3.00
#>  7:   Este     Adulto            Bajo  3.00   NaN   NaN   NaN
#>  8:   Este     Adulto           Medio  4.67   NaN   NaN   NaN
#>  9:   Este     Adulto            Alto  4.67   NaN   NaN   NaN
#> 10:   Este     Maduro            <NA>   NaN  4.17  3.83  3.33

# Verificar qué columnas se crearon después del dcast
print("Columnas en matriz_satisfaccion:")
#> [1] "Columnas en matriz_satisfaccion:"
print(names(matriz_satisfaccion))
#> [1] "region"          "grupo_edad"      "gasto_categoria" "1"              
#> [5] "2"               "3"               "4"

# Identificar segmentos críticos (satisfacción baja en múltiples aspectos)
# Primero verificar qué columnas de aspectos existen
columnas_aspectos <- names(matriz_satisfaccion)[!names(matriz_satisfaccion) %in% c("region", "grupo_edad", "gasto_categoria")]
print(paste("Columnas de aspectos encontradas:", paste(columnas_aspectos, collapse = ", ")))
#> [1] "Columnas de aspectos encontradas: 1, 2, 3, 4"

if(length(columnas_aspectos) >= 4) {
  # Si tenemos las 4 columnas esperadas (entrega, precio, producto, servicio)
  segmentos_criticos <- matriz_satisfaccion[
    # Calcular score de satisfacción general usando las columnas que existen
    , satisfaccion_general := round(rowMeans(.SD, na.rm = TRUE), 2), .SDcols = columnas_aspectos
  ][
    satisfaccion_general < 3.5  # Umbral crítico
  ][order(satisfaccion_general)]
} else {
  # Si no tenemos todas las columnas, usar un enfoque más simple
  cat("No se encontraron todas las columnas de aspectos esperadas. Usando análisis simplificado.\n")
  segmentos_criticos <- matriz_satisfaccion[1:0]  # Tabla vacía para evitar errores
}

if(nrow(segmentos_criticos) > 0) {
  print("\n⚠️ SEGMENTOS CRÍTICOS (satisfacción < 3.5):")
  print(segmentos_criticos[, .(region, grupo_edad, gasto_categoria, satisfaccion_general)])
} else {
  cat("\n✅ No hay segmentos con satisfacción crítica\n")
}
#> [1] "\n⚠️ SEGMENTOS CRÍTICOS (satisfacción < 3.5):"
#>     region grupo_edad gasto_categoria satisfaccion_general
#>     <char>     <fctr>          <fctr>                <num>
#>  1:   Este      Joven         Premium                 2.00
#>  2:   Este     Maduro         Premium                 2.00
#>  3:   Este      Joven           Medio                 2.50
#>  4:  Norte     Maduro         Premium                 2.50
#>  5:   Este     Adulto            Bajo                 3.00
#> ---                                                       
#> 21:  Oeste     Adulto            <NA>                 3.34
#> 22:  Norte     Adulto            <NA>                 3.39
#> 23:    Sur     Maduro            <NA>                 3.39
#> 24:    Sur     Adulto            <NA>                 3.46
#> 25:  Oeste     Senior            <NA>                 3.48

8.5 Ejercicios Prácticos

🏋️ Ejercicio 13: Pipeline Completo de Reshape

Usando los datos de empleados:

  1. melt() para convertir a formato largo
  2. Agregar nuevas variables derivadas usando :=
  3. dcast() para crear múltiples vistas agregadas
  4. Generar reporte ejecutivo combinando ambas técnicas
# 1. Pipeline completo de reshape para análisis de empleados
# Paso 1: melt() para formato largo
empleados_melted <- melt(empleados_metricas,
                        id.vars = c("empleado_id", "nombre", "departamento", "nivel", "salario_base"),
                        measure = patterns(
                          bonus = "^bonus_Q",
                          evaluacion = "^evaluacion_Q",
                          proyectos = "^proyectos_Q"
                        ),
                        variable.name = "trimestre_num",
                        value.name = c("bonus", "evaluacion", "proyectos"))

# 2. Agregar variables derivadas
empleados_enriquecido <- empleados_melted[, `:=`(
  trimestre = paste0("Q", trimestre_num),
  compensacion_total = salario_base / 4 + bonus,  # Salario trimestral + bonus
  productividad = round(proyectos / (evaluacion + 0.1), 2),  # Proyectos por punto de evaluación
  categoria_performance = fcase(
    evaluacion >= 4.5, "Excelente",
    evaluacion >= 4.0, "Muy Bueno", 
    evaluacion >= 3.5, "Bueno",
    evaluacion >= 3.0, "Satisfactorio",
    default = "Necesita Mejora"
  ),
  rango_bonus = fcase(
    bonus >= 10000, "Alto",
    bonus >= 5000, "Medio",
    bonus > 0, "Bajo", 
    default = "Sin Bonus"
  )
)]

# 3. Crear múltiples vistas con dcast()

# Vista 1: Performance promedio por departamento y nivel
performance_depto <- dcast(empleados_enriquecido,
                          departamento + nivel ~ .,
                          value.var = c("evaluacion", "productividad", "compensacion_total"),
                          fun.aggregate = list(mean = mean, max = max))

cat("📊 VISTA 1: PERFORMANCE POR DEPARTAMENTO Y NIVEL\n")
#> 📊 VISTA 1: PERFORMANCE POR DEPARTAMENTO Y NIVEL
print(performance_depto[order(departamento, nivel)])
#> Key: <departamento, nivel>
#>     departamento   nivel evaluacion_mean productividad_mean
#>           <char>  <char>           <num>              <num>
#>  1:           IT  Junior           4.200            0.71000
#>  2:           IT    Lead           3.750            2.04750
#>  3:           IT Manager           3.950            1.15250
#>  4:           IT  Senior           4.350            1.01125
#>  5:    Marketing  Junior           4.150            1.06250
#> ---                                                        
#> 12:         RRHH  Senior           3.275            1.93250
#> 13:       Ventas  Junior           4.200            1.41250
#> 14:       Ventas    Lead           4.000            1.61750
#> 15:       Ventas Manager           3.750            1.36250
#> 16:       Ventas  Senior           3.975            0.73000
#>     compensacion_total_mean evaluacion_max productividad_max
#>                       <num>          <num>             <num>
#>  1:                21597.50            4.9              1.40
#>  2:                22011.00            4.2              3.43
#>  3:                17790.25            4.7              1.89
#>  4:                19230.50            5.0              2.42
#>  5:                22414.75            4.8              2.20
#> ---                                                         
#> 12:                22870.25            3.4              3.53
#> 13:                21262.50            4.9              3.33
#> 14:                22306.00            4.7              2.65
#> 15:                21264.00            4.1              2.97
#> 16:                17050.00            4.6              1.47
#>     compensacion_total_max
#>                      <num>
#>  1:                  25144
#>  2:                  28061
#>  3:                  20776
#>  4:                  26428
#>  5:                  26109
#> ---                       
#> 12:                  26179
#> 13:                  26904
#> 14:                  23872
#> 15:                  24273
#> 16:                  21455

# Vista 2: Evolución trimestral por empleado (transpuesta)
evolucion_empleados <- dcast(empleados_enriquecido[departamento == "IT"],  # Solo IT para ejemplo
                            nombre ~ trimestre,
                            value.var = "evaluacion",
                            fun.aggregate = mean)

cat("\n📈 VISTA 2: EVOLUCIÓN DE EVALUACIONES - DEPARTAMENTO IT\n")
#> 
#> 📈 VISTA 2: EVOLUCIÓN DE EVALUACIONES - DEPARTAMENTO IT
print(evolucion_empleados)
#> Key: <nombre>
#>        nombre    Q1    Q2    Q3    Q4
#>        <char> <num> <num> <num> <num>
#> 1: Empleado_F   4.6   3.2   4.9   4.7
#> 2: Empleado_G   3.3   4.2   4.1   3.4
#> 3: Empleado_H   3.1   4.7   3.6   4.4
#> 4: Empleado_I   3.5   4.9   4.1   4.3
#> 5: Empleado_J   4.6   5.0   3.8   4.0

# Vista 3: Matriz de categorías (contingencia)
matriz_categorias <- dcast(empleados_enriquecido,
                          departamento ~ categoria_performance,
                          value.var = "empleado_id",
                          fun.aggregate = function(x) length(unique(x)))

cat("\n🎯 VISTA 3: MATRIZ DE CATEGORÍAS DE PERFORMANCE\n")
#> 
#> 🎯 VISTA 3: MATRIZ DE CATEGORÍAS DE PERFORMANCE
print(matriz_categorias)
#> Key: <departamento>
#>    departamento Bueno Excelente Muy Bueno Satisfactorio
#>          <char> <int>     <int>     <int>         <int>
#> 1:           IT     3         4         4             3
#> 2:    Marketing     3         5         5             4
#> 3:         RRHH     2         3         4             4
#> 4:       Ventas     4         4         3             3

# 4. Reporte ejecutivo combinando técnicas
cat("\n📋 REPORTE EJECUTIVO DE RRHH\n")
#> 
#> 📋 REPORTE EJECUTIVO DE RRHH

# KPIs generales
kpis_generales <- empleados_enriquecido[, .(
  empleados_unicos = uniqueN(empleado_id),
  evaluacion_promedio = round(mean(evaluacion), 2),
  bonus_promedio = round(mean(bonus), 0),
  proyectos_promedio = round(mean(proyectos), 1),
  compensacion_promedio = round(mean(compensacion_total), 0)
)]

cat("KPIs GENERALES:\n")
#> KPIs GENERALES:
cat("• Empleados analizados:", kpis_generales$empleados_unicos, "\n")
#> • Empleados analizados: 20
cat("• Evaluación promedio:", kpis_generales$evaluacion_promedio, "/5.0\n")
#> • Evaluación promedio: 4.02 /5.0
cat("• Bonus promedio trimestral:", scales::dollar(kpis_generales$bonus_promedio), "\n")
#> • Bonus promedio trimestral: $5,465
cat("• Proyectos promedio por trimestre:", kpis_generales$proyectos_promedio, "\n")
#> • Proyectos promedio por trimestre: 5.1
cat("• Compensación total promedio:", scales::dollar(kpis_generales$compensacion_promedio), "\n\n")
#> • Compensación total promedio: $20,502

# Top performers
top_performers <- empleados_enriquecido[, .(
  evaluacion_promedio = round(mean(evaluacion), 2),
  productividad_promedio = round(mean(productividad), 2),
  bonus_total = sum(bonus)
), by = .(empleado_id, nombre, departamento)][
  order(-evaluacion_promedio, -productividad_promedio)
][1:5]

cat("🏆 TOP 5 PERFORMERS:\n")
#> 🏆 TOP 5 PERFORMERS:
for(i in 1:nrow(top_performers)) {
  emp <- top_performers[i]
  cat(sprintf("%d. %s (%s) - Eval: %.1f, Productividad: %.1f, Bonus Total: %s\n",
              i, emp$nombre, emp$departamento, emp$evaluacion_promedio, 
              emp$productividad_promedio, scales::dollar(emp$bonus_total)))
}
#> 1. Empleado_P (RRHH) - Eval: 4.4, Productividad: 1.3, Bonus Total: $23,858
#> 2. Empleado_J (IT) - Eval: 4.3, Productividad: 1.2, Bonus Total: $23,002
#> 3. Empleado_F (IT) - Eval: 4.3, Productividad: 0.9, Bonus Total: $23,842
#> 4. Empleado_O (Marketing) - Eval: 4.2, Productividad: 1.0, Bonus Total: $27,901
#> 5. Empleado_E (Ventas) - Eval: 4.2, Productividad: 0.8, Bonus Total: $28,921

# Análisis por departamento
analisis_depto <- empleados_enriquecido[, .(
  empleados = uniqueN(empleado_id),
  evaluacion_promedio = round(mean(evaluacion), 2),
  empleados_excelentes = sum(categoria_performance == "Excelente"),
  tasa_excelencia = round(mean(categoria_performance == "Excelente") * 100, 1),
  presupuesto_bonus = sum(bonus)
), by = departamento][order(-tasa_excelencia)]

cat("\n🏢 ANÁLISIS POR DEPARTAMENTO:\n")
#> 
#> 🏢 ANÁLISIS POR DEPARTAMENTO:
print(analisis_depto)
#>    departamento empleados evaluacion_promedio empleados_excelentes
#>          <char>     <int>               <num>                <int>
#> 1:       Ventas         5                4.03                    7
#> 2:           IT         5                4.12                    7
#> 3:    Marketing         5                4.02                    7
#> 4:         RRHH         5                3.92                    3
#>    tasa_excelencia presupuesto_bonus
#>              <num>             <num>
#> 1:              35            102880
#> 2:              35            115739
#> 3:              35            115452
#> 4:              15            103162

# Recomendaciones automáticas
cat("\n💡 RECOMENDACIONES AUTOMÁTICAS:\n")
#> 
#> 💡 RECOMENDACIONES AUTOMÁTICAS:

# Departamento con mejor performance
mejor_depto <- analisis_depto[1, departamento]
peor_depto <- analisis_depto[.N, departamento]

cat("• Mejores prácticas de", mejor_depto, "podrían replicarse en otros departamentos\n")
#> • Mejores prácticas de Ventas podrían replicarse en otros departamentos
cat("• Departamento", peor_depto, "requiere plan de mejora en evaluaciones\n")
#> • Departamento RRHH requiere plan de mejora en evaluaciones

# Empleados que necesitan atención
empleados_atencion <- empleados_enriquecido[
  categoria_performance %in% c("Necesita Mejora", "Satisfactorio"), 
  uniqueN(empleado_id), 
  by = departamento
][V1 > 0]

if(nrow(empleados_atencion) > 0) {
  cat("• Revisar planes de desarrollo individual en:", paste(empleados_atencion$departamento, collapse = ", "), "\n")
}
#> • Revisar planes de desarrollo individual en: Ventas, IT, Marketing, RRHH

# # Crear tabla interactiva del reporte (comentado para PDF)
# DT::datatable(
#   performance_depto,
#   caption = "Dashboard Ejecutivo de Performance - RRHH",
#   options = list(pageLength = 10, scrollX = TRUE)
# ) %>%
#   DT::formatRound(c("evaluacion_mean", "productividad_mean"), digits = 2) %>%
#   DT::formatCurrency("compensacion_total_mean", currency = "$")
🏋️ Ejercicio 14: Análisis de Series Temporales con Reshape
  1. Reshape datos de sensores para análisis temporal
  2. Crear ventanas móviles después del reshape
  3. Detectar anomalías por tipo de sensor
  4. Generar reporte de alertas en formato ejecutivo
# 1. Análisis temporal completo con reshape
# Preparar datos base con información temporal
sensores_temporal <- sensores_long[, `:=`(
  fecha = as.Date(timestamp),
  hora = hour(timestamp),
  dia_semana = wday(timestamp, label = TRUE)
)]

# 2. Crear ventanas móviles por tipo de sensor
sensores_con_ventanas <- sensores_temporal[order(ubicacion, tipo_sensor, numero_sensor, timestamp)][, `:=`(
  # Ventanas móviles de 24 horas (4 mediciones = 24 horas con datos cada 6h)
  media_24h = frollmean(medicion, 4, na.rm = TRUE),
  media_48h = frollmean(medicion, 8, na.rm = TRUE),
  desv_24h = frollapply(medicion, 4, sd, na.rm = TRUE),
  
  # Cambios temporales
  cambio_6h = abs(medicion - shift(medicion, 1)),
  cambio_24h = abs(medicion - shift(medicion, 4)),
  
  # Tendencia
  tendencia_24h = frollapply(medicion, 4, function(x) {
    if(length(x) < 4) return(0)
    lm(x ~ seq_along(x))$coefficients[2]
  })
), by = .(ubicacion, tipo_sensor, numero_sensor)]

# 3. Detección de anomalías por tipo de sensor
sensores_con_anomalias <- sensores_con_ventanas[, `:=`(
  # Rangos normales específicos por tipo
  limite_inferior = fcase(
    tipo_sensor == "sensor_temp", 15,
    tipo_sensor == "sensor_humedad", 30,
    tipo_sensor == "sensor_presion", 1000,
    default = -Inf
  ),
  limite_superior = fcase(
    tipo_sensor == "sensor_temp", 30,
    tipo_sensor == "sensor_humedad", 80, 
    tipo_sensor == "sensor_presion", 1030,
    default = Inf
  )
)][, `:=`(
  # Detectar anomalías
  anomalia_rango = medicion < limite_inferior | medicion > limite_superior,
  anomalia_cambio_subito = !is.na(cambio_6h) & cambio_6h > fcase(
    tipo_sensor == "sensor_temp", 5,
    tipo_sensor == "sensor_humedad", 15,
    tipo_sensor == "sensor_presion", 20,
    default = Inf
  ),
  anomalia_desviacion = !is.na(media_24h) & !is.na(desv_24h) & 
                       abs(medicion - media_24h) > 2 * desv_24h,
  anomalia_tendencia = !is.na(tendencia_24h) & abs(tendencia_24h) > fcase(
    tipo_sensor == "sensor_temp", 1,
    tipo_sensor == "sensor_humedad", 3,
    tipo_sensor == "sensor_presion", 5,
    default = Inf
  )
)][, `:=`(
  # Score compuesto de anomalía
  score_anomalia = (as.numeric(anomalia_rango) * 4) +
                  (as.numeric(anomalia_cambio_subito) * 3) +
                  (as.numeric(anomalia_desviacion) * 2) + 
                  (as.numeric(anomalia_tendencia) * 1),
  
  # Clasificación de severidad
  severidad_anomalia = fcase(
    (anomalia_rango) * 4 + (anomalia_cambio_subito) * 3 + (anomalia_desviacion) * 2 + (anomalia_tendencia) * 1 >= 6, "CRÍTICA",
    (anomalia_rango) * 4 + (anomalia_cambio_subito) * 3 + (anomalia_desviacion) * 2 + (anomalia_tendencia) * 1 >= 4, "ALTA",
    (anomalia_rango) * 4 + (anomalia_cambio_subito) * 3 + (anomalia_desviacion) * 2 + (anomalia_tendencia) * 1 >= 2, "MEDIA",
    (anomalia_rango) * 4 + (anomalia_cambio_subito) * 3 + (anomalia_desviacion) * 2 + (anomalia_tendencia) * 1 >= 1, "BAJA",
    default = "NORMAL"
  )
)]

# 4. Generar reporte ejecutivo de alertas
cat("🚨 REPORTE DE ALERTAS - SISTEMA DE SENSORES 🚨\n")
#> 🚨 REPORTE DE ALERTAS - SISTEMA DE SENSORES 🚨
cat(rep("=", 60), "\n\n")
#> = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

# Resumen general usando reshape
resumen_alertas <- sensores_con_anomalias[severidad_anomalia != "NORMAL", .N, 
                                         by = .(ubicacion, tipo_sensor, severidad_anomalia)]

if(nrow(resumen_alertas) > 0) {
  # Crear matriz de alertas con dcast
  matriz_alertas <- dcast(resumen_alertas,
                         ubicacion + tipo_sensor ~ severidad_anomalia,
                         value.var = "N",
                         fill = 0)
  
  cat("📊 MATRIZ DE ALERTAS ACTIVAS:\n")
  print(matriz_alertas)
} else {
  cat("✅ No hay alertas activas en el sistema\n")
}
#> 📊 MATRIZ DE ALERTAS ACTIVAS:
#> Key: <ubicacion, tipo_sensor>
#>        ubicacion    tipo_sensor  ALTA  BAJA CRÍTICA
#>           <char>         <char> <int> <int>   <int>
#> 1: Planta_Centro sensor_humedad     0     3       0
#> 2: Planta_Centro sensor_presion    16     3       0
#> 3: Planta_Centro    sensor_temp     2    15       0
#> 4:  Planta_Norte sensor_humedad     0     3       0
#> 5:  Planta_Norte sensor_presion    12     1       1
#> 6:  Planta_Norte    sensor_temp     2    10       0
#> 7:    Planta_Sur sensor_humedad     0     3       0
#> 8:    Planta_Sur sensor_presion    16     0       0
#> 9:    Planta_Sur    sensor_temp     2     7       0

# Estado actual por sensor (usando reshape)
estado_actual <- sensores_con_anomalias[, .SD[.N], 
                                       by = .(ubicacion, tipo_sensor, numero_sensor)][, .(
  ubicacion, tipo_sensor, numero_sensor, timestamp, medicion, 
  severidad_anomalia, score_anomalia
)]

# Convertir a formato wide para dashboard
dashboard_estado <- dcast(estado_actual,
                         ubicacion ~ tipo_sensor + numero_sensor,
                         value.var = "medicion")

cat("\n🏢 ESTADO ACTUAL POR UBICACIÓN:\n")
#> 
#> 🏢 ESTADO ACTUAL POR UBICACIÓN:
print(dashboard_estado)
#> Key: <ubicacion>
#>        ubicacion sensor_humedad_1 sensor_humedad_2 sensor_presion_1
#>           <char>            <num>            <num>            <num>
#> 1: Planta_Centro             60.5             74.1           1006.4
#> 2:  Planta_Norte             42.9             58.0           1012.8
#> 3:    Planta_Sur             71.0             60.6           1015.7
#>    sensor_presion_2 sensor_temp_1 sensor_temp_2
#>               <num>         <num>         <num>
#> 1:            998.2          19.9          25.7
#> 2:            999.9          18.4          25.8
#> 3:           1029.3          19.4          26.0

# Top alertas críticas
alertas_criticas <- sensores_con_anomalias[
  severidad_anomalia %in% c("CRÍTICA", "ALTA")
][order(-score_anomalia, -timestamp)][1:min(10, .N)]

if(nrow(alertas_criticas) > 0) {
  cat("\n🔥 TOP ALERTAS CRÍTICAS:\n")
  print(alertas_criticas[, .(
    Ubicación = ubicacion,
    Sensor = paste(tipo_sensor, numero_sensor),
    Timestamp = timestamp,
    Medición = medicion,
    Severidad = severidad_anomalia,
    Score = score_anomalia
  )])
}
#> 
#> 🔥 TOP ALERTAS CRÍTICAS:
#>         Ubicación           Sensor           Timestamp Medición Severidad Score
#>            <char>           <char>              <POSc>    <num>    <char> <num>
#>  1:  Planta_Norte sensor_presion 1 2024-01-04 00:00:00   1044.3   CRÍTICA     7
#>  2: Planta_Centro    sensor_temp 1 2024-01-06 00:00:00     14.5      ALTA     5
#>  3:  Planta_Norte    sensor_temp 1 2024-01-06 00:00:00     13.5      ALTA     5
#>  4:  Planta_Norte sensor_presion 1 2024-01-03 00:00:00   1035.1      ALTA     5
#>  5:  Planta_Norte sensor_presion 1 2024-01-02 18:00:00   1038.1      ALTA     5
#>  6: Planta_Centro sensor_presion 2 2024-01-07 18:00:00    998.2      ALTA     4
#>  7:  Planta_Norte sensor_presion 2 2024-01-07 18:00:00    999.9      ALTA     4
#>  8:    Planta_Sur sensor_presion 2 2024-01-07 12:00:00   1031.5      ALTA     4
#>  9: Planta_Centro sensor_presion 2 2024-01-07 00:00:00    997.3      ALTA     4
#> 10:    Planta_Sur sensor_presion 2 2024-01-07 00:00:00   1033.3      ALTA     4

# Análisis de tendencias por tipo usando melt/dcast
tendencias_tipo <- sensores_con_anomalias[!is.na(tendencia_24h), .(
  tendencia_promedio = round(mean(tendencia_24h, na.rm = TRUE), 4),
  medicion_promedio = round(mean(medicion, na.rm = TRUE), 2),
  anomalias_total = sum(severidad_anomalia != "NORMAL")
), by = .(ubicacion, tipo_sensor)]

# Formato wide para comparación
tendencias_wide <- dcast(tendencias_tipo,
                        ubicacion ~ tipo_sensor,
                        value.var = "tendencia_promedio")

cat("\n📈 TENDENCIAS PROMEDIO POR UBICACIÓN (24h):\n")
#> 
#> 📈 TENDENCIAS PROMEDIO POR UBICACIÓN (24h):
print(tendencias_wide)
#> Key: <ubicacion>
#>        ubicacion sensor_humedad sensor_presion sensor_temp
#>           <char>          <num>          <num>       <num>
#> 1: Planta_Centro        -0.0194        -0.7440     -0.0484
#> 2:  Planta_Norte        -0.7072        -0.4502     -0.0448
#> 3:    Planta_Sur         0.5846         0.5512     -0.0688

# Recomendaciones automáticas
cat("\n💡 RECOMENDACIONES AUTOMÁTICAS:\n")
#> 
#> 💡 RECOMENDACIONES AUTOMÁTICAS:

# Sensores con más anomalías
sensores_problematicos <- sensores_con_anomalias[, .(
  anomalias = sum(severidad_anomalia != "NORMAL")
), by = .(ubicacion, tipo_sensor, numero_sensor)][anomalias > 0][order(-anomalias)]

if(nrow(sensores_problematicos) > 0) {
  top_problematico <- sensores_problematicos[1]
  cat("• Revisar sensor", paste(top_problematico$tipo_sensor, top_problematico$numero_sensor), 
      "en", top_problematico$ubicacion, "con", top_problematico$anomalias, "anomalías\n")
}
#> • Revisar sensor sensor_presion 1 en Planta_Centro con 15 anomalías

# Ubicación con más problemas
ubicacion_problemas <- sensores_con_anomalias[, .(
  anomalias_total = sum(severidad_anomalia != "NORMAL")
), by = ubicacion][order(-anomalias_total)]

if(nrow(ubicacion_problemas) > 0 && ubicacion_problemas[1, anomalias_total] > 0) {
  cat("• Priorizar mantenimiento en", ubicacion_problemas[1, ubicacion], 
      "con", ubicacion_problemas[1, anomalias_total], "anomalías totales\n")
}
#> • Priorizar mantenimiento en Planta_Centro con 39 anomalías totales

cat("• Siguiente revisión recomendada: en 6 horas\n")
#> • Siguiente revisión recomendada: en 6 horas

# # Tabla interactiva de alertas críticas (comentado para PDF)
# if(nrow(alertas_criticas) > 0) {
#   DT::datatable(
#     alertas_criticas[, .(ubicacion, tipo_sensor, numero_sensor, timestamp, 
#                         medicion, severidad_anomalia, score_anomalia)],
#     caption = "Alertas Críticas del Sistema de Sensores",
#     options = list(pageLength = 10, scrollX = TRUE)
#   ) %>%
#     DT::formatStyle(
#       "severidad_anomalia",
#       backgroundColor = DT::styleEqual(
#         c("CRÍTICA", "ALTA", "MEDIA", "BAJA"),
#         c("red", "orange", "yellow", "lightblue")
#       )
#     ) %>%
#     DT::formatRound("medicion", digits = 2)
# }

8.6 Mejores Prácticas para Reshape

8.6.1 1. Cuándo Usar Cada Técnica

# ✅ Usar melt() cuando:
# - Necesitas análisis estadístico o visualización con ggplot2
# - Quieres aplicar funciones por grupos de variables
# - Los datos vienen de Excel/reportes en formato ancho
datos_para_analisis <- melt(datos_wide, id.vars = "identificador")

# ✅ Usar dcast() cuando:  
# - Necesitas crear reportes ejecutivos o dashboards
# - Quieres matrices de correlación o contingencia
# - Necesitas format de "tabla dinámica" para presentación
reporte_ejecutivo <- dcast(datos_long, fila ~ columna, value.var = "valor")

# ✅ Combinar ambos para:
# - Pipelines de transformación complejos
# - Análisis que requieren múltiples vistas de los mismos datos
pipeline_completo <- datos %>% melt(...) %>% 
  enriquecer(...) %>% dcast(...)

8.6.2 2. Performance y Memoria

# ✅ HACER: Especificar measure.vars explícitamente
melt(dt, measure.vars = c("col1", "col2", "col3"))  # Más rápido

# ❌ EVITAR: Melt sin especificar columnas
melt(dt)  # Puede incluir columnas no deseadas

# ✅ HACER: Usar patterns() para múltiples tipos de variables  
melt(dt, measure = patterns("^bonus_", "^eval_"))

# ✅ HACER: Limpiar datos después de reshape
datos_melted[, columna_temp := NULL]  # Eliminar columnas temporales

8.6.3 3. Manejo de Valores Faltantes

# ✅ Control de NAs en dcast
dcast(dt, row ~ col, value.var = "val", fill = 0)  # Llenar con 0
dcast(dt, row ~ col, value.var = "val", drop = FALSE)  # Mantener combinaciones vacías

# ✅ Manejo de NAs después de melt
datos_melted[!is.na(value)]  # Filtrar NAs
datos_melted[, value := nafill(value, fill = 0)]  # Llenar NAs

🎯 Puntos Clave de Este Capítulo
  1. melt() convierte datos anchos a largos - esencial para análisis estadístico y visualización
  2. dcast() convierte datos largos a anchos - perfecto para reportes y dashboards ejecutivos
  3. Patrones complejos con patterns() permiten reshape de múltiples tipos de variables simultáneamente
  4. Funciones de agregación en dcast() crean resúmenes poderosos durante el reshape
  5. Combinar ambas técnicas permite pipelines de transformación muy sofisticados
  6. Performance: Especificar columnas explícitamente mejora velocidad y memoria