Objetivo

O objetivo desse estudo é criar análises variadas sobre uma operação de e-commerce, com a produção de resumos, plots e projeções sobre vendas.

Muitos outros indicadores além dos que estão aqui podem ser extraídos, mas vamos nos ater a alguns para não tornar o estudo muito longo e cansativo.

Pacotes usados

library(prophet)
## Carregando pacotes exigidos: Rcpp
## Carregando pacotes exigidos: rlang
library(readr)
library(dplyr)
## 
## 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(tidyr)
library(ggplot2)
library(wordcloud)
## Carregando pacotes exigidos: RColorBrewer
library(tidytext)
library(tidyr)
library(stringr)

Carregamento dos datasets

Os datasets utilizados estão disponíveis em https://www.kaggle.com/datasets/olistbr/brazilian-ecommerce. É um conjunto de datasets famoso do e-commerce Olist, usado em uma série de estudos.

Os joins feitos entre os datasets levam em consideração a estrutura informada pelo mantenedor, respeitando as chaves primárias e estrangeiras.

olist_orders_dataset <- read_csv("archive/olist_orders_dataset.csv", show_col_types = FALSE)

olist_customers_dataset <- read_csv("archive/olist_customers_dataset.csv", show_col_types = FALSE)

olist_order_payments_dataset <- read_csv("archive/olist_order_payments_dataset.csv", show_col_types = FALSE)

Análises preliminares dos pedidos

summary(olist_orders_dataset)
##    order_id         customer_id        order_status      
##  Length:99441       Length:99441       Length:99441      
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##                                                          
##                                                          
##                                                          
##                                                          
##  order_purchase_timestamp         order_approved_at              
##  Min.   :2016-09-04 21:15:19.00   Min.   :2016-09-15 12:16:38.0  
##  1st Qu.:2017-09-12 14:46:19.00   1st Qu.:2017-09-12 23:24:16.0  
##  Median :2018-01-18 23:04:36.00   Median :2018-01-19 11:36:13.0  
##  Mean   :2017-12-31 08:43:12.77   Mean   :2017-12-31 18:35:24.0  
##  3rd Qu.:2018-05-04 15:42:16.00   3rd Qu.:2018-05-04 20:35:10.0  
##  Max.   :2018-10-17 17:30:18.00   Max.   :2018-09-03 17:40:06.0  
##                                   NA's   :160                    
##  order_delivered_carrier_date     order_delivered_customer_date   
##  Min.   :2016-10-08 10:34:01.00   Min.   :2016-10-11 13:46:32.00  
##  1st Qu.:2017-09-15 22:28:50.25   1st Qu.:2017-09-25 22:07:22.25  
##  Median :2018-01-24 16:10:58.00   Median :2018-02-02 19:28:10.50  
##  Mean   :2018-01-04 21:49:48.14   Mean   :2018-01-14 12:09:19.03  
##  3rd Qu.:2018-05-08 13:37:45.00   3rd Qu.:2018-05-15 22:48:52.25  
##  Max.   :2018-09-11 19:48:28.00   Max.   :2018-10-17 13:22:46.00  
##  NA's   :1783                     NA's   :2965                    
##  order_estimated_delivery_date   
##  Min.   :2016-09-30 00:00:00.00  
##  1st Qu.:2017-10-03 00:00:00.00  
##  Median :2018-02-15 00:00:00.00  
##  Mean   :2018-01-24 03:08:37.73  
##  3rd Qu.:2018-05-25 00:00:00.00  
##  Max.   :2018-11-12 00:00:00.00  
## 

São observados alguns NAs nas datas, para os pedidos que ainda não atingiram certa fase da jornada na data da extração do dataset

Novos dados

Podemos criar novas variáveis com a quantidade de dias entre cada etapa do pedido, de forma a calcular a jornada do cliente.

olist_orders_dataset <- olist_orders_dataset %>%
  mutate(compra_a_aprovacao = difftime(order_approved_at,
                            order_purchase_timestamp, units = "days"),
         aprovacao_ao_transportador = difftime(order_delivered_carrier_date,
                            order_approved_at, 
                            units = "days"),
         transportador_a_entrega = difftime(order_delivered_customer_date,
                            order_delivered_carrier_date, 
                            units = "days"),
         entregou_atraso = ifelse(order_delivered_customer_date > order_estimated_delivery_date, 1, 0),
         jornada_total = (compra_a_aprovacao / 24) + aprovacao_ao_transportador + transportador_a_entrega) %>%
  left_join(olist_customers_dataset, by = 'customer_id')

Resumo com a jornada do cliente em média de dias

Aqui temos as tabelas com os resumos de jornada do cliente por UF e município.

