Exploratory Data Analisis

Para este trabajo se escogió un database de Ventas de Chocolate, con venta en dólares. Descargado en Kaggle. https://www.kaggle.com/datasets/atharvasoundankar/chocolate-sales

1. Explorando los datos

2.1 Lectura de Chocolate Sales Dataset.

df <- read.csv("Chocolate_Sales.csv")

1.2 Usando Head

head(df, 3)
##     Sales.Person Country             Product      Date  Amount Boxes.Shipped
## 1 Jehu Rudeforth      UK     Mint Chip Choco 04-Jan-22 $5,320            180
## 2    Van Tuxwell   India       85% Dark Bars 01-Aug-22 $7,896             94
## 3   Gigi Bohling   India Peanut Butter Cubes 07-Jul-22 $4,501             91

1.3 Usando Tail

tail(df, 3)
##          Sales.Person Country             Product      Date  Amount
## 1092     Ches Bonnell  Canada Organic Choco Syrup 26-Jul-22   $574 
## 1093   Dotty Strutley   India             Eclairs 28-Jul-22 $2,086 
## 1094 Karlen McCaffrey   India      70% Dark Bites 23-May-22 $5,075 
##      Boxes.Shipped
## 1092           217
## 1093           384
## 1094           344

2. Estructura del DataSet

2.1 Usando summary

Genera un resumen estadístico de cada variable. Para variables numéricas, muestra la media, mediana, mínimo, máximo y cuartiles.

Para variables categóricas, muestra la frecuencia de cada categoría.

summary(df)
##  Sales.Person         Country            Product              Date          
##  Length:1094        Length:1094        Length:1094        Length:1094       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##     Amount          Boxes.Shipped  
##  Length:1094        Min.   :  1.0  
##  Class :character   1st Qu.: 70.0  
##  Mode  :character   Median :135.0  
##                     Mean   :161.8  
##                     3rd Qu.:228.8  
##                     Max.   :709.0

2.2 Usando str

Para ver la estructura de mis datos, puedo hacerlo con str.

Acá debemos de tener en cuenta los tipos de datos que tiene cada columna ya que algunos se tendrán que transformar para su uso estadístico.

str(df)
## 'data.frame':    1094 obs. of  6 variables:
##  $ Sales.Person : chr  "Jehu Rudeforth" "Van Tuxwell" "Gigi Bohling" "Jan Morforth" ...
##  $ Country      : chr  "UK" "India" "India" "Australia" ...
##  $ Product      : chr  "Mint Chip Choco" "85% Dark Bars" "Peanut Butter Cubes" "Peanut Butter Cubes" ...
##  $ Date         : chr  "04-Jan-22" "01-Aug-22" "07-Jul-22" "27-Apr-22" ...
##  $ Amount       : chr  "$5,320 " "$7,896 " "$4,501 " "$12,726 " ...
##  $ Boxes.Shipped: int  180 94 91 342 184 38 176 73 59 102 ...

En la estructura anterior, se puede apreciar que en _*Amount se muestra como char, entonces a continuación procederemos a eliminar el signo de dólar($), la coma(,) y convertirlo en número, para poder trabajar con los datos.

Para lograrlo se investigó la función gsub, que significa “Global Substitution”, y se usa para reemplazar patrones dentro de un string.

Formula: gsub(pattern, replacement, x) donde x = a la columna del df. En este caso df$Amount.

El patrón es una expresión regular, “[\$, ]”, y esto se reemplaza por -> “” que no tiene nada.

Finalmente, se convierte en numero con la función as.numeric()

df$Amount <- as.numeric(gsub("[\\$, ]", "", df$Amount))
summary(df)
##  Sales.Person         Country            Product              Date          
##  Length:1094        Length:1094        Length:1094        Length:1094       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##      Amount      Boxes.Shipped  
##  Min.   :    7   Min.   :  1.0  
##  1st Qu.: 2390   1st Qu.: 70.0  
##  Median : 4868   Median :135.0  
##  Mean   : 5652   Mean   :161.8  
##  3rd Qu.: 8027   3rd Qu.:228.8  
##  Max.   :22050   Max.   :709.0

