En este proyecto se pretende dar información a un negocio dedicado a la compra-venta de productos alimentarios y que inició su andadura comercial en Abril de 2020; los últimos datos a analizar son del 05/06/2022.
Se realizarán tareas de ETL, creación de nuevas variables de negocio y visualización de insights.
knitr::opts_chunk$set(echo = TRUE)
Vamos a realizar el proceso de ETL directamente en la BBDD de SQL Server:
Obtención de las tablas de la BBDD:
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
TABLE_NAME |
---|
PaisCosto |
PaisIngresos |
Empleados |
Proveedores |
Categorias |
Productos |
Clientes |
Transportistas |
Ordenes |
DetalleOrden |
Visualización de la informacion de las tablas de la BBDD para obtener las tablas que necesitamos:
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, IS_NULLABLE
FROM INFORMATION_SCHEMA.COLUMNS
TABLE_NAME | COLUMN_NAME | DATA_TYPE | IS_NULLABLE |
---|---|---|---|
PaisCosto | pais | nvarchar | NO |
PaisCosto | periodo | nvarchar | YES |
PaisCosto | costo | money | YES |
PaisIngresos | pais | nvarchar | NO |
PaisIngresos | periodo | nvarchar | YES |
PaisIngresos | ingreso | money | YES |
fctVentas | fecha_orden | date | NO |
fctVentas | id_orden | int | NO |
fctVentas | id_cliente | int | YES |
fctVentas | id_producto | int | NO |
SELECT * FROM RRHH.Empleados
id_empleado | apellido | nombre | titulo | titulocortesia | fecha_nac | fecha_contrato | direccion | ciudad | region | codigopostal | pais | telefono | id_jefe |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | García | María | Director Ejecutivo | Sra. | 1968-12-08 | 2020-05-01 | 7890 - 20th Ave. E., Apt. 2A | Seattle | WA | 10003 | USA | (206) 555-0101 | NA |
2 | Martínez | Juan | Vicepresidente de Ventas | Dr. | 1972-02-19 | 2020-08-14 | 9012 W. Capital Way | Tacoma | WA | 10001 | USA | (206) 555-0100 | 1 |
3 | López | Ana | Gerente de Ventas | Sra. | 1983-08-30 | 2020-04-01 | 2345 Moss Bay Blvd. | Kirkland | WA | 10007 | USA | (206) 555-0103 | 2 |
4 | Fernández | José | Representante de Ventas | Sr. | 1957-09-19 | 2021-05-03 | 5678 Old Redmond Rd. | Redmond | WA | 10009 | USA | (206) 555-0104 | 3 |
5 | Díaz | Luis | Gerente de Ventas | Sr. | 1975-03-04 | 2021-10-17 | 8901 Garrett Hill | Londres | NA | 10004 | Reino Unido | (71) 234-5678 | 2 |
6 | González | Laura | Representante de Ventas | Sra. | 1983-07-02 | 2021-10-17 | 3456 Coventry House, Miner Rd. | Londres | NA | 10005 | Reino Unido | (71) 345-6789 | 5 |
7 | Ruíz | Carlos | Representante de Ventas | Sr. | 1980-05-29 | 2022-01-02 | 6789 Edgeham Hollow, Winchester Way | Londres | NA | 10002 | Reino Unido | (71) 123-4567 | 5 |
8 | Hernández | María | Representante de Ventas | Sra. | 1978-01-09 | 2022-03-05 | 4567 - 11th Ave. N.E. | Seattle | WA | 10006 | USA | (206) 555-0102 | 3 |
9 | Pérez | Patricia | Representante de Ventas | Sra. | 1986-01-27 | 2022-11-15 | 1234 Houndstooth Rd. | Londres | NA | 10008 | Reino Unido | (71) 456-7890 | 5 |
Despúes de visualizar las tablas, comenzamos a realizar las transformaciones con las tablas necesarias para el analisis; para ello usaremos: - Transformaciones iniciales usando el conector odbc para realizar manipulaciones de tablas directamente desde el servidor. - transformaciones intermedias con la libreria dplyr. - Transformaciones finales con sqldf.
WITH T1 AS
(
SELECT id_empleado,
CONCAT(nombre, ' ', apellido) AS nombre_empleado,
titulo,
DATEDIFF(YEAR, fecha_nac, '20220506') AS edad_empleado,
DATEDIFF(DAY, fecha_contrato, '20220506') AS dias_antig_empleado,
ciudad AS ciudad_empleado,
pais AS pais_empleado
FROM RRHH.Empleados
)
SELECT id_empleado,
nombre_empleado,
titulo,
edad_empleado,
CASE WHEN dias_antig_empleado < 0 THEN 1
ELSE dias_antig_empleado END AS antiguedad_rev,
ciudad_empleado,
pais_empleado
FROM T1
ORDER BY antiguedad_rev DESC
empleados_x_antiguedad
Visualizamos la antiguedad de los empleados de mayor a menor agrupados por el pais del empleado:
library(ggplot2)
library(plotly)
##
## Adjuntando el paquete: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
p_empleados_x_antiguedad <- ggplot(empleados_x_antiguedad) +
aes(x = antiguedad_rev, y = reorder(nombre_empleado, antiguedad_rev), fill = titulo) +
geom_bar(stat = "summary", fun = "sum") +
scale_fill_hue(direction = 1) +
theme_minimal() +
facet_wrap(vars(pais_empleado)) +
labs(x = "Antigüedad", title = "Antigüedad de Empleados por País y Título") +
theme(
axis.text.y = element_text(size = 8),
legend.position = "none",
axis.title.y = element_blank(),
plot.title = element_text(hjust = 0.2)
)
plotly_empleados_x_antiguedad <- ggplotly(p_empleados_x_antiguedad)
plotly_empleados_x_antiguedad
Visualización múltiple de la antiguedad del empleado, por empleados, titulo y edad, ordenados por la antiguedad del empleado:
library(ggplot2)
library(plotly)
p_empleados_x_antiguedad2 <- ggplot(empleados_x_antiguedad) +
aes(x = antiguedad_rev, y = reorder(nombre_empleado, antiguedad_rev), fill = titulo) +
geom_bar(stat = "summary", fun = "sum") +
scale_fill_hue(direction = 1) +
theme_minimal() +
facet_wrap(vars(edad_empleado)) +
labs(x = "Antigüedad", title = "Antigüedad de Empleados por País, Título y Edad") +
theme(
legend.position = "none",
axis.title.y = element_blank()
)
plotly_empleados_x_antiguedad2 <- ggplotly(p_empleados_x_antiguedad2)
plotly_empleados_x_antiguedad2
Para la creación de la tabla de Productos tenemos que combinar varias tablas:
SELECT T1.id_producto,
T1.nombreproducto,
T2.nombrecategoria,
T2.descripcion AS desc_categ,
T3.nombreproveedor,
T3.ciudad AS ciudad_proveedor,
T3.pais AS pais_proveedor
FROM Produccion.Productos T1
LEFT JOIN Produccion.Categorias T2
ON T1.id_categoria = T2.id_categoria
LEFT JOIN Produccion.Proveedores T3
ON T1.id_proveedor = T3.id_proveedor
productos
SELECT T1.id_orden,
CONCAT (T4.nombre,' ', T4.apellido) AS nomb_empleado,
T1.fecha_orden AS fecha_pedido,
T1.fecha_requerida AS fecha_entrega,
T1.flete,
T1.ciudadtransporte AS ciudad_envio,
T1.paistransporte AS pais_envio,
T2.nombrecliente,
T2.ciudad AS ciudad_cliente,
T2.pais AS pais_cliente,
T3.compañia AS transportista
FROM ventas.Ordenes T1
LEFT JOIN ventas.Clientes T2
ON T1.id_cliente = T2.id_cliente
LEFT JOIN ventas.Transportistas T3
ON T1.id_transportista = T3.id_transportista
LEFT JOIN RRHH.Empleados T4
ON T1.id_empleado = T4.id_empleado
library(dplyr)
##
## Adjuntando el paquete: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
glimpse(pedidos)
## Rows: 830
## Columns: 11
## $ id_orden <int> 10248, 10249, 10250, 10251, 10252, 10253, 10254, 10255,…
## $ nomb_empleado <chr> "Luis Díaz", "Laura González", "José Fernández", "Ana L…
## $ fecha_pedido <date> 2020-07-04, 2020-07-05, 2020-07-08, 2020-07-08, 2020-0…
## $ fecha_entrega <date> 2020-08-01, 2020-08-16, 2020-08-05, 2020-08-05, 2020-0…
## $ flete <dbl> 32.38, 11.61, 65.83, 41.34, 51.30, 58.17, 22.98, 148.33…
## $ ciudad_envio <chr> "Reims", "Münster", "Rio de Janeiro", "Lyon", "Charlero…
## $ pais_envio <chr> "Francia", "Alemania", "Brasil", "Francia", "Bélgica", …
## $ nombrecliente <chr> "Cliente ENQZT", "Cliente FAPSM", "Cliente IBVRG", "Cli…
## $ ciudad_cliente <chr> "Reims", "Münster", "Rio de Janeiro", "Lyon", "Charlero…
## $ pais_cliente <chr> "Francia", "Alemania", "Brasil", "Francia", "Bélgica", …
## $ transportista <chr> "Transportista ZHISN", "Transportista GVSUA", "Transpor…
SELECT T1.id_orden,
T1.id_producto,
T1.preciounitario AS pcu,
T2.preciounitario AS pvu,
T1.cantidad,
T1.descuento
FROM Ventas.DetalleOrden T1
LEFT JOIN Produccion.Productos T2
ON T1.id_producto = T2.id_producto
detalle_orden
MANIPULACION INTERMEDIA ENTRE DOS VARIABLES:
library(dplyr)
# Suponiendo que ya has obtenido ambos dataframes:
# 'detalle_orden' y 'productos' son los resultados de las consultas
detalle_pdos <- detalle_orden %>%
left_join(productos, by = "id_producto")
# Visualiza los primeros registros de la unión
detalle_pdos
MANIPULACIONES FINALES PARA CALIDAD DE DATOS:
summary(pedidos) # El rango máximo de la variable 'flete' es muy alto y se revisará por posibles valores atípicos.
## id_orden nomb_empleado fecha_pedido fecha_entrega
## Min. :10248 Length:830 Min. :2020-07-04 Min. :2020-07-24
## 1st Qu.:10455 Class :character 1st Qu.:2021-02-24 1st Qu.:2021-03-25
## Median :10662 Mode :character Median :2021-09-09 Median :2021-10-07
## Mean :10662 Mean :2021-08-08 Mean :2021-09-05
## 3rd Qu.:10870 3rd Qu.:2022-02-04 3rd Qu.:2022-03-05
## Max. :11077 Max. :2022-05-06 Max. :2022-06-11
## flete ciudad_envio pais_envio nombrecliente
## Min. : 0.02 Length:830 Length:830 Length:830
## 1st Qu.: 13.38 Class :character Class :character Class :character
## Median : 41.36 Mode :character Mode :character Mode :character
## Mean : 78.24
## 3rd Qu.: 91.43
## Max. :1007.64
## ciudad_cliente pais_cliente transportista
## Length:830 Length:830 Length:830
## Class :character Class :character Class :character
## Mode :character Mode :character Mode :character
##
##
##
summary(detalle_pdos) # Los rangos máximos de las variables 'pcu','pvu' y 'cantidad' son elevados y se revisarán por posibles valores atípicos.
## id_orden id_producto pcu pvu
## Min. :10248 Min. : 1.00 Min. : 2.00 Min. : 2.50
## 1st Qu.:10451 1st Qu.:22.00 1st Qu.: 12.00 1st Qu.: 12.50
## Median :10657 Median :41.00 Median : 18.40 Median : 19.45
## Mean :10659 Mean :40.79 Mean : 26.22 Mean : 27.95
## 3rd Qu.:10862 3rd Qu.:60.00 3rd Qu.: 32.00 3rd Qu.: 34.00
## Max. :11077 Max. :77.00 Max. :263.50 Max. :263.50
## cantidad descuento nombreproducto nombrecategoria
## Min. : 1.00 Min. :0.00000 Length:2155 Length:2155
## 1st Qu.: 10.00 1st Qu.:0.00000 Class :character Class :character
## Median : 20.00 Median :0.00000 Mode :character Mode :character
## Mean : 23.81 Mean :0.05617
## 3rd Qu.: 30.00 3rd Qu.:0.10000
## Max. :130.00 Max. :0.25000
## desc_categ nombreproveedor ciudad_proveedor pais_proveedor
## Length:2155 Length:2155 Length:2155 Length:2155
## Class :character Class :character Class :character Class :character
## Mode :character Mode :character Mode :character Mode :character
##
##
##
library(ggplot2)
ggplot(pedidos) +
aes(x = flete, fill = nomb_empleado) +
geom_density() +
scale_fill_hue(direction = 1) +
theme_minimal() +
facet_wrap(vars(nomb_empleado)) +
labs( title = "Flete facturado por Empleado") +
theme(legend.position = "none",
plot.title = element_text(hjust = 0.5)
)
library(sqldf)
## Cargando paquete requerido: gsubfn
## Cargando paquete requerido: proto
## Cargando paquete requerido: RSQLite
rev_flete <- sqldf("
WITH inicial AS
(
SELECT
T1.nomb_empleado,
T1.nombrecliente,
T1.flete,
AVG (T1.flete) OVER() AS media_flete,
AVG (T1.flete) OVER (PARTITION BY T1.nombrecliente) AS media_flete_x_cliente
FROM pedidos T1
GROUP BY T1.nomb_empleado,
T1.nombrecliente
),
tabla1 AS
(
SELECT *,
flete - media_flete_x_cliente AS diferencia
FROM inicial
ORDER BY diferencia ASC
)
SELECT nomb_empleado,
media_flete,
SUM(diferencia) OVER (PARTITION BY nomb_empleado) dif_x_empleado
FROM tabla1
GROUP BY nomb_empleado,
media_flete
ORDER BY dif_x_empleado ASC
")
rev_flete
Podemos concluir que los empleados concentran los fletes facturados en importes pequeños y facturan algunos fletes elevados (atípicos); incluso así, podemos observar que el total de flete que factura cada empleado tiene un desvio negativo con respecto a la media de flete general.
library(ggplot2)
ggplot(detalle_pdos) +
aes(x = "", y = pcu, fill = nombreproveedor) +
geom_boxplot() +
scale_fill_hue(direction = 1) +
coord_flip() +
theme_minimal() +
theme(legend.position = "none", plot.title = element_text(hjust = 0.5)) +
facet_wrap(vars(nombreproveedor)) +
labs(title = "Precio unitario de producto por Proveedor")
Existen algunos precios atipicos en algunos proveedores, el departamento
de compras revisará en consecuencia.
Añadimos nuevas variables a los dataset ‘pedidos’ y ‘detalle_pdos’
-- Precalculos en SQL con nuevas variables a graficar
WITH inicial AS (
SELECT T1.id_orden,
CONCAT(T4.nombre, ' ', T4.apellido) AS nomb_empleado,
T1.fecha_orden AS fecha_pedido,
T1.fecha_requerida AS fecha_entrega,
T1.flete,
T1.ciudadtransporte AS ciudad_envio,
T1.paistransporte AS pais_envio,
T2.nombrecliente,
T2.ciudad AS ciudad_cliente,
T2.pais AS pais_cliente,
T3.compañia AS transportista
FROM ventas.Ordenes T1
LEFT JOIN ventas.Clientes T2 ON T1.id_cliente = T2.id_cliente
LEFT JOIN ventas.Transportistas T3 ON T1.id_transportista = T3.id_transportista
LEFT JOIN RRHH.Empleados T4 ON T1.id_empleado = T4.id_empleado
), acum AS
(
SELECT *,
SUM(flete) OVER (ORDER BY fecha_pedido) AS imp_flete_acum,
AVG(flete) OVER (ORDER BY fecha_pedido) AS avg_flete_acum,
SUM(flete) OVER (PARTITION BY nombrecliente ORDER BY fecha_pedido) AS flete_acum_cliente,
AVG(flete) OVER (PARTITION BY nombrecliente ORDER BY fecha_pedido) AS avg_flete_cliente,
COUNT(id_orden) OVER (PARTITION BY nombrecliente ORDER BY fecha_pedido) AS pedidos_acum_cliente,
SUM(flete) OVER (PARTITION BY pais_envio ORDER BY fecha_pedido) AS flete_acum_pais,
SUM(flete) OVER (PARTITION BY ciudad_cliente ORDER BY fecha_pedido) AS flete_acum_ciudad,
LAG(fecha_pedido, 0) OVER (PARTITION BY nombrecliente ORDER BY fecha_pedido) AS fecha_pdo_anterior
FROM inicial
)
SELECT
*,
DATEDIFF (DAY, fecha_pdo_anterior, fecha_pedido) AS dias_entre_pdos
FROM
acum
acumulados