# Opciones knit y repos
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
options(repos = c(CRAN = "https://cloud.r-project.org/"))
# Reproducibilidad y estética
set.seed(123)
library(ggplot2)
theme_set(theme_minimal(base_size = 13))
library(tidyverse)
library(readxl)
library(lubridate)
library(mclust)
knitr::opts_chunk$set(
echo = TRUE,
warning = FALSE,
message = FALSE
)
En este punto se carga la base de datos y se seleccionan las categorías de balones y bloques de construcción. Posteriormente, se calculan estadísticas descriptivas para el precio y la cantidad vendida.
# ========================================================================================
#=================== PUNTO 1 CARGA Y SELECCION VARIABLES =================================
# ========================================================================================
library(tidyverse)
library(readxl)
df <- read_excel("jugueteria_ventas_diarias_rtaborda20260626.xlsx") %>%
filter(categoria %in% c("balones", "bloques de construcción"))
tabla_desc <- df %>%
group_by(categoria) %>%
summarise(
n = n(),
p_media = mean(precio),
p_sd = sd(precio),
p_mediana = median(precio),
p_iqr = IQR(precio),
q_media = mean(cantidad),
q_sd = sd(cantidad),
q_mediana = median(cantidad),
q_iqr = IQR(cantidad)
)
print(tabla_desc)
## # A tibble: 2 × 10
## categoria n p_media p_sd p_mediana p_iqr q_media q_sd q_mediana q_iqr
## <chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 balones 7304 25011. 1995. 25032 2698. 86.3 36.0 81 45
## 2 bloques de … 7304 90108. 7212. 90000 9842. 21.6 8.96 20 12
Se analizan gráficamente las distribuciones de los precios y de las cantidades vendidas para las dos categorías seleccionadas.
# ========================================================================================
#=================== PUNTO 2 HISTOGRAMAS Y DISTRIBUCIÓN DE PRECIOS =======================
# ========================================================================================
library(patchwork)
promedios <- df %>%
group_by(categoria) %>%
summarise(
p_media = mean(precio),
q_media = mean(cantidad)
)
g_precio <- ggplot(df, aes(x = precio, fill = categoria)) +
geom_histogram(
bins = 30,
alpha = 0.7,
position = "identity",
color = "white"
) +
geom_vline(
data = promedios,
aes(
xintercept = p_media,
color = categoria
),
linetype = "dashed",
linewidth = 0.9
) +
labs(
title = "Distribución de Precios",
x = "Precio (COP)",
y = "Frecuencia"
) +
theme_minimal() +
theme(legend.position = "none")
g_cant <- ggplot(df, aes(x = cantidad, fill = categoria)) +
geom_histogram(
bins = 30,
alpha = 0.7,
position = "identity",
color = "white"
) +
geom_vline(
data = promedios,
aes(
xintercept = q_media,
color = categoria
),
linetype = "dashed",
linewidth = 0.9
) +
labs(
title = "Distribución de Cantidades",
x = "Cantidad (Unidades)",
y = "Frecuencia",
fill = "Categoría",
color = "Categoría"
) +
theme_minimal() +
theme(legend.position = "bottom")
g_precio
g_cant
g_box_p <- ggplot(
df,
aes(
x = categoria,
y = precio,
fill = categoria
)
) +
geom_boxplot(
alpha = 0.7,
outlier.size = 1.5,
outlier.alpha = 0.3
) +
stat_summary(
fun = mean,
geom = "point",
shape = 18,
size = 4.5,
color = "darkred"
) +
labs(
title = "Dispersión de Precios",
x = "",
y = "Precio (COP)"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(
size = 16,
face = "bold"
),
axis.title.y = element_text(
size = 14,
face = "bold"
),
axis.text.x = element_text(
size = 13,
face = "bold",
color = "black"
),
axis.text.y = element_text(
size = 12,
face = "bold",
color = "black"
)
)
g_box_q <- ggplot(
df,
aes(
x = categoria,
y = cantidad,
fill = categoria
)
) +
geom_boxplot(
alpha = 0.7,
outlier.size = 1.5,
outlier.alpha = 0.3
) +
stat_summary(
fun = mean,
geom = "point",
shape = 18,
size = 4.5,
color = "darkred"
) +
labs(
title = "Dispersión de Cantidades",
x = "",
y = "Cantidad (Unidades)"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(
size = 16,
face = "bold"
),
axis.title.y = element_text(
size = 14,
face = "bold"
),
axis.text.x = element_text(
size = 13,
face = "bold",
color = "black"
),
axis.text.y = element_text(
size = 12,
face = "bold",
color = "black"
)
)
print(g_box_p)
print(g_box_q)
Se analiza la relación entre el precio de los productos y la cantidad vendida, diferenciando las categorías.
# ========================================================================================
#=================== PUNTO 3 DISPERCION VS PRECIOS =======================
# ========================================================================================
g_scatter <- ggplot(
df,
aes(
x = precio,
y = cantidad,
color = categoria
)
) +
geom_point(
alpha = 0.25,
size = 1.2
) +
geom_smooth(
method = "lm",
se = FALSE,
linetype = "solid",
linewidth = 1
) +
geom_vline(
data = promedios,
aes(
xintercept = p_media,
color = categoria
),
linetype = "dashed"
) +
geom_hline(
data = promedios,
aes(
yintercept = q_media,
color = categoria
),
linetype = "dashed"
) +
labs(
title = "Relación Cantidad vs. Precio",
x = "Precio (COP)",
y = "Cantidad Demandada (Unidades)",
color = "Categoría"
) +
theme_minimal() +
theme(legend.position = "bottom")
g_scatter
Se representa la evolución temporal de las ventas mensuales entre 2010 y 2014 para cada categoría.
# ========================================================================================
#=================== PUNTO 4 SERIES DE TIEMPO =======================
# ========================================================================================
library(lubridate)
df_ts <- df %>%
mutate(
fecha = as.Date(fecha),
periodo = floor_date(fecha, unit = "month")
) %>%
group_by(periodo, categoria) %>%
summarise(
total_cantidad = sum(cantidad, na.rm = TRUE),
.groups = "drop"
)
g_ts <- ggplot(
df_ts,
aes(
x = periodo,
y = total_cantidad,
color = categoria
)
) +
geom_line(linewidth = 0.9) +
geom_point(size = 1.3) +
scale_x_date(
breaks = seq(
as.Date("2010-01-01"),
as.Date("2014-12-01"),
by = "6 months"
),
date_labels = "%b %Y"
) +
labs(
title = "Evolución Temporal de Ventas Mensuales (2010 - 2014)",
x = "Fecha",
y = "Unidades Totales Vendidas",
color = "Categoría"
) +
theme_minimal() +
theme(
legend.position = "bottom",
axis.text.x = element_text(
angle = 45,
hjust = 1
)
)
g_ts
Se realiza una segmentación de los almacenes utilizando la cantidad promedio vendida y los ingresos totales como indicadores de desempeño comercial.
# ========================================================================================
#=================== PUNTO 5 CLUSTERING =======================
# ========================================================================================
library(factoextra)
library(ggrepel)
df_cluster_data <- df %>%
group_by(id_almacen) %>%
summarise(
cant_prom = mean(cantidad),
ingreso_total = sum(
as.numeric(precio) *
as.numeric(cantidad)
),
.groups = "drop"
) %>%
column_to_rownames("id_almacen")
df_scaled <- scale(df_cluster_data)
#Evaluación del número de clústeres
#Se utiliza el método del codo para evaluar el número óptimo de grupos.
g_elbow <- fviz_nbclust(
df_scaled,
kmeans,
method = "wss",
k.max = 3
) +
labs(
title = "Evaluación del Codo (Elbow Method)",
x = "k",
y = "WSS"
) +
theme_minimal()
print(g_elbow)
Se representan los almacenes según el clúster al que pertenecen, utilizando las variables estandarizadas de cantidad promedio e ingresos totales.
# Segmentación mediante K-means
set.seed(123)
km_res <- kmeans(
df_scaled,
centers = 3,
nstart = 25
)
# Punto 5. Clustering
library(factoextra)
library(ggrepel)
# 1. Preparación y escalado de datos por almacén
df_cluster_data <- df %>%
group_by(id_almacen) %>%
summarise(
cant_prom = mean(cantidad),
ingreso_total = sum(
as.numeric(precio) * as.numeric(cantidad)
),
.groups = "drop"
) %>%
column_to_rownames("id_almacen")
df_scaled <- scale(df_cluster_data)
# 2. Evaluación del número de clústeres
g_elbow <- fviz_nbclust(
df_scaled,
kmeans,
method = "wss",
k.max = 3
) +
labs(
title = "Evaluación del Codo (Elbow Method)",
x = "k",
y = "WSS"
) +
theme_minimal()
print(g_elbow)
# 3. Creación del modelo K-means
set.seed(123)
km_res <- kmeans(
df_scaled,
centers = 3,
nstart = 25
)
# 4. Preparación de los datos para la gráfica
df_plot <- as.data.frame(df_scaled) %>%
mutate(
almacen = rownames(.),
cluster = as.factor(km_res$cluster)
)
# 5. Gráfica de segmentación
g_cluster_clean <- ggplot(
df_plot,
aes(
x = cant_prom,
y = ingreso_total,
color = cluster
)
) +
geom_point(
aes(shape = cluster),
size = 4
) +
geom_text_repel(
aes(label = almacen),
size = 4.5,
fontface = "bold",
box.padding = 0.5,
point.padding = 0.3
) +
labs(
title = "Segmentación de Almacenes por Desempeño Comercial",
x = "Volumen Promedio Diario (Estandarizado)",
y = "Ingresos Totales (Estandarizados)",
color = "Clúster",
shape = "Clúster"
) +
theme_minimal() +
theme(legend.position = "bottom")
print(g_cluster_clean)
Se compara la venta promedio diaria de cada almacén según la categoría de producto.
g_store_comp <- df %>%
group_by(id_almacen, categoria) %>%
summarise(
cant_prom = mean(cantidad),
.groups = "drop"
) %>%
ggplot(
aes(
x = id_almacen,
y = cant_prom,
fill = categoria
)
) +
geom_col(
position = "dodge",
alpha = 0.85,
width = 0.6
) +
geom_text(
aes(label = round(cant_prom, 1)),
position = position_dodge(0.6),
vjust = -0.5,
size = 3.5,
fontface = "bold"
) +
labs(
title = "Comparativo de Rotación Diaria por Almacén y Categoría",
x = "Almacén",
y = "Venta Promedio Diaria (Unidades)",
fill = "Categoría"
) +
theme_minimal() +
theme(legend.position = "bottom")
print(g_store_comp)