jornada_cliente_UF <- olist_orders_dataset %>%
  group_by(customer_state) %>%
  summarise(compra_a_aprovacao = mean(compra_a_aprovacao, na.rm = TRUE),
            aprovacao_ao_transportador = mean(aprovacao_ao_transportador, na.rm = TRUE),
            transportador_a_entrega = mean(transportador_a_entrega, na.rm = TRUE), 
            jornada_total = mean(jornada_total, na.rm = TRUE),
            entregou_atraso = sum(entregou_atraso, na.rm = TRUE),
            indice_atraso = sum(entregou_atraso, na.rm = TRUE) / n())

jornada_cliente_municipio <- olist_orders_dataset %>%
  group_by(customer_city) %>%
  summarise(compra_a_aprovacao = mean(compra_a_aprovacao, na.rm = TRUE),
            aprovacao_ao_transportador = mean(aprovacao_ao_transportador, na.rm = TRUE),
            transportador_a_entrega = mean(transportador_a_entrega, na.rm = TRUE), 
            jornada_total = mean(jornada_total, na.rm = TRUE),
            entregou_atraso = sum(entregou_atraso, na.rm = TRUE),
            indice_atraso = sum(entregou_atraso, na.rm = TRUE) / n())

head(jornada_cliente_UF)
head(jornada_cliente_municipio)

Plots

#Remanejamento da tabela para o plot agrupado
jornada_cliente_UF_pivot <- jornada_cliente_UF %>%
  pivot_longer(cols = 2:5, names_to = "etapa", values_to = "dias") %>%
  select(customer_state, etapa, dias)

Por UF e etapa da compra

jornada_cliente_UF_pivot %>% 
  filter(!(etapa %in% 'jornada_total')) %>%
  ggplot(aes(y = customer_state, fill = etapa, x = dias)) + 
  geom_bar(stat = "identity", position = "stack") +
  theme_classic() +
  labs(title = "Jornada do Cliente em Média de Dias por UF - Por Etapa") +
  xlab("Dias") +
  ylab("UF")
## Don't know how to automatically pick scale for object of type <difftime>.
## Defaulting to continuous.

Por UF compilado

jornada_cliente_UF_pivot %>% 
  filter((etapa %in% 'jornada_total')) %>%
  ggplot(aes(y = customer_state, x = dias)) + 
  geom_bar(stat = "identity", fill = "orange") +
  geom_text(aes(label = round(dias, digits = 2)), 
            position = position_stack(vjust = 1.1)) +
  theme_classic() +
  labs(title = "Jornada do Cliente em Média de Dias por UF - Total") + 
  xlab("Dias") +
  ylab("UF")
## Don't know how to automatically pick scale for object of type <difftime>.
## Defaulting to continuous.
## Don't know how to automatically pick scale for object of type <difftime>.
## Defaulting to continuous.

Projeções de faturamento

Aqui vamos utilizar o algoritmo prophet para produção de forecast de vendas com o faturamento.

O algoritmo prophet é uma espécie de projeção HoltWinters, porém com a vantagem de necessitar de menos parametrizações e conversões de dados e menos tempo gasto para aprimorar a precisão do algoritmo.

prophet facilita o nosso trabalho detectando automaticamente mudanças de tendência ao longo da série temporal, além de funções de grande utilidade, como a geração de plot interativo apresentando a projeção e plots secundários de tendência e sazonalidade.

Com as vendas numa base diária, podemos parametrizar o algoritmo para levar em consideração as sazonalidades diárias, semanais e anuais presentes nos dados, de forma a obter projeções fiéis e confiáveis.

Faturamento Global

#Tratamento do dataset com os pagamentos. 
#Vouchers convertidos para negativos, pois são concedidos pela loja
dataset_pagamentos <- olist_order_payments_dataset %>%
  mutate(payment_value = ifelse(payment_type == 'voucher',
                                payment_value * -1, 
                                payment_value)) %>% 
  group_by(order_id) %>%
  summarise(fatura = sum(payment_value))

dataset_pedidos <- olist_orders_dataset %>%
  left_join(dataset_pagamentos, by = 'order_id')

#Resumo apenas com os pedidos entregues, excluindo os cancelados e ainda não concluídos
resumo_vendas_dia <- dataset_pedidos %>%
  filter(order_status %in% 'delivered') %>%
  mutate(ds = as.Date(order_purchase_timestamp)) %>%
  group_by(ds) %>%
  summarise(y = sum(fatura))

#Modelo com prophet
modelo_forecast_dia <- prophet(resumo_vendas_dia, 
                           growth = "linear", 
                           yearly.seasonality = TRUE, 
                           weekly.seasonality = TRUE, 
                           daily.seasonality = TRUE, 
                           seasonality.mode = "multiplicative")

periodos_futuros_dia <- make_future_dataframe(modelo_forecast_dia,
                                              periods = 720, 
                                              freq = "day",
                                              include_history = TRUE)