2.3 Conversión a Factor

Para poder usar Sales.Person de mejor manera, lo hemos convertido a factor, y podemos ver determinar a simple vista un top de mejores vendedores, aunque los datos no están ordenados de mayor a menor.

df$Sales.Person <- as.factor(df$Sales.Person)
sort(table(df$Sales.Person), decreasing = TRUE)
## 
##       Kelci Walkden         Brien Boise         Van Tuxwell      Beverie Moffet 
##                  54                  53                  51                  50 
## Dennison Crosswaite          Oby Sorrel        Ches Bonnell        Gigi Bohling 
##                  49                  49                  48                  47 
##    Karlen McCaffrey      Curtice Advani         Kaine Padly     Madelene Upcott 
##                  47                  46                  45                  45 
##      Marney O'Breen        Barr Faughny     Gunar Cockshoot      Jehu Rudeforth 
##                  45                  43                  43                  43 
##     Roddy Speechley      Mallorie Waber      Andria Kimpton        Jan Morforth 
##                  43                  41                  39                  39 
##        Husein Augar      Dotty Strutley Rafaelita Blaksland      Wilone O'Kielt 
##                  38                  36                  34                  34 
##      Camilla Castle 
##                  32

Acá convertimos Country a factor. Mas adelante veremos un gráfico para observar visualmente el país con mayor venta

country_factor <- as.factor(df$Country)
sort(table(country_factor), decreasing = TRUE)
## country_factor
##   Australia       India         USA          UK      Canada New Zealand 
##         205         184         179         178         175         173

3. Métricas Estadísticas

Vamos a explorar el promedio, la media.

Acá estamos usando num_cols para almacenar todos los rows de las columnas que solo son numéricas con sapply.

num_cols <- df[, sapply(df, is.numeric)]
head(num_cols, 5)
##   Amount Boxes.Shipped
## 1   5320           180
## 2   7896            94
## 3   4501            91
## 4  12726           342
## 5  13685           184

3.2 Mínimos

Acá vamos aplicar el min para num_cols

min_values <- apply(num_cols, 2, min, na.rm = TRUE)
min_values
##        Amount Boxes.Shipped 
##             7             1

3.3 Máximos

Lo mismo para el max en num_cols

max_values <- apply(num_cols, 2, max, na.rm = TRUE)
max_values
##        Amount Boxes.Shipped 
##         22050           709

3.4 Promedios

Aplicaremos la misma fórmula para revisar los promedios

mean_values <- apply(num_cols, 2, mean, na.rm = TRUE)
mean_values
##        Amount Boxes.Shipped 
##      5652.308       161.798

3.5 Mediana

Con la mediana verificaremos que tantos valores atípicos tiene el promedio de datos

median_values <- apply(num_cols, 2, median, na.rm = TRUE)
median_values
##        Amount Boxes.Shipped 
##        4868.5         135.0

3.6 Desviación Estándar (DE)

Revisaremos si tenemos una DE alta o baja con respecto al promedio

sd_values <- apply(num_cols, 2, sd, na.rm = TRUE)
sd_values
##        Amount Boxes.Shipped 
##     4102.4420      121.5441

3.7 Correlacion entre Amount y Box shipped

cor(df$Amount, df$Boxes.Shipped)
## [1] -0.01882685

4. Análisis de las Variables Amount y Box shipped

No existe una correlación entre Amount y Boxes.Shipped. Las ventas no dependen de la cantidad de cajas enviadas.

