# Estructura modular para apps grandes
library(shiny)
library(shinydashboard)
library(data.table)
library(DT)
library(ggplot2)
library(plotly)
# === MÓDULO: Data Processing ===
# Todas las operaciones data.table en funciones separadas
process_customer_data <- function(clientes_dt, filters = list()) {
# Aplicar filtros dinámicamente
result <- clientes_dt
if(!is.null(filters$region) && filters$region != "Todos") {
result <- result[region == filters$region]
}
if(!is.null(filters$edad_min)) {
result <- result[edad >= filters$edad_min]
}
if(!is.null(filters$fecha_desde)) {
# Aquí se aplicarían filtros de fecha si tuviéramos esos datos
}
return(result)
}
calculate_customer_metrics <- function(clientes_dt) {
clientes_dt[,
.(
total_clientes = .N,
edad_promedio = round(mean(edad), 1),
ingresos_promedio = round(mean(ingresos), 0),
satisfaccion_promedio = round(mean(satisfaccion), 2),
churn_rate = round(mean(churn_flag) * 100, 1),
valor_promedio = round(mean(valor_cliente), 2),
engagement_promedio = round(mean(engagement_score), 2)
),
by = .(region, categoria_ingresos)
]
}
# === UI: Dashboard Layout ===
ui <- dashboardPage(
dashboardHeader(title = "Customer Analytics - Powered by data.table"),
dashboardSidebar(
sidebarMenu(
menuItem("Overview", tabName = "overview", icon = icon("dashboard")),
menuItem("Clientes", tabName = "clientes", icon = icon("users")),
menuItem("Segmentación", tabName = "segmentacion", icon = icon("chart-pie")),
menuItem("Predicciones", tabName = "predicciones", icon = icon("brain")),
menuItem("Datos Raw", tabName = "datos", icon = icon("table"))
)
),
dashboardBody(
tags$head(
tags$style(HTML("
.content-wrapper, .right-side {
background-color: #f4f4f4;
}
.main-header .navbar {
background-color: #2E8B57 !important;
}
"))
),
tabItems(
# Tab Overview
tabItem(tabName = "overview",
fluidRow(
valueBoxOutput("total_clientes"),
valueBoxOutput("churn_rate"),
valueBoxOutput("valor_promedio")
),
fluidRow(
box(
title = "Filtros Globales", status = "primary", solidHeader = TRUE,
width = 3,
selectInput("region_filter", "Región:",
choices = c("Todos", unique(datos_clientes$region)),
selected = "Todos"),
sliderInput("edad_range", "Rango de Edad:",
min = 18, max = 80, value = c(18, 80)),
actionButton("aplicar_filtros", "Aplicar Filtros",
class = "btn-primary")
),
box(
title = "Revenue por Región", status = "success", solidHeader = TRUE,
width = 9,
plotlyOutput("revenue_plot", height = "400px")
)
)
),
# Tab Clientes
tabItem(tabName = "clientes",
fluidRow(
box(
title = "Análisis de Clientes", status = "primary", solidHeader = TRUE,
width = 12,
DT::dataTableOutput("clientes_table")
)
)
),
# Tab Segmentación
tabItem(tabName = "segmentacion",
fluidRow(
box(
title = "Segmentación por Valor", status = "warning", solidHeader = TRUE,
width = 6,
plotOutput("segmentacion_plot")
),
box(
title = "Matriz de Retención", status = "info", solidHeader = TRUE,
width = 6,
plotOutput("matriz_retencion")
)
)
)
)
)
)
# === SERVER: Lógica Reactiva ===
server <- function(input, output, session) {
# Datos reactivos - aquí brilla data.table
datos_filtrados <- reactive({
input$aplicar_filtros # Dependencia del botón
isolate({
filtros <- list(
region = input$region_filter,
edad_min = input$edad_range[1],
edad_max = input$edad_range[2]
)
result <- datos_clientes[
edad >= filtros$edad_min & edad <= filtros$edad_max
]
if(filtros$region != "Todos") {
result <- result[region == filtros$region]
}
return(result)
})
})
# ValueBoxes
output$total_clientes <- renderValueBox({
valueBox(
value = comma(nrow(datos_filtrados())),
subtitle = "Total Clientes",
icon = icon("users"),
color = "blue"
)
})
output$churn_rate <- renderValueBox({
rate <- round(mean(datos_filtrados()$churn_flag) * 100, 1)
valueBox(
value = paste0(rate, "%"),
subtitle = "Tasa de Churn",
icon = icon("exclamation-triangle"),
color = if(rate > 15) "red" else if(rate > 10) "yellow" else "green"
)
})
output$valor_promedio <- renderValueBox({
valor <- round(mean(datos_filtrados()$valor_cliente), 2)
valueBox(
value = dollar(valor),
subtitle = "Valor Promedio",
icon = icon("dollar-sign"),
color = "green"
)
})
# Gráfico principal
output$revenue_plot <- renderPlotly({
# Cálculo super rápido con data.table
plot_data <- datos_filtrados()[,
.(
valor_total = sum(valor_cliente),
clientes = .N,
satisfaccion_avg = round(mean(satisfaccion), 2)
),
by = region
]
p <- ggplot(plot_data, aes(x = reorder(region, valor_total), y = valor_total)) +
geom_col(aes(fill = satisfaccion_avg), alpha = 0.8) +
geom_text(aes(label = dollar(valor_total, scale = 1e-3, suffix = "K")),
hjust = -0.1) +
scale_fill_viridis_c(name = "Satisfacción\nPromedio") +
coord_flip() +
labs(title = "Valor Total por Región", x = "Región", y = "Valor Total") +
theme_minimal()
ggplotly(p)
})
# Tabla de clientes
output$clientes_table <- DT::renderDataTable({
tabla_data <- datos_filtrados()[,
.(
cliente_id, edad, region,
ingresos = dollar(ingresos),
valor_cliente = round(valor_cliente, 2),
satisfaccion,
engagement_score = round(engagement_score, 2),
riesgo_churn = ifelse(riesgo_churn == 1, "Alto", "Bajo"),
es_vip = ifelse(cliente_vip == 1, "Sí", "No")
)
]
DT::datatable(
tabla_data,
options = list(pageLength = 25, scrollX = TRUE),
rownames = FALSE
) %>%
DT::formatStyle(
"riesgo_churn",
backgroundColor = DT::styleEqual("Alto", "#ffebee")
) %>%
DT::formatStyle(
"es_vip",
backgroundColor = DT::styleEqual("Sí", "#e8f5e8")
)
})
}
# Lanzar la app
# shinyApp(ui = ui, server = server)12 Aplicaciones del Mundo Real
12.1 Aplicaciones Shiny Escalables con data.table
12.1.1 1. Arquitectura de App Shiny Profesional
12.1.2 2. Optimización de Performance en Shiny
# === TÉCNICAS DE OPTIMIZACIÓN ===
# 1. Pre-procesar datos pesados al inicio
onSessionStart <- function(session) {
# Cálculos que no cambian frecuentemente
session$userData$metricas_estaticas <- datos_clientes[,
.(
clientes_por_region = .N,
valor_total = sum(valor_cliente),
edad_promedio = mean(edad)
),
by = region
]
# Índices para búsquedas rápidas
setkey(session$userData$clientes_dt, cliente_id)
setindex(session$userData$clientes_dt, region, categoria_ingresos)
}
# 2. Usar reactive values para cache inteligente
server <- function(input, output, session) {
# Cache de cálculos costosos
cache_metricas <- reactiveValues()
# Solo recalcular cuando cambien inputs relevantes
observe({
key <- paste(input$region_filter, input$edad_range[1], input$edad_range[2], sep = "_")
if(is.null(cache_metricas[[key]])) {
cache_metricas[[key]] <- calculate_customer_metrics(
datos_clientes[
region %in% input$region_filter &
edad %between% input$edad_range
]
)
}
})
# 3. Renderizado condicional
output$tabla_grande <- DT::renderDataTable({
# Solo renderizar si la pestaña está visible
req(input$tabs == "datos_detallados")
# data.table operation ultrarrápida
datos_tabla <- datos_filtrados()[,
.(cliente_id, region, valor_cliente, churn_flag)
][order(-valor_cliente)]
DT::datatable(datos_tabla, options = list(pageLength = 50))
})
}
# 4. Módulos para escalabilidad
customerMetricsUI <- function(id) {
ns <- NS(id)
tagList(
valueBoxOutput(ns("total_value")),
plotOutput(ns("distribution_plot"))
)
}
customerMetricsServer <- function(id, data) {
moduleServer(id, function(input, output, session) {
output$total_value <- renderValueBox({
# Cálculo modular con data.table
total <- data()[, sum(valor_cliente)]
valueBox(
value = dollar(total, scale = 1e-6, suffix = "M"),
subtitle = "Valor Total",
icon = icon("chart-line"),
color = "green"
)
})
})
}12.2 Integración con tidymodels: Machine Learning Robusto
12.2.1 1. Workflow Completo: data.table → tidymodels → data.table
# PASO 1: Feature Engineering con data.table (ultrarrápido)
preparar_datos_ml <- function(clientes_dt, transacciones_dt) {
# Calcular métricas de transacciones por cliente
metricas_transaccionales <- transacciones_dt[, monto_final := monto * (1 - descuento_aplicado)][
fecha_transaccion >= Sys.Date() - 365, # Último año
.(
transacciones_año = .N,
monto_total_año = sum(monto_final),
monto_promedio = round(mean(monto_final), 2),
categorias_compradas = uniqueN(producto_categoria),
canal_principal = names(sort(table(canal), decreasing = TRUE))[1],
frecuencia_compra_dias = as.numeric(max(fecha_transaccion) - min(fecha_transaccion)) / .N,
usa_descuentos = mean(descuento_aplicado > 0),
tasa_devolucion = mean(es_devolucion)
),
by = cliente_id
]
# Unir con datos de clientes
datos_completos <- clientes_dt[metricas_transaccionales, on = .(cliente_id)]
# Feature engineering adicional
datos_completos[, `:=`(
# Variables de interacción
ingresos_edad_ratio = ingresos / edad,
valor_por_producto = valor_cliente / num_productos,
engagement_per_month = engagement_score / pmax(antiguedad_meses, 1),
# Variables categóricas optimizadas
segmento_valor = cut(valor_cliente,
breaks = quantile(valor_cliente, c(0, 0.25, 0.5, 0.75, 1), na.rm = TRUE),
labels = c("Bajo", "Medio-Bajo", "Medio-Alto", "Alto"),
include.lowest = TRUE),
# Variables binarias
cliente_frecuente = as.numeric(transacciones_año > quantile(transacciones_año, 0.75, na.rm = TRUE)),
multicanal = as.numeric(categorias_compradas >= 3),
# Target variable limpia
churn = factor(ifelse(churn_flag == 1, "Si", "No"), levels = c("No", "Si"))
)]
# Remover casos con NAs en variables críticas
datos_limpios <- datos_completos[
!is.na(transacciones_año) &
!is.na(monto_total_año) &
!is.na(churn)
]
return(datos_limpios)
}
# Ejecutar preparación
datos_ml <- preparar_datos_ml(datos_clientes, transacciones_detalle)
cat("Datos preparados para ML:\n")
#> Datos preparados para ML:
cat("- Filas:", nrow(datos_ml), "\n")
#> - Filas: 5938
cat("- Columnas:", ncol(datos_ml), "\n")
#> - Columnas: 33
cat("- Distribución de churn:\n")
#> - Distribución de churn:
print(datos_ml[, .N, by = churn])
#> churn N
#> <fctr> <int>
#> 1: Si 707
#> 2: No 523112.2.2 2. Modelado con tidymodels (Ejemplo Conceptual)
# Ejemplo de workflow completo con tidymodels
library(tidymodels)
library(themis) # Para balanceo de clases
# PASO 2: Convertir a tibble para tidymodels
datos_tibble <- as_tibble(datos_ml)
# PASO 3: Split de datos
set.seed(123)
data_split <- initial_split(
datos_tibble,
prop = 0.8,
strata = churn
)
train_data <- training(data_split)
test_data <- testing(data_split)
# PASO 4: Receta de preprocesamiento
receta_churn <- recipe(churn ~ ., data = train_data) %>%
# Remover variables no predictivas
step_rm(cliente_id, churn_flag) %>%
# Imputación de NAs
step_impute_median(all_numeric_predictors()) %>%
step_impute_mode(all_nominal_predictors()) %>%
# Feature engineering
step_log(ingresos, valor_cliente, offset = 1) %>%
step_normalize(all_numeric_predictors()) %>%
# Variables dummy
step_dummy(all_nominal_predictors()) %>%
# Balanceo de clases
step_downsample(churn, under_ratio = 2) %>%
# Remover variables con varianza cero
step_zv(all_predictors()) %>%
# Correlación alta
step_corr(all_numeric_predictors(), threshold = 0.9)
# PASO 5: Modelos
modelo_rf <- rand_forest(
trees = tune(),
mtry = tune(),
min_n = tune()
) %>%
set_mode("classification") %>%
set_engine("randomForest")
modelo_xgb <- boost_tree(
trees = tune(),
tree_depth = tune(),
learn_rate = tune(),
min_n = tune()
) %>%
set_mode("classification") %>%
set_engine("xgboost")
# PASO 6: Workflows
workflow_rf <- workflow() %>%
add_recipe(receta_churn) %>%
add_model(modelo_rf)
workflow_xgb <- workflow() %>%
add_recipe(receta_churn) %>%
add_model(modelo_xgb)
# PASO 7: Cross-validation y tuning
cv_folds <- vfold_cv(train_data, v = 5, strata = churn)
# Tuning Random Forest
tune_rf <- workflow_rf %>%
tune_grid(
resamples = cv_folds,
grid = 20,
metrics = metric_set(roc_auc, precision, recall, f_meas)
)
# PASO 8: Mejor modelo
best_rf <- select_best(tune_rf, metric = "roc_auc")
final_workflow_rf <- finalize_workflow(workflow_rf, best_rf)
# PASO 9: Fit final y predicciones
modelo_final <- fit(final_workflow_rf, train_data)
predicciones <- predict(modelo_final, test_data, type = "prob")
# Métricas de evaluación
metricas_modelo <- test_data %>%
cbind(predicciones) %>%
mutate(pred = factor(fifelse(.pred_Si >= 0.5, 1, 0), levels = c(0, 1), labels = c("No", "Si"))) %>%
metrics(truth = churn, pred)
print(metricas_modelo)12.2.3 3. Post-procesamiento y Análisis con data.table
# Simular predicciones para el ejemplo (en producción vendrían del modelo)
set.seed(789)
predicciones_simuladas <- data.table(
cliente_id = datos_ml[sample(.N, 2000), cliente_id],
prob_churn = runif(2000, 0, 1),
pred_churn = sample(c("Si", "No"), 2000, replace = TRUE, prob = c(0.15, 0.85)),
confidence_score = runif(2000, 0.6, 0.95)
)
# ANÁLISIS DE RESULTADOS con data.table
# Unir predicciones con datos originales
resultados_ml <- datos_ml[predicciones_simuladas, on = .(cliente_id)]
# Análisis de segmentos de riesgo
analisis_riesgo <- resultados_ml[,
.(
clientes_total = .N,
churn_predicho = sum(pred_churn == "Si"),
prob_churn_media = round(mean(prob_churn), 3),
valor_en_riesgo = sum(valor_cliente[pred_churn == "Si"]),
revenue_en_riesgo = sum(monto_total_año[pred_churn == "Si"], na.rm = TRUE),
ingresos_promedio = round(mean(ingresos), 0),
engagement_promedio = round(mean(engagement_score), 2)
),
by = .(region, categoria_ingresos)
][order(-valor_en_riesgo)]
cat("=== ANÁLISIS DE RIESGO DE CHURN POR SEGMENTO ===\n")
#> === ANÁLISIS DE RIESGO DE CHURN POR SEGMENTO ===
print(analisis_riesgo)
#> region categoria_ingresos clientes_total churn_predicho prob_churn_media
#> <char> <fctr> <int> <int> <num>
#> 1: Centro Alto 115 27 0.496
#> 2: Norte Alto 149 22 0.474
#> 3: Este Alto 149 26 0.476
#> 4: Sur Alto 136 15 0.472
#> 5: Centro Medio 132 23 0.480
#> ---
#> 11: Oeste Medio 123 20 0.477
#> 12: Centro Bajo 141 19 0.477
#> 13: Sur Bajo 118 25 0.501
#> 14: Este Bajo 142 17 0.531
#> 15: Oeste Bajo 133 22 0.523
#> valor_en_riesgo revenue_en_riesgo ingresos_promedio engagement_promedio
#> <num> <num> <num> <num>
#> 1: 3971.9904 4357.158 101231 7.74
#> 2: 3448.6127 3115.558 101081 7.56
#> 3: 2752.8342 2759.645 103060 7.62
#> 4: 1918.1725 1584.725 93285 7.55
#> 5: 1900.4059 2514.022 48193 7.54
#> ---
#> 11: 1159.6149 2632.164 49278 7.63
#> 12: 912.9440 3273.967 27460 7.72
#> 13: 879.7883 3768.650 26754 7.62
#> 14: 812.1611 1624.176 27489 7.43
#> 15: 794.0182 2108.543 27076 7.72
# Identificar clientes de alta prioridad para retención
clientes_retencion <- resultados_ml[
pred_churn == "Si" &
prob_churn > 0.7 &
valor_cliente > quantile(resultados_ml$valor_cliente, 0.7, na.rm = TRUE),
.(
cliente_id, region, edad, ingresos, valor_cliente,
prob_churn = round(prob_churn, 3),
transacciones_año, monto_total_año,
satisfaccion, engagement_score,
accion_recomendada = fcase(
satisfaccion < 3, "Mejora_Servicio",
engagement_score < 5, "Aumentar_Engagement",
monto_total_año < 1000, "Incentivo_Compra",
default = "Programa_Lealtad"
)
)
][order(-valor_cliente)]
cat("\n=== TOP 10 CLIENTES PARA RETENCIÓN INMEDIATA ===\n")
#>
#> === TOP 10 CLIENTES PARA RETENCIÓN INMEDIATA ===
print(head(clientes_retencion, 10))
#> cliente_id region edad ingresos valor_cliente prob_churn transacciones_año
#> <int> <char> <num> <num> <num> <num> <int>
#> 1: 69 Centro 49 312277 761.9559 0.899 1
#> 2: 9176 Centro 22 70610 406.0075 0.888 2
#> 3: 6866 Sur 49 61726 274.0634 0.967 1
#> 4: 3461 Sur 23 138172 269.4354 0.998 2
#> 5: 8123 Oeste 49 140832 266.1725 0.705 1
#> 6: 3497 Norte 39 102743 240.4186 0.849 1
#> 7: 2847 Este 41 82017 216.5249 0.890 2
#> 8: 2896 Este 21 83534 197.9756 0.836 2
#> 9: 4247 Oeste 35 68037 175.5355 0.709 1
#> 10: 9463 Norte 20 46169 164.3616 0.843 2
#> monto_total_año satisfaccion engagement_score accion_recomendada
#> <num> <num> <num> <char>
#> 1: 11.56000 3.1 10.0 Incentivo_Compra
#> 2: 292.31936 3.5 10.0 Incentivo_Compra
#> 3: 55.99000 3.1 10.0 Incentivo_Compra
#> 4: 190.29448 4.3 9.3 Incentivo_Compra
#> 5: 42.78000 1.0 8.0 Mejora_Servicio
#> 6: 103.06000 2.4 6.8 Mejora_Servicio
#> 7: 169.53063 3.1 10.0 Incentivo_Compra
#> 8: 73.40943 4.7 10.0 Incentivo_Compra
#> 9: 49.41055 2.5 4.5 Mejora_Servicio
#> 10: 148.34431 2.3 10.0 Mejora_Servicio
# Análisis de efectividad del modelo por segmento
if(nrow(resultados_ml[!is.na(churn_flag)]) > 0) {
efectividad_modelo <- resultados_ml[!is.na(churn_flag),
.(
precision = sum(pred_churn == "Si" & churn == "Si") / sum(pred_churn == "Si"),
recall = sum(pred_churn == "Si" & churn == "Si") / sum(churn == "Si"),
accuracy = mean(pred_churn == churn),
clientes_evaluados = .N
),
by = .(region, categoria_edad)
][clientes_evaluados >= 10] # Solo segmentos con suficientes datos
cat("\n=== EFECTIVIDAD DEL MODELO POR SEGMENTO ===\n")
print(head(efectividad_modelo[order(-accuracy)]))
}
#>
#> === EFECTIVIDAD DEL MODELO POR SEGMENTO ===
#> region categoria_edad precision recall accuracy clientes_evaluados
#> <char> <fctr> <num> <num> <num> <int>
#> 1: Oeste Senior 0.20000000 0.25000000 0.8478261 46
#> 2: Este Adulto 0.22222222 0.33333333 0.8225806 124
#> 3: Este Joven 0.13333333 0.22222222 0.8148148 108
#> 4: Norte Adulto 0.19047619 0.30769231 0.8000000 130
#> 5: Oeste Adulto 0.08333333 0.07142857 0.7837838 111
#> 6: Norte Joven 0.07142857 0.09090909 0.7676768 9912.3 dtplyr: El Puente Entre data.table y tidyverse
12.3.1 1. Introducción y Casos de Uso
if(requireNamespace("dtplyr", quietly = TRUE) && requireNamespace("dplyr", quietly = TRUE)) {
library(dtplyr)
library(dplyr, warn.conflicts = FALSE)
# Convertir data.table a lazy_dt
clientes_lazy <- lazy_dt(datos_clientes)
# Workflow con sintaxis dplyr que se traduce a data.table
analisis_dtplyr <- clientes_lazy %>%
filter(edad >= 25, edad <= 65) %>%
mutate(
categoria_valor = case_when(
valor_cliente >= quantile(valor_cliente, 0.8) ~ "Premium",
valor_cliente >= quantile(valor_cliente, 0.5) ~ "Standard",
TRUE ~ "Basic"
),
riesgo_total = riesgo_churn + (5 - satisfaccion) / 5
) %>%
group_by(region, categoria_valor) %>%
summarise(
clientes = n(),
valor_promedio = round(mean(valor_cliente), 2),
engagement_promedio = round(mean(engagement_score), 2),
churn_rate = round(mean(churn_flag) * 100, 1),
riesgo_promedio = round(mean(riesgo_total), 2),
.groups = 'drop'
) %>%
arrange(desc(valor_promedio)) %>%
as.data.table() # Convertir de vuelta a data.table
print("Resultado del análisis con dtplyr:")
print(head(analisis_dtplyr, 10))
# Ver el código data.table generado
cat("\n=== CÓDIGO DATA.TABLE GENERADO POR DTPLYR ===\n")
codigo_generado <- clientes_lazy %>%
filter(edad >= 25, edad <= 65) %>%
group_by(region) %>%
summarise(valor_promedio = mean(valor_cliente), .groups = 'drop') %>%
show_query()
} else {
cat("💡 Para usar dtplyr, instala los paquetes:\n")
cat("install.packages(c('dtplyr', 'dplyr'))\n")
}
#> [1] "Resultado del análisis con dtplyr:"
#> region categoria_valor clientes valor_promedio engagement_promedio
#> <char> <char> <int> <num> <num>
#> 1: Centro Premium 329 278.55 9.09
#> 2: Norte Premium 361 272.26 9.14
#> 3: Este Premium 327 270.18 9.18
#> 4: Sur Premium 328 267.10 9.03
#> 5: Oeste Premium 293 266.28 8.93
#> 6: Este Standard 508 91.43 7.97
#> 7: Sur Standard 486 90.45 8.09
#> 8: Norte Standard 476 89.11 7.87
#> 9: Oeste Standard 527 88.67 7.88
#> 10: Centro Standard 460 87.94 8.00
#> churn_rate riesgo_promedio
#> <num> <num>
#> 1: 12.5 0.92
#> 2: 9.1 0.84
#> 3: 11.6 0.97
#> 4: 8.5 0.94
#> 5: 12.6 0.91
#> 6: 13.6 0.87
#> 7: 14.4 0.87
#> 8: 12.8 0.99
#> 9: 10.8 0.91
#> 10: 13.0 0.92
#>
#> === CÓDIGO DATA.TABLE GENERADO POR DTPLYR ===12.3.2 2. Comparación de Performance: dtplyr vs dplyr puro
if(requireNamespace("dtplyr", quietly = TRUE) && requireNamespace("dplyr", quietly = TRUE)) {
library(microbenchmark)
# Crear dataset más grande para benchmark
datos_benchmark <- rbindlist(replicate(5, datos_clientes, simplify = FALSE))
# Operación compleja para comparar
operacion_compleja <- function(data, metodo) {
if(metodo == "dplyr") {
# dplyr puro sobre data.frame
as.data.frame(data) %>%
filter(edad >= 30, satisfaccion >= 3) %>%
group_by(region, categoria_ingresos) %>%
summarise(
clientes = n(),
valor_total = sum(valor_cliente),
engagement_avg = mean(engagement_score),
.groups = 'drop'
) %>%
arrange(desc(valor_total))
} else if(metodo == "dtplyr") {
# dtplyr (sintaxis dplyr + motor data.table)
lazy_dt(data) %>%
filter(edad >= 30, satisfaccion >= 3) %>%
group_by(region, categoria_ingresos) %>%
summarise(
clientes = n(),
valor_total = sum(valor_cliente),
engagement_avg = mean(engagement_score),
.groups = 'drop'
) %>%
arrange(desc(valor_total)) %>%
as.data.table()
} else {
# data.table puro
data[
edad >= 30 & satisfaccion >= 3,
.(
clientes = .N,
valor_total = sum(valor_cliente),
engagement_avg = mean(engagement_score)
),
by = .(region, categoria_ingresos)
][order(-valor_total)]
}
}
# Benchmark
benchmark_results <- microbenchmark(
dplyr_puro = operacion_compleja(datos_benchmark, "dplyr"),
dtplyr = operacion_compleja(datos_benchmark, "dtplyr"),
data_table = operacion_compleja(datos_benchmark, "data.table"),
times = 10
)
cat("=== BENCHMARK DE PERFORMANCE ===\n")
print(benchmark_results)
# Calcular speedup
medias <- aggregate(time ~ expr, data = benchmark_results, FUN = mean)
medias$speedup <- medias$time[medias$expr == "dplyr_puro"] / medias$time
cat("\n=== SPEEDUP RELATIVO (vs dplyr puro) ===\n")
print(medias[, c("expr", "speedup")])
} else {
cat("Benchmark requiere dtplyr y dplyr instalados\n")
}
#> === BENCHMARK DE PERFORMANCE ===
#> Unit: milliseconds
#> expr min lq mean median uq max neval
#> dplyr_puro 7.651211 7.991026 11.723056 9.585700 10.979950 33.61189 10
#> dtplyr 7.059959 7.183349 8.336634 7.435068 9.899625 11.70520 10
#> data_table 2.452785 2.534377 2.845249 2.733674 3.121031 3.36696 10
#>
#> === SPEEDUP RELATIVO (vs dplyr puro) ===
#> expr speedup
#> 1 dplyr_puro 1.000000
#> 2 dtplyr 1.406210
#> 3 data_table 4.12022212.4 Conexión con Bases de Datos y Big Data
12.4.1 1. Lectura/Escritura Eficiente con fread/fwrite
# === FREAD: Lectura ultrarrápida ===
# Crear archivo de ejemplo grande
archivo_test <- tempfile(fileext = ".csv")
# Generar datos sintéticos para el ejemplo
datos_grandes <- data.table(
id = 1:100000,
timestamp = seq(as.POSIXct("2024-01-01"), by = "min", length.out = 100000),
sensor_value = round(rnorm(100000, 50, 15), 2),
location = sample(c("Factory_A", "Factory_B", "Factory_C"), 100000, replace = TRUE),
quality_score = round(runif(100000, 0.8, 1.0), 3),
batch_id = sample(1:1000, 100000, replace = TRUE)
)
# Escribir archivo
cat("Escribiendo archivo de prueba...\n")
#> Escribiendo archivo de prueba...
tiempo_write <- system.time({
fwrite(datos_grandes, archivo_test,
nThread = getDTthreads(),
showProgress = FALSE)
})
# Información del archivo
info_archivo <- file.info(archivo_test)
cat("Archivo creado:", round(info_archivo$size / 1024^2, 2), "MB\n")
#> Archivo creado: 4.06 MB
cat("Tiempo de escritura:", round(tiempo_write[3], 3), "segundos\n")
#> Tiempo de escritura: 0.018 segundos
# Lectura con diferentes configuraciones
cat("\n=== COMPARACIÓN DE MÉTODOS DE LECTURA ===\n")
#>
#> === COMPARACIÓN DE MÉTODOS DE LECTURA ===
# 1. fread básico
tiempo_fread_basico <- system.time({
datos_fread <- fread(archivo_test, showProgress = FALSE)
})
# 2. fread optimizado
tiempo_fread_opt <- system.time({
datos_fread_opt <- fread(
archivo_test,
nThread = getDTthreads(),
select = c("id", "timestamp", "sensor_value", "location"), # Solo columnas necesarias
colClasses = list(character = "location", numeric = c("sensor_value")),
showProgress = FALSE
)
})
# 3. read.csv para comparación
tiempo_base_r <- system.time({
datos_base <- read.csv(archivo_test, stringsAsFactors = FALSE)
})
cat("fread básico:", round(tiempo_fread_basico[3], 3), "segundos\n")
#> fread básico: 0.009 segundos
cat("fread optimizado:", round(tiempo_fread_opt[3], 3), "segundos\n")
#> fread optimizado: 0.008 segundos
cat("read.csv (base R):", round(tiempo_base_r[3], 3), "segundos\n")
#> read.csv (base R): 0.184 segundos
cat("Speedup fread vs base R:", round(tiempo_base_r[3] / tiempo_fread_basico[3], 1), "x\n")
#> Speedup fread vs base R: 20.4 x
# Verificar que los datos son idénticos
cat("Datos idénticos:", identical(datos_fread$id, datos_fread_opt$id), "\n")
#> Datos idénticos: TRUE
# Limpiar
unlink(archivo_test)12.4.2 2. Integración con Bases de Datos
# Ejemplo de integración con bases de datos
library(DBI)
library(RSQLite) # o RPostgreSQL, RMySQL, etc.
# === SETUP DE CONEXIÓN ===
# Crear base de datos SQLite para el ejemplo
con <- dbConnect(RSQLite::SQLite(), ":memory:")
# Escribir data.table a la base de datos
dbWriteTable(con, "clientes", datos_clientes)
dbWriteTable(con, "transacciones", transacciones_detalle)
# === WORKFLOW HÍBRIDO: SQL + data.table ===
# 1. Query inicial en SQL (aprovechar índices de DB)
query_sql <- "
SELECT c.cliente_id, c.region, c.edad, c.ingresos,
t.monto, t.fecha_transaccion, t.producto_categoria
FROM clientes c
JOIN transacciones t ON c.cliente_id = t.cliente_id
WHERE c.edad >= 25 AND c.edad <= 65
AND t.fecha_transaccion >= '2024-01-01'
"
# 2. Traer datos a data.table
datos_query <- as.data.table(dbGetQuery(con, query_sql))
# 3. Análisis complejo con data.table (más rápido que SQL)
analisis_hibrido <- datos_query[,
.(
transacciones_total = .N,
monto_total = sum(monto),
monto_promedio = round(mean(monto), 2),
categorias_distintas = uniqueN(producto_categoria),
primera_compra = min(fecha_transaccion),
ultima_compra = max(fecha_transaccion)
),
by = .(cliente_id, region)
][, `:=`(
dias_activo = as.numeric(as.Date(ultima_compra) - as.Date(primera_compra)) + 1,
frecuencia_compra = transacciones_total / pmax(as.numeric(as.Date(ultima_compra) - as.Date(primera_compra)) + 1, 1)
)][order(-monto_total)]
# 4. Escribir resultados de vuelta a DB (opcional)
dbWriteTable(con, "analisis_clientes", analisis_hibrido, overwrite = TRUE)
# 5. Verificación
resumen_db <- dbGetQuery(con, "SELECT COUNT(*) as clientes_analizados FROM analisis_clientes")
cat("Clientes analizados en DB:", resumen_db$clientes_analizados, "\n")
# Cerrar conexión
dbDisconnect(con)
# === MEJORES PRÁCTICAS ===
# 1. Usar SQL para filtros iniciales y joins simples
# 2. Traer datos a data.table para análisis complejos
# 3. Aprovechar índices de DB para WHERE y JOIN
# 4. Usar data.table para agregaciones complejas y feature engineering
# 5. Escribir resultados finales de vuelta a DB si es necesario12.4.3 3. Integración con Apache Arrow/Parquet
# Ejemplo de integración con ecosistema Arrow
library(arrow)
# === ESCRITURA A PARQUET ===
# Parquet es ultra-eficiente para datasets grandes
archivo_parquet <- tempfile(fileext = ".parquet")
# Escribir data.table a Parquet
write_parquet(datos_clientes, archivo_parquet)
# === LECTURA DESDE PARQUET ===
# Leer directo a data.table
datos_parquet <- read_parquet(archivo_parquet, as_data_frame = TRUE)
setDT(datos_parquet) # Asegurar que es data.table
# === DATASETS PARTICIONADOS ===
# Para datasets muy grandes, usar particiones
directorio_particionado <- file.path(tempdir(), "partitioned_data")
dir.create(directorio_particionado, showWarnings = FALSE, recursive = TRUE)
# Particionar por región - método más robusto
for(region_name in unique(datos_clientes$region)) {
region_data <- datos_clientes[region == region_name]
archivo_region <- file.path(directorio_particionado, paste0("region_", region_name, ".parquet"))
write_parquet(region_data, archivo_region)
}
# Verificar que los archivos se crearon correctamente
archivos_parquet <- list.files(directorio_particionado, pattern = "\\.parquet$", full.names = TRUE)
cat("Archivos Parquet creados:", length(archivos_parquet), "\n")
# Leer dataset particionado solo si hay archivos válidos
if(length(archivos_parquet) > 0) {
dataset <- open_dataset(directorio_particionado)
} else {
stop("No se pudieron crear archivos Parquet válidos")
}
# Query con pushdown de predicados (muy eficiente)
tryCatch({
resultado_arrow <- dataset %>%
filter(edad >= 30, satisfaccion >= 4) %>%
group_by(region) %>%
summarise(
clientes = n(),
valor_promedio = mean(valor_cliente),
ingresos_promedio = mean(ingresos)
) %>%
collect() %>% # Traer a memoria
as.data.table() # Convertir a data.table
print(resultado_arrow)
}, error = function(e) {
cat("Error en consulta Arrow:", conditionMessage(e), "\n")
cat("Usando método alternativo con data.table directo...\n")
# Fallback: usar data.table directamente
resultado_arrow <- datos_clientes[edad >= 30 & satisfaccion >= 4, .(
clientes = .N,
valor_promedio = mean(valor_cliente),
ingresos_promedio = mean(ingresos)
), by = region]
print(resultado_arrow)
})
# === VENTAJAS DEL WORKFLOW ARROW + DATA.TABLE ===
# 1. Parquet es extremadamente eficiente en espacio
# 2. Pushdown de predicados reduce transferencia de datos
# 3. Compatibilidad con otros lenguajes (Python, Spark)
# 4. data.table para análisis final en R12.5 Casos de Uso Industriales Reales
12.5.1 1. Sistema de Monitoreo IoT en Tiempo Real
# === ANÁLISIS DE SENSORES IOT ===
# Simular análisis en tiempo real de sensores
# Función para procesar batch de datos de sensores
procesar_batch_sensores <- function(datos_sensores, ventana_horas = 1) {
# Análisis de anomalías en tiempo real
datos_sensores[,
`:=`(
temp_anomaly = abs(temperatura - mean(temperatura)) > 2 * sd(temperatura),
humidity_anomaly = abs(humedad - mean(humedad)) > 2 * sd(humedad),
battery_critical = nivel_bateria < 15
),
by = .(sensor_id, fecha)
]
# Resumen por sensor y hora
resumen_sensores <- datos_sensores[,
.(
temp_promedio = round(mean(temperatura), 2),
temp_min = min(temperatura),
temp_max = max(temperatura),
humedad_promedio = round(mean(humedad), 1),
presion_promedio = round(mean(presion), 1),
movimientos_detectados = sum(movimiento_detectado),
anomalias_temp = sum(temp_anomaly),
anomalias_humedad = sum(humidity_anomaly),
alertas_bateria = sum(battery_critical),
lecturas_total = .N,
uptime_pct = round((1 - sum(is.na(temperatura)) / .N) * 100, 1)
),
by = .(sensor_id, fecha, hora)
][, `:=`(
estado_sensor = fcase(
alertas_bateria > 0, "CRÍTICO",
anomalias_temp + anomalias_humedad > 5, "ADVERTENCIA",
uptime_pct < 95, "DEGRADADO",
default = "NORMAL"
),
score_salud = pmin(100, uptime_pct - (anomalias_temp + anomalias_humedad) * 5 - alertas_bateria * 20)
)]
return(resumen_sensores)
}
# Procesar datos de sensores
analisis_sensores <- procesar_batch_sensores(sensores_iot)
# Dashboard de alertas críticas
alertas_criticas <- analisis_sensores[
estado_sensor %in% c("CRÍTICO", "ADVERTENCIA"),
.(
sensor_id, fecha, hora,
estado_sensor, score_salud,
anomalias_temp, anomalias_humedad, alertas_bateria,
accion_requerida = fcase(
alertas_bateria > 0, "Cambiar_Batería",
anomalias_temp > 3, "Revisar_Sensor_Temperatura",
anomalias_humedad > 3, "Revisar_Sensor_Humedad",
default = "Inspección_General"
)
)
][order(fecha, hora, -score_salud)]
cat("=== ALERTAS CRÍTICAS DEL SISTEMA IOT ===\n")
#> === ALERTAS CRÍTICAS DEL SISTEMA IOT ===
print(head(alertas_criticas, 15))
#> sensor_id fecha hora estado_sensor score_salud anomalias_temp
#> <char> <Date> <int> <char> <num> <int>
#> 1: SENSOR_091 2024-01-22 20 CRÍTICO 80 0
#> 2: SENSOR_078 2024-01-24 4 CRÍTICO 80 0
#> 3: SENSOR_030 2024-01-26 19 CRÍTICO 80 0
#> 4: SENSOR_032 2024-01-31 7 CRÍTICO 80 0
#> anomalias_humedad alertas_bateria accion_requerida
#> <int> <int> <char>
#> 1: 0 1 Cambiar_Batería
#> 2: 0 1 Cambiar_Batería
#> 3: 0 1 Cambiar_Batería
#> 4: 0 1 Cambiar_Batería
# Estadísticas de salud del sistema
salud_sistema <- analisis_sensores[,
.(
sensores_activos = uniqueN(sensor_id),
sensores_criticos = uniqueN(sensor_id[estado_sensor == "CRÍTICO"]),
sensores_degradados = uniqueN(sensor_id[estado_sensor %in% c("ADVERTENCIA", "DEGRADADO")]),
score_salud_promedio = round(mean(score_salud), 1),
anomalias_totales = sum(anomalias_temp + anomalias_humedad),
uptime_sistema = round(mean(uptime_pct), 1)
),
by = .(fecha)
]
cat("\n=== SALUD GENERAL DEL SISTEMA ===\n")
#>
#> === SALUD GENERAL DEL SISTEMA ===
print(head(salud_sistema))
#> fecha sensores_activos sensores_criticos sensores_degradados
#> <Date> <int> <int> <int>
#> 1: 2024-01-01 100 0 0
#> 2: 2024-01-02 100 0 0
#> 3: 2024-01-03 100 0 0
#> 4: 2024-01-04 100 0 0
#> 5: 2024-01-05 100 0 0
#> 6: 2024-01-06 100 0 0
#> score_salud_promedio anomalias_totales uptime_sistema
#> <num> <int> <num>
#> 1: 100 0 100
#> 2: 100 0 100
#> 3: 100 0 100
#> 4: 100 0 100
#> 5: 100 0 100
#> 6: 100 0 10012.5.2 2. Sistema de Recomendaciones E-commerce
# === MOTOR DE RECOMENDACIONES ===
# Sistema basado en comportamiento de compra
# Función para calcular similaridad entre clientes
calcular_recomendaciones <- function(transacciones_dt, cliente_target, top_n = 5) {
# Matriz de compras por cliente-categoría
matriz_compras <- transacciones_dt[,
.(
total_comprado = sum(monto_final),
frecuencia = .N
),
by = .(cliente_id, producto_categoria)
]
# Perfil del cliente target
perfil_target <- matriz_compras[cliente_id == cliente_target]
if(nrow(perfil_target) == 0) {
return(data.table(mensaje = "Cliente no encontrado"))
}
# Encontrar clientes similares
clientes_similares <- matriz_compras[
producto_categoria %in% perfil_target$producto_categoria &
cliente_id != cliente_target,
.(
overlap_categorias = .N,
valor_similar = sum(total_comprado)
),
by = cliente_id
][overlap_categorias >= 2][order(-overlap_categorias, -valor_similar)]
# Recomendaciones basadas en clientes similares
if(nrow(clientes_similares) > 0) {
recomendaciones <- matriz_compras[
cliente_id %in% head(clientes_similares$cliente_id, 20) &
!producto_categoria %in% perfil_target$producto_categoria,
.(
score_recomendacion = sum(total_comprado),
clientes_que_compran = .N,
frecuencia_promedio = round(mean(frecuencia), 1)
),
by = producto_categoria
][clientes_que_compran >= 3][order(-score_recomendacion)][1:top_n]
return(recomendaciones)
} else {
return(data.table(mensaje = "No hay suficientes datos para recomendaciones"))
}
}
# Función para análisis de mercado
analizar_tendencias_mercado <- function(transacciones_dt, periodo_dias = 30) {
fecha_corte <- max(transacciones_dt$fecha_transaccion) - periodo_dias
tendencias <- transacciones_dt[
fecha_transaccion >= fecha_corte,
.(
ventas_recientes = sum(monto_final),
transacciones_recientes = .N,
clientes_unicos = uniqueN(cliente_id),
ticket_promedio = round(mean(monto_final), 2),
crecimiento_semanal = .N / (periodo_dias / 7)
),
by = producto_categoria
][order(-ventas_recientes)]
# Calcular métricas adicionales
tendencias[, `:=`(
penetracion_mercado = round((clientes_unicos / uniqueN(transacciones_dt$cliente_id)) * 100, 1),
velocidad_venta = round(transacciones_recientes / periodo_dias, 2),
categoria_trend = fcase(
crecimiento_semanal > quantile(crecimiento_semanal, 0.75), "📈 CRECIENTE",
crecimiento_semanal < quantile(crecimiento_semanal, 0.25), "📉 DECLINANTE",
default = "➡️ ESTABLE"
)
)]
return(tendencias)
}
# Ejecutar análisis de recomendaciones
cliente_ejemplo <- datos_clientes[sample(.N, 1), cliente_id]
recomendaciones <- calcular_recomendaciones(transacciones_detalle, cliente_ejemplo)
cat("=== RECOMENDACIONES PARA CLIENTE", cliente_ejemplo, "===\n")
#> === RECOMENDACIONES PARA CLIENTE 5677 ===
print(recomendaciones)
#> producto_categoria score_recomendacion clientes_que_compran
#> <char> <num> <int>
#> 1: <NA> NA NA
#> 2: <NA> NA NA
#> 3: <NA> NA NA
#> 4: <NA> NA NA
#> 5: <NA> NA NA
#> frecuencia_promedio
#> <num>
#> 1: NA
#> 2: NA
#> 3: NA
#> 4: NA
#> 5: NA
# Análisis de tendencias de mercado
tendencias_mercado <- analizar_tendencias_mercado(transacciones_detalle, 60)
cat("\n=== TENDENCIAS DE MERCADO (últimos 60 días) ===\n")
#>
#> === TENDENCIAS DE MERCADO (últimos 60 días) ===
print(tendencias_mercado)
#> producto_categoria ventas_recientes transacciones_recientes clientes_unicos
#> <char> <num> <int> <int>
#> 1: Sports 59738.74 728 696
#> 2: Books 57747.86 673 658
#> 3: Home 57641.77 700 678
#> 4: Clothing 55649.79 723 699
#> 5: Beauty 55388.89 680 662
#> 6: Electronics 52302.97 689 663
#> ticket_promedio crecimiento_semanal penetracion_mercado velocidad_venta
#> <num> <num> <num> <num>
#> 1: 82.06 84.93333 7.0 12.13
#> 2: 85.81 78.51667 6.6 11.22
#> 3: 82.35 81.66667 6.8 11.67
#> 4: 76.97 84.35000 7.0 12.05
#> 5: 81.45 79.33333 6.7 11.33
#> 6: 75.91 80.38333 6.7 11.48
#> categoria_trend
#> <char>
#> 1: 📈 CRECIENTE
#> 2: 📉 DECLINANTE
#> 3: ➡️ ESTABLE
#> 4: 📈 CRECIENTE
#> 5: 📉 DECLINANTE
#> 6: ➡️ ESTABLE
# Análisis de cohorstes de clientes
analisis_cohortes <- transacciones_detalle[,
.(
primera_compra = min(fecha_transaccion),
ultima_compra = max(fecha_transaccion),
valor_total = sum(monto_final),
frecuencia_compra = .N
),
by = cliente_id
][, `:=`(
cohorte_mes = format(primera_compra, "%Y-%m"),
dias_como_cliente = as.numeric(ultima_compra - primera_compra) + 1
)][, `:=`(
valor_por_dia = round(valor_total / pmax(dias_como_cliente, 1), 2)
)][,
.(
clientes_cohorte = .N,
valor_promedio_cohorte = round(mean(valor_total), 2),
dias_retencion_promedio = round(mean(dias_como_cliente), 1),
valor_por_dia_promedio = round(mean(valor_por_dia), 2)
),
by = cohorte_mes
][order(cohorte_mes)]
cat("\n=== ANÁLISIS DE COHORTES POR MES ===\n")
#>
#> === ANÁLISIS DE COHORTES POR MES ===
print(head(analisis_cohortes, 12))
#> cohorte_mes clientes_cohorte valor_promedio_cohorte dias_retencion_promedio
#> <char> <int> <num> <num>
#> 1: 2023-01 1919 480.73 576.0
#> 2: 2023-02 1419 468.32 540.2
#> 3: 2023-03 1254 449.64 513.0
#> 4: 2023-04 1018 434.10 479.9
#> 5: 2023-05 797 418.61 458.0
#> ---
#> 8: 2023-08 451 354.58 356.5
#> 9: 2023-09 366 342.35 336.0
#> 10: 2023-10 310 326.32 289.0
#> 11: 2023-11 218 323.99 274.6
#> 12: 2023-12 192 277.01 260.5
#> valor_por_dia_promedio
#> <num>
#> 1: 1.71
#> 2: 1.81
#> 3: 2.11
#> 4: 2.59
#> 5: 2.89
#> ---
#> 8: 3.89
#> 9: 5.20
#> 10: 7.84
#> 11: 6.48
#> 12: 5.1612.6 Ejercicio Final: Aplicación Completa de Producción
💡 Solución del Ejercicio 10
# === PIPELINE DE DATOS UNIFICADO ===
crear_pipeline_analytics <- function() {
# 1. CONSOLIDACIÓN DE DATOS
pipeline_data <- list()
# Métricas de clientes
pipeline_data$clientes_kpis <- datos_clientes[,
.(
clientes_total = .N,
valor_total = sum(valor_cliente),
churn_rate = round(mean(churn_flag) * 100, 1),
satisfaccion_promedio = round(mean(satisfaccion), 2),
engagement_promedio = round(mean(engagement_score), 2)
),
by = .(region, categoria_ingresos)
]
# Métricas de transacciones
pipeline_data$transacciones_kpis <- transacciones_detalle[
fecha_transaccion >= Sys.Date() - 90, # Últimos 90 días
.(
revenue_total = sum(monto_final),
transacciones_total = .N,
ticket_promedio = round(mean(monto_final), 2),
clientes_activos = uniqueN(cliente_id),
productos_vendidos = sum(1 - es_devolucion)
),
by = .(mes = month(fecha_transaccion), producto_categoria)
]
# Estado de sensores IoT
pipeline_data$sensores_status <- sensores_iot[
fecha >= Sys.Date() - 7, # Última semana
.(
sensores_activos = uniqueN(sensor_id),
alertas_criticas = sum(alerta_temperatura + alerta_bateria + alerta_humedad),
uptime_promedio = round(mean(1 - is.na(temperatura)) * 100, 1),
score_salud = round(mean(100 - alerta_temperatura * 20 - alerta_bateria * 30), 1)
),
by = fecha
]
# 2. ALERTAS AUTOMÁTICAS
alertas <- list()
# Alerta de churn elevado
alertas$churn_critico <- pipeline_data$clientes_kpis[
churn_rate > 15,
.(region, categoria_ingresos, churn_rate, valor_total)
]
# Alerta de caída de revenue
alertas$revenue_bajo <- pipeline_data$transacciones_kpis[,
.(revenue_cambio = (revenue_total / shift(revenue_total, 1) - 1) * 100),
by = producto_categoria
][revenue_cambio < -10 & !is.na(revenue_cambio)]
# Alerta de sensores críticos
alertas$sensores_criticos <- pipeline_data$sensores_status[
score_salud < 80 | alertas_criticas > 10
]
# 3. DASHBOARD SUMMARY
dashboard_summary <- list(
kpis_generales = list(
clientes_total = sum(pipeline_data$clientes_kpis$clientes_total),
revenue_total = sum(pipeline_data$transacciones_kpis$revenue_total),
churn_promedio = round(weighted.mean(pipeline_data$clientes_kpis$churn_rate,
pipeline_data$clientes_kpis$clientes_total), 1),
alertas_activas = length(alertas$churn_critico) + nrow(alertas$revenue_bajo) + nrow(alertas$sensores_criticos)
),
alertas_activas = alertas,
datos_pipeline = pipeline_data
)
return(dashboard_summary)
}
# Ejecutar pipeline
sistema_analytics <- crear_pipeline_analytics()
# === REPORTE EJECUTIVO AUTOMÁTICO ===
generar_reporte_ejecutivo <- function(analytics_data) {
cat("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
cat(" 📊 REPORTE EJECUTIVO EMPRESARIAL \n")
cat("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n")
kpis <- analytics_data$kpis_generales
alertas <- analytics_data$alertas_activas
# KPIs principales
cat("📈 INDICADORES CLAVE DE RENDIMIENTO:\n")
cat(" • Total de Clientes:", scales::comma(kpis$clientes_total), "\n")
cat(" • Revenue Total:", scales::dollar(kpis$revenue_total), "\n")
cat(" • Tasa de Churn Promedio:", kpis$churn_promedio, "%\n")
cat(" • Alertas Activas:", kpis$alertas_activas, "\n\n")
# Estado de alertas
cat("🚨 ESTADO DE ALERTAS:\n")
cat(" • Regiones con Churn Crítico:", nrow(alertas$churn_critico), "\n")
cat(" • Productos con Revenue Bajo:", nrow(alertas$revenue_bajo), "\n")
cat(" • Sensores en Estado Crítico:", nrow(alertas$sensores_criticos), "\n\n")
# Recomendaciones automáticas
cat("💡 RECOMENDACIONES AUTOMÁTICAS:\n")
if(nrow(alertas$churn_critico) > 0) {
cat(" • ACCIÓN INMEDIATA: Implementar programa de retención en regiones críticas\n")
}
if(nrow(alertas$revenue_bajo) > 0) {
cat(" • ANÁLISIS REQUERIDO: Investigar caída de ventas en productos específicos\n")
}
if(nrow(alertas$sensores_criticos) > 0) {
cat(" • MANTENIMIENTO: Revisar sensores con bajo score de salud\n")
}
if(kpis$alertas_activas == 0) {
cat(" • ✅ SISTEMA SALUDABLE: Todos los indicadores dentro de rangos normales\n")
}
cat("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
cat("Reporte generado automáticamente:", format(Sys.time(), "%Y-%m-%d %H:%M:%S"), "\n")
cat("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
}
# Generar reporte
generar_reporte_ejecutivo(sistema_analytics)
#> ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#> 📊 REPORTE EJECUTIVO EMPRESARIAL
#> ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#>
#> 📈 INDICADORES CLAVE DE RENDIMIENTO:
#> • Total de Clientes: 10,000
#> • Revenue Total: $0
#> • Tasa de Churn Promedio: 12.1 %
#> • Alertas Activas: 4
#>
#> 🚨 ESTADO DE ALERTAS:
#> • Regiones con Churn Crítico: 0
#> • Productos con Revenue Bajo: 0
#> • Sensores en Estado Crítico: 0
#>
#> 💡 RECOMENDACIONES AUTOMÁTICAS:
#>
#> ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#> Reporte generado automáticamente: 2025-08-21 07:05:24
#> ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# === MÉTRICAS DETALLADAS ===
cat("\n📊 DETALLE DE ALERTAS CRÍTICAS:\n\n")
#>
#> 📊 DETALLE DE ALERTAS CRÍTICAS:
if(nrow(sistema_analytics$alertas_activas$churn_critico) > 0) {
cat("🔴 CHURN CRÍTICO POR REGIÓN:\n")
print(sistema_analytics$alertas_activas$churn_critico)
cat("\n")
}
if(nrow(sistema_analytics$alertas_activas$revenue_bajo) > 0) {
cat("📉 PRODUCTOS CON REVENUE BAJO:\n")
print(head(sistema_analytics$alertas_activas$revenue_bajo))
cat("\n")
}
if(nrow(sistema_analytics$alertas_activas$sensores_criticos) > 0) {
cat("⚠️ SENSORES EN ESTADO CRÍTICO:\n")
print(head(sistema_analytics$alertas_activas$sensores_criticos))
}Componentes del Sistema Completo:
- Pipeline de Datos: Consolidación automática de múltiples fuentes
- Sistema de Alertas: Detección automática de anomalías
- Dashboard KPIs: Métricas en tiempo real
- Reporte Ejecutivo: Generación automática de insights
- Recomendaciones: Acciones basadas en datos
- Escalabilidad: Modular y extensible
🎯 Puntos Clave de Este Capítulo
- Shiny + data.table = Aplicaciones web ultrarrápidas y responsivas
- tidymodels se integra perfectamente con data.table para ML robusto
- dtplyr facilita la transición desde dplyr manteniendo performance
- fread/fwrite son las herramientas más rápidas de R para I/O
- Bases de datos + data.table = Workflow híbrido óptimo
- Casos reales demuestran la versatilidad industrial de data.table
- Sistemas completos integran múltiples componentes de forma modular
Has completado tu formación integral en aplicaciones del mundo real con data.table. Ahora tienes las herramientas para construir sistemas completos de analytics empresarial de nivel industrial.