projecao_dia <- predict(modelo_forecast_dia, 
                        periodos_futuros_dia)

plot_sazonalidade <- prophet_plot_components(modelo_forecast_dia, projecao_dia)

plot_dia <- dyplot.prophet(modelo_forecast_dia, projecao_dia, 
                           main = "Projeção de Vendas Global", 
                           xlab = "Linha Temporal", 
                           ylab = "Faturamento", 
                           width = "100%")

plot_dia

Plots com a tendência e as sazonalidades por periodicidade

Tendência
plot_sazonalidade[1]
## [[1]]

Podemos observar um tendência de crescimento das vendas ano após ano. Margem de erro também exibida.

Semanal
plot_sazonalidade[2]
## [[1]]

No plot semanal, observa-se maior propensão das pessoas comprarem de segunda a sexta (faixa acima do 0%, que representa a média global), sendo segunda-feira o dia da semana líder de vendas e sexta-feira o dia mais próximo da média. Vendas abaixo da média no sábado e domingo.

Anual
plot_sazonalidade[3]
## [[1]]

No plot anual, que exibe os meses ao longo do ano, observa-se uma maior sazonalidade no final do ano, em Novembro e Dezembro, muito em razão dos presentes adquiridos para as festas, na ordem de mais de 50% acima da média.

Faturamento por categoria de produto

olist_products_dataset <- read_csv("archive/olist_products_dataset.csv", 
    col_types = cols_only(product_id = col_guess(),
                          product_category_name = col_guess()))

olist_order_items_dataset <- read_csv("archive/olist_order_items_dataset.csv", show_col_types = FALSE)

dataset_pedidos_categorizados <- olist_order_items_dataset %>%
  left_join(olist_products_dataset, by = 'product_id') %>%
  left_join(olist_orders_dataset[, c(1, 4)], by = 'order_id')

resumo_vendas_dia_categoria <- dataset_pedidos_categorizados %>%
  mutate(ds = format(order_purchase_timestamp, "%Y-%m-%d")) %>%
  group_by(ds, product_category_name) %>%
  summarise(y = sum(price))
## `summarise()` has grouped output by 'ds'. You can override using the `.groups`
## argument.

Exemplo de projeção de faturamento com os produtos da categoria ‘Móveis e Decoração’

categoria <- 'moveis_decoracao'

modelo_categoria <- prophet(resumo_vendas_dia_categoria %>%
                                filter(product_category_name == categoria), 
                           growth = "linear", 
                           yearly.seasonality = TRUE, 
                           weekly.seasonality = TRUE, 
                           daily.seasonality = TRUE, 
                           seasonality.mode = "multiplicative")
  
  periodos_futuros <- make_future_dataframe(modelo_categoria, 
                                            periods = 720, 
                                            freq = "day")
  
  projecao_categoria <- predict(modelo_categoria, periodos_futuros)

  plot_categoria <- dyplot.prophet(modelo_categoria,
                                   projecao_categoria, 
                                   main = paste0("Projeção de Vendas - ", categoria), 
                                   xlab = "Linha Temporal", 
                                   ylab = "Faturamento", 
                                   width = "100%")
## Adding missing grouping variables: `ds`
  plot_categoria

Análise das avaliações

olist_order_reviews_dataset <- read_csv("archive/olist_order_reviews_dataset.csv", show_col_types = FALSE)

olist_sellers_dataset <- read_csv("archive/olist_sellers_dataset.csv", show_col_types = FALSE)

dataset_reviews <- olist_order_items_dataset %>%
  left_join(olist_sellers_dataset, by = 'seller_id') %>%
  left_join(olist_order_reviews_dataset, by = 'order_id') %>%
  distinct(order_id, .keep_all = TRUE)

Média das notas por vendedor

media_notas <- dataset_reviews %>%
  group_by(seller_id) %>%
  summarise(media_nota = round(mean(review_score, na.rm = TRUE), 
                               digits = 2), 
            quantidade_notas = n()) %>%
  arrange(desc(quantidade_notas))

head(media_notas)

Nuvem de palavras

Visualização gráfica na modalidade “nuvem de palavras”, onde as maiores palavras são as de maior ocorrência nas avaliações.

lista_palavras <- olist_order_reviews_dataset %>% 
  unnest_tokens(output = 'palavra', 
                input = review_comment_message, 
                token = "words", 
                format = "text",
                to_lower = TRUE) %>%
  drop_na() %>%
  filter(nchar(palavra) > 3)

frequencia_palavras <- lista_palavras %>%
  count(palavra, name = "quantidade", sort = TRUE)

wordcloud(words = frequencia_palavras$palavra,
          freq = frequencia_palavras$quantidade, 
          min.freq = 100,
          colors = brewer.pal(8, "Dark2"), 
          random.order = FALSE, 
          rot.per = 0)