4.1 Análisis Amount

  • La diferencia entre el valor min_values y el max_values}}` para Amount es muy significativa, lo que indica que hay amplia variabilidad en los montos de las ventas.
  • La median_values < mean_values lo que quiere decir que hay valores muy altos que llevan la media hacia arriba.
  • La sd_values es bastante alta, lo que confirma la alta variabilidad en los montos de las ventas.

4.2 Análisis Box Shipped

  • La diferencia entre el valor min_values y el max_values para Box Shipped es muy significativa, lo que indica que hay amplia variabilidad en la cantidad de productos enviados.
  • La median_values < mean_values pero no es una diferencia tan pronunciada con en Amount.
  • La sd_values es considerablemente alta, lo que confirma la variabilidad en la cantidad de cajas enviadas.

5. Visualizaciones

Los gráficos que vamos a utilizar en este caso serán:

  • Scatterplot: Grafico de dispersión para visualizar la relación entre Amount y Boxes.Shipped y ver gráficamente lo que vimos en números anteriores.
  • Boxplot: Para ver los valores atípicos de Amount y Boxes.Shipped.
  • Bar: gráficos en los que veremos rankings de mejores y peores ventas, vendedores, países.
  • Line: Con este podremos visualizar cuando se vendió más.

librerías a utilizar

library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.4.3
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.4.3
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(lubridate)
## Warning: package 'lubridate' was built under R version 4.4.3
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union

5.1 Scatterplot

Acá mostraremos la distribución de los puntos de datos.

  • La línea roja muestra la tendencia lineal entre las variables. No hay regresión lineal.
  • Los puntos que se alejan mucho de la línea de regresión podrían ser valores atípicos.
ggplot(df, aes(x=Boxes.Shipped, y = Amount, color=Country))+
  geom_point(size=1, alpha = 0.6) + 
  geom_smooth(method = "lm", se = FALSE, color = "#ff5959") +
  labs(title = "Relacion entre Amount y Boxes.Shipped",
       x = "Boxes Shipped",
       y = "Amount (USD)") +
  scale_y_continuous(labels = scales::label_dollar()) +
  theme_minimal() #+
## `geom_smooth()` using formula = 'y ~ x'

5.2 Boxplot

Como la DE es larga, habían sospechas de outliers, acá podemos identificar esos valores atípicos visualmente.

ggplot(df, aes(x = Amount)) +
  geom_boxplot(fill="lightblue") +
  scale_x_continuous(labels = scales::label_dollar()) +
  labs(title = "Boxplot per Amount (USD)",
       x = "Amount (USD)",
       y = "Frequency")

ggplot(df, aes(x = Boxes.Shipped)) +
  geom_boxplot(fill="lightblue") +
  labs(title = "Boxplot Boxes shipped",
       x = "Boxes Shipped",
       y = "Frequency")

5.3 Bar

Acá veremos los distintos usos del Grafico de Barras, funciona muy bien para rankings

5.3.1 Rankings

Para el *new_topN usamos “%>%” que es el pipe operator** del paquete dplyr que nos ayuda a concatenar varias operaciones. Entonces pasamos df, a la siguiente función de la cadena.

Arrange() se utiliza para reordenar las filas de un data frame basándose en los valores de una o más columnas. Mientras que desc() nos ordena de mayor a menor.

Para un Top N usaremos esta función la cual podremos utilizar a través de nuestro EDA y podremos definir si es un ranking más o menos ventas con head o tail.

new_topN <- function(topN, rank = "head") {
  df %>%
    group_by(Product) %>%               # Group by Product
    summarize(Amount = sum(Amount)) %>%  # Sum sales for each product
    arrange(desc(Amount)) %>%            # Sort in descending order
    { if (rank == "head") head(., topN) else tail(., topN) }  # Apply head or tail dynamically
}

5.3.1.1 Top 10 de productos más vendidos

A continuación se presenta un gráfico de barras, con el fin de mostrar el top 20 de los productos más vendidos.

Aquí usamos la función *scale_x_continuous** para darle formato a de dólares al eje X

ggplot(new_topN(10, "head"), aes(x = Amount, y = reorder(Product, Amount), fill = Product)) +
  geom_bar(stat = "identity") +
  geom_text(aes(label = scales::label_dollar()(Amount)),  
            hjust = 1.2, color = "white", size = 4, fontface = "bold") +  
  scale_x_continuous(labels = scales::label_dollar()) +
  labs(title = "Top 10 de Mayores Ingresos Generados por Producto", 
       x = "Amount (USD)", y = "Product") +
  theme_minimal()

5.3.1.2 Top 5 producto menos vendido

Acá se presenta un gráfico de barras, mostrando el top 5 de los productos menos vendidos.

ggplot(new_topN(5, "tail"), aes(x = Amount, y = reorder(Product, Amount), fill = Product)) +
  geom_bar(stat = "identity") +
  geom_text(aes(label = scales::label_dollar()(Amount)),  # Add formatted labels 
            hjust = 1.2, color = "white", size = 4, fontface = "bold") +  # Adjust label position & color
  scale_x_continuous(labels = scales::label_dollar()) +
  labs(title = "Top 5 de Menores Ingresos Generados por Producto", x = "Amount (USD)", y = "Product") +
  theme_minimal()

5.3.1.3 Top 10 de los Mejores Vendedores

sales_rate <- df %>%
  group_by(Sales.Person) %>%
  summarize(Total_Amount = sum(Amount)) %>%
  arrange(desc(Total_Amount)) %>%  # Sort from highest to lowest
  slice(1:10)

ggplot(sales_rate, aes(x = reorder(Sales.Person, Total_Amount), y = Total_Amount)) +
  geom_bar(stat = "identity", fill = "#4682B4") +
  geom_text(aes(label = scales::label_dollar()(Total_Amount)),  # Add formatted labels 
          hjust = 0.5, vjust = 2, color = "white", size = 3, fontface = "bold") +
  scale_y_continuous(labels = scales::label_dollar()) +
  labs(title = "Top 10 de los mejores Vendedores", x = "Sales Person", y = "Amount (USD)") +
  scale_fill_viridis_d(option = "C") +  # "C" is a blueish Viridis scale
  theme_minimal() +
  theme(axis.text.x = element_text(angle=45, hjust = 1))

5.3.2 Productos vendidos por País

country_sales <- df %>%
  group_by(Country) %>%
  summarize(Total_Amount = sum(Amount)) %>%
  arrange(desc(Total_Amount))  # Sort from highest to lowest

ggplot(country_sales, aes(x = Total_Amount, y = reorder(Country, Total_Amount))) +
  geom_bar(stat = "identity", fill = "#4682B4") +
  geom_text(aes(label = scales::label_dollar()(Total_Amount)),  # Add formatted labels 
          hjust = 1.2, color = "white", size = 4, fontface = "bold") +
  scale_x_continuous(labels = scales::label_dollar()) +
  labs(title = "Pais con mayor venta de Chocolates", x = "Country", y = "Amount (USD)") +
  scale_fill_viridis_d(option = "C") +  # "C" is a blueish Viridis scale
  theme_minimal()

5.4 Grafico Líneas

Vamos a darle formato a Date para poder trabajar con él y poder visualizar el comportamiento de los Datos.

library(lubridate)
df$Date <- as.Date(df$Date, format = "%d-%b-%y")
df$Month <- format(df$Date, "%Y-%m")
df$month <- floor_date(df$date, "month")

monthly_sales <- aggregate(Amount ~ Month, data = df, sum)
monthly_sales <- monthly_sales[order(monthly_sales$Amount, decreasing = TRUE), ]

5.4.1 Comportamiento de las Ventas por mes

ggplot(monthly_sales, aes(x = Month, y = Amount, group = 1)) +
  geom_line(color = "#4682B4", size = 1) +
  geom_point(color = "#3c6e99", size = 3) +
  geom_text(aes(label = scales::label_dollar()(Amount)),  # Add formatted labels 
          hjust = 0.5, vjust = -0.4, color = "black", size = 4) +
  scale_y_continuous(labels = scales::label_dollar()) +
  labs(title = "Comportamiento de Ventas Mensuales",
       x = "Mes",
       y = "Ventas Totales") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.