# Bibliotecas -------------------------------------------------------------
if(!require(tidyverse)) install.packages("tidyverse")
# Config -------------------------------------------------------------
# Para notación no numérica.
options(scipen=999)
# Para reproducibilidad.
set.seed(2024) Minería de Datos
Práctica 2. Selección, limpieza y transformación de datos
Introducción
En esta práctica, abordamos el desafío de predecir las ventas mensuales de una cadena de supermercados en Ecuador. El objetivo principal es aplicar técnicas de selección, limpieza y transformación de datos para preparar un conjunto de datos que sea adecuado para el desarrollo de un modelo predictivo. Este modelo permitirá a la empresa optimizar su inventario, planificar campañas de marketing de manera más efectiva y mejorar la gestión de la cadena de suministro.
Los datos disponibles incluyen información detallada sobre productos, ubicaciones, clientes y promociones, lo que ofrece una base rica para el análisis. Como primer paso en este proyecto, se requiere llevar a cabo un proceso exhaustivo de selección, limpieza y transformación de los datos, garantizando que estén en las condiciones óptimas para un análisis y modelado precisos
Configuración Inicial
0. Dominio del problema
En este paso previo, desarrollamos una comprensión profunda del problema que enfrentamos y lo traducimos en datos “entendibles” para la máquina. Respondamos a las preguntas clave:
¿Cuál es el problema específico que estamos tratando de resolver?
Estamos tratando de predecir las ventas mensuales de una cadena de supermercados en Ecuador en función de factores como el histórico de ventas, la ubicación, el tipo de producto, las características del cliente y las promociones.
¿Es factible lograr nuestros objetivos con los datos disponibles? ¿Cómo podemos alcanzarlos?
Sí, es factible. Tenemos acceso a un conjunto de datos histórico de ventas que incluye información sobre productos, ubicaciones, clientes y promociones.
¿Cuáles son los beneficios si nuestra solución funciona y cuáles son las consecuencias si falla?
Si nuestra solución funciona:
- La cadena de supermercados podrá optimizar su inventario por ciudad y tipo de producto.
- Se podrán planificar campañas de marketing de manera más efectiva, enfocándose en productos y segmentos de clientes específicos.
- Mejorará la gestión de la cadena de suministro, reduciendo costos y desperdicios.
- Aumentarán las ganancias al tener una mejor previsión de la demanda.
Si la solución falla:
- La cadena podría enfrentar pérdidas financieras debido a exceso o falta de inventario.
- Podrían surgir dificultades en la gestión de la cadena de suministro y la planificación de promociones.
- Se podrían tomar decisiones de negocio basadas en predicciones incorrectas, lo que afectaría la competitividad de la empresa.
¿Qué tipo de problema se va a resolver: Predictivo/Descriptivo?
Estamos abordando principalmente un problema predictivo, ya que queremos predecir las ventas futuras en función de datos históricos y variables predictoras. Sin embargo, también incluiremos elementos descriptivos para entender mejor los patrones de compra y el comportamiento del cliente.
¿El objetivo es predecir, segmentar, …?
Nuestro objetivo principal es predecir las ventas mensuales. Adicionalmente, podriamos resolver otras tareas:
- Segmentación de clientes para comprender mejor el comportamiento de compra de diferentes grupos.
- Análisis de tendencias de ventas por ubicación y tipo de producto.
¿De dónde vendrán los datos, cuánto cuesta conseguirlos?
Los datos de ventas históricas provienen de la base de datos interna de la cadena de supermercados, en concreto de los sistemas de gestión de la empresa. El costo de adquirir estos datos es mínimo, ya que la mayoría estaría disponible internamente. Sin embargo, podría haber costos asociados con la limpieza y preparación de los datos, así como con la implementación de sistemas para recopilar datos adicionales si fuera necesario.
1. Selección
En esta etapa, se realiza la selección del conjunto de datos. Como este es proporcionado por la empresa, únicamente lo cargamos.
# Cargar datos
ventas_ecuador <- read.csv("ventas_ecuador.csv")
# mostrar un vista previa de datos
glimpse(ventas_ecuador)Rows: 2,350
Columns: 9
$ fecha <chr> "2019-01-01", "2019-01-02", "2019-01-03", "2019-0…
$ ciudad <chr> "Quito", "Guayaquil", "Guayaquil", "Quito", "Mant…
$ producto <chr> "Leche", "Atún", "Verduras", "Carne", "Atún", "At…
$ ventas <dbl> 399.26, 75.36, 414.08, 250.49, 313.35, 235.28, 11…
$ unidades_vendidas <int> 49, 31, 34, 19, 31, 37, 42, 20, 42, 37, 19, 46, 6…
$ edad_cliente <int> 24, 24, 45, 53, 34, 39, 18, 45, 59, 36, 32, 42, 1…
$ promocion <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, T…
$ satisfaccion_cliente <int> 2, 4, 5, 4, 3, 4, 4, 1, 2, 3, 5, 1, 4, 2, 5, 4, 1…
$ tasa_devolucion <dbl> 0.08, 0.09, 0.02, 0.04, 0.02, 0.03, 0.07, 0.01, 0…
Se observa que el conjunto de datos tiene 2350 instancias y 10 variables. Se muestran ejemplos de los datos que contiene cada columna así como su tipo de variable.
2. Limpieza
La limpieza de datos es un paso crucial para garantizar la calidad de nuestro análisis. Abordaremos varios aspectos:
2.1 Limpieza
Eliminación de observaciones duplicadas
# Verificamos si hay duplicados
ventas_repetidas <- ventas_ecuador |>
group_by_all() |>
filter(n() > 1) |>
ungroup()
# Vemos las repetidas
glimpse(ventas_repetidas)Rows: 20
Columns: 9
$ fecha <chr> "2019-01-01", "2019-01-02", "2019-01-03", "2019-0…
$ ciudad <chr> "Quito", "Guayaquil", "Guayaquil", "Quito", "Mant…
$ producto <chr> "Leche", "Atún", "Verduras", "Carne", "Atún", "At…
$ ventas <dbl> 399.26, 75.36, 414.08, 250.49, 313.35, 235.28, 11…
$ unidades_vendidas <int> 49, 31, 34, 19, 31, 37, 42, 20, 42, 37, 49, 31, 3…
$ edad_cliente <int> 24, 24, 45, 53, 34, 39, 18, 45, 59, 36, 24, 24, 4…
$ promocion <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, T…
$ satisfaccion_cliente <int> 2, 4, 5, 4, 3, 4, 4, 1, 2, 3, 2, 4, 5, 4, 3, 4, 4…
$ tasa_devolucion <dbl> 0.08, 0.09, 0.02, 0.04, 0.02, 0.03, 0.07, 0.01, 0…
# Elminamos duplicados
ventas_ecuador_limpio <- ventas_ecuador |> distinct()
cat("Filas originales:", nrow(ventas_ecuador), "\n")Filas originales: 2350
cat("Filas con repetidos:", nrow(ventas_repetidas), "\n")Filas con repetidos: 20
cat("Filas después de eliminar duplicados:", nrow(ventas_ecuador_limpio), "\n")Filas después de eliminar duplicados: 2340
Valores faltantes
# Verificamos los NA
valores_faltantes <- ventas_ecuador_limpio |> summarise_all(~sum(is.na(.)))
# Mostramos los Na en cada variable
glimpse(valores_faltantes)Rows: 1
Columns: 9
$ fecha <int> 0
$ ciudad <int> 0
$ producto <int> 0
$ ventas <int> 0
$ unidades_vendidas <int> 0
$ edad_cliente <int> 51
$ promocion <int> 0
$ satisfaccion_cliente <int> 0
$ tasa_devolucion <int> 0
Observamos los faltantes en la variable edad_cliente. Para corregir los NA realizamos lo siguiente:
ventas_ecuador_limpio <- ventas_ecuador_limpio |>
mutate(
# Reemplazamos los NA en 'edad_cliente' con la mediana de la columna
edad_cliente = ifelse(is.na(edad_cliente), median(edad_cliente, na.rm = TRUE), edad_cliente)
)
# Verificamos nuevamente si quedan NA en 'edad_cliente'
valores_faltantes <- ventas_ecuador_limpio |>
summarise(edad_cliente = sum(is.na(edad_cliente)))
glimpse(valores_faltantes)Rows: 1
Columns: 1
$ edad_cliente <int> 0
Errores estructurales
Vamos a asegurarnos de que los nombres de las ciudades estén escritos correctamente:
# Verificamos las formas de escritura de cada ciudad
ventas_ecuador_limpio |>
count(ciudad) ciudad n
1 Cuenca 471
2 GUAYAQUIL 1
3 Guayaquil 452
4 Manta 478
5 Portoviejo 470
6 Quito 465
7 cuenca 1
8 portoviejo 1
9 quito 1
Observamos que hay diversas formas de escritura de una misma ciudad. Para corregir eso realizamos lo siguiente:
# Convertimos todos los nombre a formato titulo con str_to_title
ventas_ecuador_limpio <- ventas_ecuador_limpio |>
mutate(ciudad = str_to_title(ciudad))
# Verificamos
ventas_ecuador_limpio |>
count(ciudad) ciudad n
1 Cuenca 472
2 Guayaquil 453
3 Manta 478
4 Portoviejo 471
5 Quito 466
Fechas fuera de rango
Verificaremos si hay fechas fuera del rango esperado:
# Obtener el rango actual de fechas en el dataset
rango_fechas <- ventas_ecuador_limpio |>
summarise(
fecha_min = min(fecha),
fecha_max = max(fecha)
)
print(rango_fechas) fecha_min fecha_max
1 2019-01-01 2026-12-31
Se evidencia que algunas fechas son posteriores al 2024. Estos casos serán eliminados.
# Filtrar para eliminar fechas superiores al año 2024
ventas_ecuador_limpio <- ventas_ecuador_limpio |>
filter(year(fecha) <= 2024)
# Verificar el nuevo rango de fechas
nuevo_rango_fechas <- ventas_ecuador_limpio |>
summarise(
fecha_min = min(fecha),
fecha_max = max(fecha)
)
print(nuevo_rango_fechas) fecha_min fecha_max
1 2019-01-01 2024-12-31
2.2 Enriquecimiento
En esta etapa, agregaremos información adicional para enriquecer nuestro conjunto de datos.
Enriquecimiento geográfico
Añadiremos información sobre la región de cada ciudad:
ventas_ecuador_enriquecido <- ventas_ecuador_limpio |>
mutate(
region = case_when(
ciudad %in% c("Quito") ~ "Sierra",
ciudad %in% c("Guayaquil", "Manta", "Portoviejo") ~ "Costa",
ciudad == "Cuenca" ~ "Austro",
TRUE ~ "Otra"
)
)
glimpse(ventas_ecuador_enriquecido)Rows: 2,196
Columns: 10
$ fecha <chr> "2019-01-01", "2019-01-02", "2019-01-03", "2019-0…
$ ciudad <chr> "Quito", "Guayaquil", "Guayaquil", "Quito", "Mant…
$ producto <chr> "Leche", "Atún", "Verduras", "Carne", "Atún", "At…
$ ventas <dbl> 399.26, 75.36, 414.08, 250.49, 313.35, 235.28, 11…
$ unidades_vendidas <int> 49, 31, 34, 19, 31, 37, 42, 20, 42, 37, 19, 46, 6…
$ edad_cliente <int> 24, 24, 45, 53, 34, 39, 18, 45, 59, 36, 32, 42, 1…
$ promocion <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, T…
$ satisfaccion_cliente <int> 2, 4, 5, 4, 3, 4, 4, 1, 2, 3, 5, 1, 4, 2, 5, 4, 1…
$ tasa_devolucion <dbl> 0.08, 0.09, 0.02, 0.04, 0.02, 0.03, 0.07, 0.01, 0…
$ region <chr> "Sierra", "Costa", "Costa", "Sierra", "Costa", "C…
Enriquecimiento demográfico
Categorizaremos a los clientes por grupo de edad:
ventas_ecuador_enriquecido <- ventas_ecuador_enriquecido |>
mutate(
edad_grupo = cut(edad_cliente,
breaks = c(0, 25, 40, 60, Inf),
labels = c("Joven", "Adulto joven", "Adulto", "Adulto mayor"),
right = FALSE)
)
glimpse(ventas_ecuador_enriquecido)Rows: 2,196
Columns: 11
$ fecha <chr> "2019-01-01", "2019-01-02", "2019-01-03", "2019-0…
$ ciudad <chr> "Quito", "Guayaquil", "Guayaquil", "Quito", "Mant…
$ producto <chr> "Leche", "Atún", "Verduras", "Carne", "Atún", "At…
$ ventas <dbl> 399.26, 75.36, 414.08, 250.49, 313.35, 235.28, 11…
$ unidades_vendidas <int> 49, 31, 34, 19, 31, 37, 42, 20, 42, 37, 19, 46, 6…
$ edad_cliente <int> 24, 24, 45, 53, 34, 39, 18, 45, 59, 36, 32, 42, 1…
$ promocion <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, T…
$ satisfaccion_cliente <int> 2, 4, 5, 4, 3, 4, 4, 1, 2, 3, 5, 1, 4, 2, 5, 4, 1…
$ tasa_devolucion <dbl> 0.08, 0.09, 0.02, 0.04, 0.02, 0.03, 0.07, 0.01, 0…
$ region <chr> "Sierra", "Costa", "Costa", "Sierra", "Costa", "C…
$ edad_grupo <fct> Joven, Joven, Adulto, Adulto, Adulto joven, Adult…
Enriquecimiento de productos
Añadiremos una categorización de productos:
ventas_ecuador_enriquecido <- ventas_ecuador_enriquecido |>
mutate(
categoria_producto = case_when(
producto %in% c("Arroz", "Atún", "Leche", "Pan") ~ "Alimentos básicos",
producto %in% c("Frutas", "Verduras") ~ "Productos frescos",
producto %in% c("Carne", "Pollo") ~ "Proteínas",
TRUE ~ "Otro"
)
)
glimpse(ventas_ecuador_enriquecido)Rows: 2,196
Columns: 12
$ fecha <chr> "2019-01-01", "2019-01-02", "2019-01-03", "2019-0…
$ ciudad <chr> "Quito", "Guayaquil", "Guayaquil", "Quito", "Mant…
$ producto <chr> "Leche", "Atún", "Verduras", "Carne", "Atún", "At…
$ ventas <dbl> 399.26, 75.36, 414.08, 250.49, 313.35, 235.28, 11…
$ unidades_vendidas <int> 49, 31, 34, 19, 31, 37, 42, 20, 42, 37, 19, 46, 6…
$ edad_cliente <int> 24, 24, 45, 53, 34, 39, 18, 45, 59, 36, 32, 42, 1…
$ promocion <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, T…
$ satisfaccion_cliente <int> 2, 4, 5, 4, 3, 4, 4, 1, 2, 3, 5, 1, 4, 2, 5, 4, 1…
$ tasa_devolucion <dbl> 0.08, 0.09, 0.02, 0.04, 0.02, 0.03, 0.07, 0.01, 0…
$ region <chr> "Sierra", "Costa", "Costa", "Sierra", "Costa", "C…
$ edad_grupo <fct> Joven, Joven, Adulto, Adulto, Adulto joven, Adult…
$ categoria_producto <chr> "Alimentos básicos", "Alimentos básicos", "Produc…
2.3 Análisis Exploratorio de Datos (EDA)
El EDA nos ayudará a comprender mejor la naturaleza de nuestros datos. Analizaremos cada variable considerando si es numérica o categórica.
# Asegurémonos de que estamos trabajando con el conjunto de datos de calidad
ventas_ecuador_enriquecido <- ventas_ecuador_enriquecido |>
mutate(fecha = as.Date(fecha)) # Asegurarse de que fecha es de tipo Date
glimpse(ventas_ecuador_enriquecido)Rows: 2,196
Columns: 12
$ fecha <date> 2019-01-01, 2019-01-02, 2019-01-03, 2019-01-04, …
$ ciudad <chr> "Quito", "Guayaquil", "Guayaquil", "Quito", "Mant…
$ producto <chr> "Leche", "Atún", "Verduras", "Carne", "Atún", "At…
$ ventas <dbl> 399.26, 75.36, 414.08, 250.49, 313.35, 235.28, 11…
$ unidades_vendidas <int> 49, 31, 34, 19, 31, 37, 42, 20, 42, 37, 19, 46, 6…
$ edad_cliente <int> 24, 24, 45, 53, 34, 39, 18, 45, 59, 36, 32, 42, 1…
$ promocion <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, T…
$ satisfaccion_cliente <int> 2, 4, 5, 4, 3, 4, 4, 1, 2, 3, 5, 1, 4, 2, 5, 4, 1…
$ tasa_devolucion <dbl> 0.08, 0.09, 0.02, 0.04, 0.02, 0.03, 0.07, 0.01, 0…
$ region <chr> "Sierra", "Costa", "Costa", "Sierra", "Costa", "C…
$ edad_grupo <fct> Joven, Joven, Adulto, Adulto, Adulto joven, Adult…
$ categoria_producto <chr> "Alimentos básicos", "Alimentos básicos", "Produc…
Variable: fecha (Temporal)
Primero, observamos la distribución tipo quartiles de la variable fecha.
# Resumen básico
summary(ventas_ecuador_enriquecido$fecha) Min. 1st Qu. Median Mean 3rd Qu. Max.
"2019-01-01" "2020-07-02" "2022-01-02" "2022-01-01" "2023-07-01" "2024-12-31"
Luego, la distribicón temporal de las fechas.
# Visualización de la distribución temporal
ggplot(ventas_ecuador_enriquecido, aes(x = fecha)) +
geom_histogram(binwidth = 7, fill = "skyblue", color = "black") +
labs(title = "Distribución de Ventas a lo largo del tiempo",
x = "Fecha", y = "Número de Ventas")Y finalmente el comportamiento de las ventas por mes
# Ventas por mes
ventas_ecuador_enriquecido |>
mutate(mes = floor_date(fecha, "month")) |>
group_by(mes) |>
summarise(total_ventas = sum(ventas)) |>
ggplot(aes(x = mes, y = total_ventas)) +
geom_line() +
geom_point() +
labs(title = "Ventas Totales por Mes",
x = "Mes", y = "Ventas totales")A partir de los análisis concluimos que las ventas muestran cierta estacionalidad mensual, con picos y valles a lo largo del año.
Variable: ciudad (Cualitativa)
Para la variable ciudad analizamos la cantidad de registros para cada ciudad.
# Frecuencia de ventas por ciudad
ventas_ecuador_enriquecido |> count(ciudad) ciudad n
1 Cuenca 431
2 Guayaquil 427
3 Manta 449
4 Portoviejo 449
5 Quito 440
Ahora analizamos la distribución de ventas por ciudad mediante un diagrama de cajas.
# Visualización de ventas por ciudad
ggplot(ventas_ecuador_enriquecido, aes(x = ciudad, y = ventas)) +
geom_boxplot(fill = "lightgreen") +
labs(title = "Distribución de ventas (cantidad) por ciudad",
x = "Ciudad", y = "Ventas (cantidad)") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Finalmente, observamos las ventas totales por ciudad.
# Ventas totales por ciudad
ventas_ecuador_enriquecido |>
group_by(ciudad) |>
summarise(total_ventas = sum(ventas)) |>
ggplot(aes(x = reorder(ciudad, -total_ventas), y = total_ventas)) +
geom_bar(stat = "identity", fill = "coral") +
labs(title = "Ventas totales ($) por ciudad",
x = "Ciudad", y = "Ventas totales ($)") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Finalmente podemos darnos cuenta que la cantidad de ventas entre ciudades parecen muy similares, con pequeña cantidad superior acumulada en dólares en las ventas de Quito.
Variable: producto (Cualitativa)
Para la variable producto analizamos la cantidad de registros de cada ítem.
# Frecuencia de ventas por producto
ventas_ecuador_enriquecido |> count(producto) producto n
1 Arroz 276
2 Atún 260
3 Carne 264
4 Frutas 288
5 Leche 270
6 Pan 278
7 Pollo 271
8 Verduras 289
Observamos que en total hay 8 distintos productos. Ahora veamos las ventas registradas de cada uno.
# Visualización de ventas por producto
ggplot(ventas_ecuador_enriquecido, aes(x = producto, y = ventas)) +
geom_boxplot(fill = "lightblue") +
labs(title = "Distribución de ventas por producto",
x = "Producto", y = "Ventas (cantidad)") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))# Ventas totales por producto
ventas_ecuador_enriquecido |>
group_by(producto) |>
summarise(total_ventas = sum(ventas)) |>
ggplot(aes(x = reorder(producto, -total_ventas), y = total_ventas)) +
geom_bar(stat = "identity", fill = "gray") +
labs(title = "Ventas totales ($) por Producto",
x = "Producto", y = "Ventas totales ($)") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Con el análisis podemos concluir que las ventas de varios productos parece ser homogénea, sin embargo, las verduras generan mas ingresos a la empresa.
Variable: ventas (Cuantitativa)
Para la variable ventas, que es la variable a predecir, realizamos estadística descriptiva.
# Resumen estadístico
ventas_resumen <- ventas_ecuador_enriquecido |>
summarise(
conteo = n(),
media = mean(ventas, na.rm = TRUE),
mediana = median(ventas, na.rm = TRUE),
desviacion_estandar = sd(ventas, na.rm = TRUE),
min = min(ventas, na.rm = TRUE),
max = max(ventas, na.rm = TRUE),
q1 = quantile(ventas, 0.25, na.rm = TRUE),
q3 = quantile(ventas, 0.75, na.rm = TRUE)
)
print(ventas_resumen) conteo media mediana desviacion_estandar min max q1 q3
1 2196 273.7479 270.47 127.9387 50.03 499.34 169.095 383.4425
También, visualizamos la cantidad de ventas.
# Histograma de ventas
ggplot(ventas_ecuador_enriquecido, aes(x = ventas)) +
geom_histogram(bins = 30, fill = "pink", color = "black") +
labs(title = "Distribución de ventas (cantidad)",
x = "Ventas", y = "Frecuencia")Finalmente, mostramos el diagrama de cajas de la variable.
# Boxplot de ventas
ggplot(ventas_ecuador_enriquecido, aes(y = ventas)) +
geom_boxplot(fill = "lightyellow") +
labs(title = "Boxplot de ventas",
y = "Ventas")Del análisis se puede concluir que las ventas tienen una distribución bastante simétrica y no parecen presentar valores atípicos extremos. La mayoría de las ventas se concentran en el rango de USD200 a USD400, con una mediana alrededor de USD300.
Variable: unidades_vendidas (Cuantitativa)
Mostramos un resumen descriptivo de la variable.
# Resumen estadístico
unidades_resumen <- ventas_ecuador_enriquecido |>
summarise(
conteo = n(),
media = mean(unidades_vendidas, na.rm = TRUE),
mediana = median(unidades_vendidas, na.rm = TRUE),
desviacion_estandar = sd(unidades_vendidas, na.rm = TRUE),
min = min(unidades_vendidas, na.rm = TRUE),
max = max(unidades_vendidas, na.rm = TRUE),
q1 = quantile(unidades_vendidas, 0.25, na.rm = TRUE),
q3 = quantile(unidades_vendidas, 0.75, na.rm = TRUE)
)
print(unidades_resumen) conteo media mediana desviacion_estandar min max q1 q3
1 2196 25.68989 26 14.46271 1 50 13 39
De igual modo, generamos un diagrama de columnas de las unidades vendidas.
# Diagrama de columnas de unidades vendidas
ggplot(ventas_ecuador_enriquecido, aes(x = as.factor(unidades_vendidas))) +
geom_bar(fill = "magenta", color = "black") +
labs(title = "Distribución de Unidades vendidas",
x = "Unidades vendidas", y = "Frecuencia") +
theme(axis.text.x = element_text(angle = 90, hjust = 1))Finalmente, analizamos las unidades vendidas por productos.
# Gráfico de cajas de unidades vendidas por producto
ggplot(ventas_ecuador_enriquecido, aes(x = producto, y = unidades_vendidas, fill = producto)) +
geom_boxplot() +
labs(title = "Distribución de Unidades vendidas por Producto",
x = "Producto", y = "Unidades vendidas") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Observamos que mayoritariamente se venden 16 unidades de productos. La cantidad vendida de cada producto es similar.
Variable: edad_cliente (Cuantitativa)
Para la edad del cliente analizamos su comportamiento a través de un histograma.
# Histograma de edad del cliente
ggplot(ventas_ecuador_enriquecido, aes(x = edad_cliente)) +
geom_histogram(bins = 20, fill = "orange", color = "black") +
labs(title = "Distribución de Edad de los clientes",
x = "Edad", y = "Frecuencia")Ahora analizamos la relación entre edad del cliente y ventas.
# Crear rangos de edad
ventas_ecuador_enriquecido$edad_rango <- cut(ventas_ecuador_enriquecido$edad_cliente, breaks = seq(0, 100, by = 10), right = FALSE)
# Gráfico de cajas por rango de edad
ggplot(ventas_ecuador_enriquecido, aes(x = edad_rango, y = ventas, fill = edad_rango)) +
geom_boxplot() +
labs(title = "Distribución de Ventas por rango de edad del cliente",
x = "Rango de Edad", y = "Ventas") +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
theme_minimal()A partir de las visualizaciones observamos que mayoritariamente los clientes tiene cerca de 40 años. Pero los que mayores ventas han representado a la empresa son aquellos en el rango de 20 a 30 años.
Variable: promocion (Cualitativa)
Analizaremos la variable promocion, que es cualitativa y representa si una venta fue realizada con o sin promoción. Primero, calcularemos la frecuencia de ventas en cada categoría de promoción.
# Cálculo de la frecuencia de ventas con y sin promoción
promocion_freq <- ventas_ecuador_enriquecido |>
count(promocion) |>
rename(Frecuencia = n) |>
mutate(Porcentaje = (Frecuencia / sum(Frecuencia)) * 100)
# Mostrar la tabla de frecuencias
print(promocion_freq) promocion Frecuencia Porcentaje
1 FALSE 1738 79.1439
2 TRUE 458 20.8561
Ahora, visualizaremos cómo varían las ventas entre las ventas con promoción y las ventas sin promoción usando un gráfico de cajas (boxplot).
# Comparación de ventas con y sin promoción
ggplot(ventas_ecuador_enriquecido, aes(x = factor(promocion), y = ventas, fill = factor(promocion))) +
geom_boxplot() +
scale_fill_manual(values = c("purple", "lightseagreen"),
labels = c("Sin promoción", "Con promoción")) +
labs(title = "Comparación de Ventas con y sin promoción",
x = "Promoción", y = "Ventas", fill = "Promoción")De los análisis inferimos que la mayoría de las ventas fue realizada sin promoción, lo que representa cerca del 20%. Al observar las ventas no se evidencia que las mismas sean mayores cuando se hacen con promociones.
Variable: satisfaccion_cliente (Cualitativa)
Primero calcularemos la frecuencia de cada nivel de satisfacción.
# Cálculo de la frecuencia de niveles de satisfacción, incluyendo porcentaje
satisfaccion_freq <- ventas_ecuador_limpio |>
count(satisfaccion_cliente) |>
rename(Frecuencia = n) |>
mutate(Porcentaje = (Frecuencia / sum(Frecuencia)) * 100)
# Mostrar la tabla de frecuencias y porcentajes
print(satisfaccion_freq) satisfaccion_cliente Frecuencia Porcentaje
1 1 445 20.26412
2 2 455 20.71949
3 3 411 18.71585
4 4 454 20.67395
5 5 431 19.62659
Ahora exploraremos la relación entre la satisfacción del cliente y las ventas mediante un gráfico de cajas (boxplot).
# Relación entre satisfacción del cliente y ventas
ggplot(ventas_ecuador_limpio, aes(x = factor(satisfaccion_cliente), y = ventas)) +
geom_boxplot(fill = "lightgreen") +
labs(title = "Relación entre Satisfacción del cliente y ventas",
x = "Nivel de Satisfacción", y = "Ventas")La variable satistacción del cliente tiene cinco categorías que están bastante balanceadas en cerca del 20% cada una. En cuanto a estos niveles y las ventas, también son homogeneas con una pequeña baja en ventas en el nivel de satisfacción 3.
Conclusiones del EDA
Estos hallazgos proporcionan insights valiosos que pueden informar decisiones de negocio y estrategias de marketing, así como ayudar en la selección de características para modelos predictivos.
3. Transformación
En esta etapa, modificaremos la forma de nuestros datos para prepararlos para el análisis.
Eliminación de valores ausentes
Ya verificamos que no hay valores ausentes en nuestro conjunto de datos.
Discretización de atributos
# Discretizaremos la variable 'ventas' en categorías.
ventas_ecuador_transformado <- ventas_ecuador_enriquecido |>
mutate(
ventas_categoria = case_when(
ventas < 150 ~ "Bajo",
ventas >= 150 & ventas < 300 ~ "Medio",
ventas >= 300 ~ "Alto"
)
)
glimpse(ventas_ecuador_transformado)Rows: 2,196
Columns: 14
$ fecha <date> 2019-01-01, 2019-01-02, 2019-01-03, 2019-01-04, …
$ ciudad <chr> "Quito", "Guayaquil", "Guayaquil", "Quito", "Mant…
$ producto <chr> "Leche", "Atún", "Verduras", "Carne", "Atún", "At…
$ ventas <dbl> 399.26, 75.36, 414.08, 250.49, 313.35, 235.28, 11…
$ unidades_vendidas <int> 49, 31, 34, 19, 31, 37, 42, 20, 42, 37, 19, 46, 6…
$ edad_cliente <int> 24, 24, 45, 53, 34, 39, 18, 45, 59, 36, 32, 42, 1…
$ promocion <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, T…
$ satisfaccion_cliente <int> 2, 4, 5, 4, 3, 4, 4, 1, 2, 3, 5, 1, 4, 2, 5, 4, 1…
$ tasa_devolucion <dbl> 0.08, 0.09, 0.02, 0.04, 0.02, 0.03, 0.07, 0.01, 0…
$ region <chr> "Sierra", "Costa", "Costa", "Sierra", "Costa", "C…
$ edad_grupo <fct> Joven, Joven, Adulto, Adulto, Adulto joven, Adult…
$ categoria_producto <chr> "Alimentos básicos", "Alimentos básicos", "Produc…
$ edad_rango <fct> "[20,30)", "[20,30)", "[40,50)", "[50,60)", "[30,…
$ ventas_categoria <chr> "Alto", "Bajo", "Alto", "Medio", "Alto", "Medio",…
Numerización de atributos categóricos
Convertiremos algunas variables categóricas en numéricas:
# Convertiremos algunas variables categóricas en numéricas:
ventas_ecuador_transformado <- ventas_ecuador_transformado |>
mutate(
ciudad_numerica = as.numeric(factor(ciudad)),
promocion_numerica = as.numeric(promocion)
)
# Verificamos el resultado
glimpse(ventas_ecuador_transformado)Rows: 2,196
Columns: 16
$ fecha <date> 2019-01-01, 2019-01-02, 2019-01-03, 2019-01-04, …
$ ciudad <chr> "Quito", "Guayaquil", "Guayaquil", "Quito", "Mant…
$ producto <chr> "Leche", "Atún", "Verduras", "Carne", "Atún", "At…
$ ventas <dbl> 399.26, 75.36, 414.08, 250.49, 313.35, 235.28, 11…
$ unidades_vendidas <int> 49, 31, 34, 19, 31, 37, 42, 20, 42, 37, 19, 46, 6…
$ edad_cliente <int> 24, 24, 45, 53, 34, 39, 18, 45, 59, 36, 32, 42, 1…
$ promocion <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, T…
$ satisfaccion_cliente <int> 2, 4, 5, 4, 3, 4, 4, 1, 2, 3, 5, 1, 4, 2, 5, 4, 1…
$ tasa_devolucion <dbl> 0.08, 0.09, 0.02, 0.04, 0.02, 0.03, 0.07, 0.01, 0…
$ region <chr> "Sierra", "Costa", "Costa", "Sierra", "Costa", "C…
$ edad_grupo <fct> Joven, Joven, Adulto, Adulto, Adulto joven, Adult…
$ categoria_producto <chr> "Alimentos básicos", "Alimentos básicos", "Produc…
$ edad_rango <fct> "[20,30)", "[20,30)", "[40,50)", "[50,60)", "[30,…
$ ventas_categoria <chr> "Alto", "Bajo", "Alto", "Medio", "Alto", "Medio",…
$ ciudad_numerica <dbl> 5, 2, 2, 5, 3, 3, 4, 3, 1, 1, 3, 2, 2, 4, 2, 2, 2…
$ promocion_numerica <dbl> 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0…
Normalización de atributos numéricos
### Normalización de atributos numéricos
ventas_ecuador_transformado <- ventas_ecuador_transformado |>
mutate(
ventas_normalizadas = as.vector(scale(ventas)),
unidades_vendidas_normalizadas = as.vector(scale(unidades_vendidas))
)
# Verificamos el resultado
glimpse(ventas_ecuador_transformado)Rows: 2,196
Columns: 18
$ fecha <date> 2019-01-01, 2019-01-02, 2019-01-03, 20…
$ ciudad <chr> "Quito", "Guayaquil", "Guayaquil", "Qui…
$ producto <chr> "Leche", "Atún", "Verduras", "Carne", "…
$ ventas <dbl> 399.26, 75.36, 414.08, 250.49, 313.35, …
$ unidades_vendidas <int> 49, 31, 34, 19, 31, 37, 42, 20, 42, 37,…
$ edad_cliente <int> 24, 24, 45, 53, 34, 39, 18, 45, 59, 36,…
$ promocion <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE…
$ satisfaccion_cliente <int> 2, 4, 5, 4, 3, 4, 4, 1, 2, 3, 5, 1, 4, …
$ tasa_devolucion <dbl> 0.08, 0.09, 0.02, 0.04, 0.02, 0.03, 0.0…
$ region <chr> "Sierra", "Costa", "Costa", "Sierra", "…
$ edad_grupo <fct> Joven, Joven, Adulto, Adulto, Adulto jo…
$ categoria_producto <chr> "Alimentos básicos", "Alimentos básicos…
$ edad_rango <fct> "[20,30)", "[20,30)", "[40,50)", "[50,6…
$ ventas_categoria <chr> "Alto", "Bajo", "Alto", "Medio", "Alto"…
$ ciudad_numerica <dbl> 5, 2, 2, 5, 3, 3, 4, 3, 1, 1, 3, 2, 2, …
$ promocion_numerica <dbl> 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, …
$ ventas_normalizadas <dbl> 0.98103386, -1.55064828, 1.09687062, -0…
$ unidades_vendidas_normalizadas <dbl> 1.61173920, 0.36715878, 0.57458885, -0.…
Reducción de dimensionalidad
Para este ejemplo, no realizaremos reducción de dimensionalidad, pero en un conjunto de datos más grande, podríamos utilizar técnicas como PCA (Análisis de Componentes Principales).
Aumento de dimensionalidad
Crearemos algunas características nuevas. Esto es crear mes, dia, si es fin de semana y ventas por unidad a partir de variables disponibles en el conjunto de datos.
ventas_ecuador_transformado <- ventas_ecuador_transformado |>
mutate(
mes = month(fecha),
dia_semana = wday(fecha),
es_fin_semana = ifelse(dia_semana %in% c(1, 7), 1, 0),
ventas_por_unidad = ventas / unidades_vendidas
)
glimpse(ventas_ecuador_transformado)Rows: 2,196
Columns: 22
$ fecha <date> 2019-01-01, 2019-01-02, 2019-01-03, 20…
$ ciudad <chr> "Quito", "Guayaquil", "Guayaquil", "Qui…
$ producto <chr> "Leche", "Atún", "Verduras", "Carne", "…
$ ventas <dbl> 399.26, 75.36, 414.08, 250.49, 313.35, …
$ unidades_vendidas <int> 49, 31, 34, 19, 31, 37, 42, 20, 42, 37,…
$ edad_cliente <int> 24, 24, 45, 53, 34, 39, 18, 45, 59, 36,…
$ promocion <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE…
$ satisfaccion_cliente <int> 2, 4, 5, 4, 3, 4, 4, 1, 2, 3, 5, 1, 4, …
$ tasa_devolucion <dbl> 0.08, 0.09, 0.02, 0.04, 0.02, 0.03, 0.0…
$ region <chr> "Sierra", "Costa", "Costa", "Sierra", "…
$ edad_grupo <fct> Joven, Joven, Adulto, Adulto, Adulto jo…
$ categoria_producto <chr> "Alimentos básicos", "Alimentos básicos…
$ edad_rango <fct> "[20,30)", "[20,30)", "[40,50)", "[50,6…
$ ventas_categoria <chr> "Alto", "Bajo", "Alto", "Medio", "Alto"…
$ ciudad_numerica <dbl> 5, 2, 2, 5, 3, 3, 4, 3, 1, 1, 3, 2, 2, …
$ promocion_numerica <dbl> 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, …
$ ventas_normalizadas <dbl> 0.98103386, -1.55064828, 1.09687062, -0…
$ unidades_vendidas_normalizadas <dbl> 1.61173920, 0.36715878, 0.57458885, -0.…
$ mes <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
$ dia_semana <dbl> 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 1, …
$ es_fin_semana <dbl> 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, …
$ ventas_por_unidad <dbl> 8.148163, 2.430968, 12.178824, 13.18368…
Actividad calificada
La actividad tiene como objetivo aplicar los conceptos aprendidos en esta guía práctica sobre el uso de R y RStudio.
Instrucciones
Crear un nuevo proyecto en RStudio con el nombre
Mineria_Datos. Dentro del proyecto, organizar las carpetas de la siguiente manera:Scripts: Para almacenar tus scripts de código R.
Datos: Para almacenar cualquier dataset que utilice en la actividad.
Dentro del directorio Scripts, crear un script R llamado
etapas_1_2_3.R. En este script, codificar las siguientes tareas:Realizar el EDA de acuerdo con el tipo de variable de los siguientes casos:
- tasa_devolucion
- region
- edad_grupo
- edad_rango
- ventas_categoria
- ciudad_numerica
- promocion_numerica
- ventas_normalizadas
- unidades_vendidas_normalizadas
- mes
- dia_semana
- es_fin_semana
- ventas_por_unidad
Basándonse en este EDA, responda las preguntas:
- ¿Cuál es la tasa de devolución promedio y cómo se distribuye?
- ¿Qué región tiene el mayor número de ventas?
- ¿Cómo se distribuyen las ventas entre los diferentes grupos de edad?
- ¿Qué mes del año tiene las ventas más altas?
- ¿Cómo se comportan las ventas los fines de semana?
Documentación:
- Agregar comentarios al código explicando la funcionalidad de cada sección. Incluir al inicio del script información sobre carrera e integrantes del grupo. Además, información sobre lo que hace el código y cómo se usa.
Entrega
Una vez completada la actividad, subir comprimir el proyecto en formato .zip con el nombre MD_Practica_2_GrupoX. Donde X debe ser reemplazado por su número de grupo.
Criterios de Evaluación
Estructura y organización del proyecto (10%).
Implementación correcta del EDA y respuestas a preguntas (80%).
Documentación, formato de entrega y comentarios en el código (10%).