Este projeto foi desenvolvido a partir de um desafio do clube de assinaturas Universidade dos Dados, utilizando o dataset ’Delivery Center: Food & Goods orders in Brazil, disponibilizado no kaggle por Nosbielcs.
Atuamos em uma empresa do setor de delivery, dentro de um time de dados centralizado que atende diversas áreas da companhia. Recentemente, recebemos quadro demandas específicas:
A primeira delas veio do time de marketing, que irá lançar uma campanha para atrair mais entregadores. Nessa ação, será concedida uma bonificação aos 20 profissionais que mais percorreram distância no período analisado. Essa bonificação será variável, de acordo com o tipo de profissional (como parceiro ou fixo) e o modal de entrega utilizado (moto ou bicicleta). Nossa tarefa é identificar esses entregadores e levantar as informações necessárias para a execução da campanha.
A segunda demanda partiu do time de Pricing, que está revisando a política de remuneração dos entregadores. Para embasar esse ajuste, foi solicitado um levantamento da distribuição da distância média percorrida pelos entregadores, segmentada por estado. Essa análise permitirá definir faixas de pagamento regionalizadas com base na realidade operacional de cada localidade.
A terceira solicitação foi feita pelo CFO, que irá apresentar à diretoria executiva alguns indicadores financeiros da operação. Para isso, devemos fornecer quatro tabelas com os seguintes dados: a receita média e a receita total segmentadas por tipo de entrega (Food x Good), e a receita média e a receita total segmentadas por estado.
A quarta demanda envolve o cálculo do bônus corporativo a ser distribuído igualmente entre os 2 mil funcionários da empresa, com base no desempenho do período coberto pelo dataset. Sabe-se que cada entrega gera um custo fixo de R$5,00, que a empresa retém 15% do valor total de cada entrega como receita, e que 20% do lucro líquido é revertido em bônus para o quadro de funcionários. A partir dessas premissas, deve-se estimar o valor que cada colaborador receberá.
Com seus diversos hubs operacionais espalhados pelo Brasil, o Delivery Center é uma plataforma integra lojistas e marketplaces, criando um ecossistema saudável para vendas de good (produtos) e food (comidas) no varejo brasileiro.
Atualmente temos um cadastro (catálogo + cardápio) com mais de 900 mil itens, milhares de pedidos e entregas são operacionalizados diariamente com uma rede de milhares lojistas e entregadores parceiros espalhados por todas as regiões do país.
Tudo isso gera dados e mais dados a todo momento!
Diante disso, nosso negócio está cada vez data driven, ou seja, utilizando dados para tomar decisões e numa visão de futuro sabemos que utilizar os dados de forma inteligente pode ser o nosso grande diferencial no mercado.
Além disso, dados não possuem a completude de toda operação do Delivery Center e algumas informações foram anonimizadas devido ao nosso tratamento com a Lei Geral de Proteção de Dados (LGPD).
library(tidyverse)
## Warning: pacote 'tidyverse' foi compilado no R versão 4.4.3
## Warning: pacote 'ggplot2' foi compilado no R versão 4.4.3
## Warning: pacote 'readr' foi compilado no R versão 4.4.3
## Warning: pacote 'dplyr' foi compilado no R versão 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.2 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(ggplot2)
library(forcats)
library(RColorBrewer)
library(patchwork)
## Warning: pacote 'patchwork' foi compilado no R versão 4.4.2
library(scales)
## Warning: pacote 'scales' foi compilado no R versão 4.4.2
##
## Anexando pacote: 'scales'
##
## O seguinte objeto é mascarado por 'package:purrr':
##
## discard
##
## O seguinte objeto é mascarado por 'package:readr':
##
## col_factor
library(extrafont)
## Registering fonts with R
drivers = read.csv("C:/Users/TutuSurfer/Desktop/Desafio Universidade dos Dados/drivers.csv", sep = ',')
chanels = read.csv("C:/Users/TutuSurfer/Desktop/Desafio Universidade dos Dados/channels.csv", sep = ',')
deliveries = read.csv("C:/Users/TutuSurfer/Desktop/Desafio Universidade dos Dados/deliveries.csv", sep = ',')
hubs = read.csv("C:/Users/TutuSurfer/Desktop/Desafio Universidade dos Dados/hubs.csv", sep = ',')
orders = read.csv("C:/Users/TutuSurfer/Desktop/Desafio Universidade dos Dados/orders.csv", sep = ',')
payments = read.csv("C:/Users/TutuSurfer/Desktop/Desafio Universidade dos Dados/payments.csv", sep = ',')
stores = read.csv("C:/Users/TutuSurfer/Desktop/Desafio Universidade dos Dados/stores.csv", sep = ',')
Este dataset possui informações sobre os canais de venda (marketplaces) onde são vendidos os good e food de nossos lojistas.
## Analisando a estrutura dos dados (dimensão da base de dados e tipo das colunas)
glimpse(chanels)
## Rows: 40
## Columns: 3
## $ channel_id <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 17, 20, 21…
## $ channel_name <chr> "OTHER PLACE", "PHONE PLACE", "WHATS PLACE", "FACE PLACE"…
## $ channel_type <chr> "OWN CHANNEL", "OWN CHANNEL", "OWN CHANNEL", "OWN CHANNEL…
## vendo os dados
head(chanels, 5)
## Verificando se existem dados ausentes
colSums(is.na(chanels))
## channel_id channel_name channel_type
## 0 0 0
## Verificando se existem dados duplicados
sum(duplicated(chanels))
## [1] 0
## verificando a distribuição dos dados
chanels %>%
group_by(channel_type) %>%
summarise(distribuição_canais = n()) %>%
mutate(proporção_canal = distribuição_canais / sum(distribuição_canais))
Este dataset possui informações sobre as entregas realizadas por nossos entregadores parceiros.
## Analisando a estrutura dos dados (dimensão da base de dados e tipo das colunas)
glimpse(deliveries)
## Rows: 378,843
## Columns: 5
## $ delivery_id <int> 2174658, 2174660, 2174661, 2174663, 2174675, …
## $ delivery_order_id <int> 68413340, 68414309, 68416230, 68412721, 68414…
## $ driver_id <int> 8378, 2473, 7615, 8378, 10463, 16430, 14513, …
## $ delivery_distance_meters <int> 5199, 410, 3784, 5714, 3746, 3924, 2489, 2564…
## $ delivery_status <chr> "DELIVERED", "DELIVERED", "DELIVERED", "DELIV…
## visualizando os dados
head(deliveries, 10)
## Verificando se existem dados ausentes
colSums(is.na(deliveries))
## delivery_id delivery_order_id driver_id
## 0 0 15886
## delivery_distance_meters delivery_status
## 73 0
## Verificando se existem dados duplicados
sum(duplicated(deliveries))
## [1] 0
## Verificando se existem ids duplicados
sum(duplicated((deliveries$delivery_order_id)))
## [1] 20189
## Verificando a estatística descritiva das variáveis
summary(deliveries)
## delivery_id delivery_order_id driver_id delivery_distance_meters
## Min. :2174658 Min. :68409030 Min. : 133 Min. : 0
## 1st Qu.:2405589 1st Qu.:76312978 1st Qu.: 7615 1st Qu.: 1184
## Median :2637111 Median :83083209 Median :18754 Median : 2073
## Mean :2634216 Mean :82204223 Mean :21002 Mean : 10721
## 3rd Qu.:2860335 3rd Qu.:87879715 3rd Qu.:31050 3rd Qu.: 3507
## Max. :3144739 Max. :93139817 Max. :66459 Max. :7251291
## NA's :15886 NA's :73
## delivery_status
## Length:378843
## Class :character
## Mode :character
##
##
##
##
## verificando a presença de outliers
ggplot(deliveries, aes(x = delivery_distance_meters)) +
geom_boxplot(fill = "skyblue", color = "black") +
labs(
title = "Boxplot da Distância Percorrida Pelos Entregadores",
x = "Distância em Metros"
) +
theme_minimal(base_size = 14) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold", size = 15, margin = margin(b = 12))
)
## Warning: Removed 73 rows containing non-finite outside the scale range
## (`stat_boxplot()`).
* Não faz nenhum sentido que entregadores tenham percorrido mais de 1000
km (1e+06 metros). Portanto, irei remover os outliers de
‘delivery_distance_meters’
## Obtendo os intervalos interquartis
Q1 = quantile(deliveries$delivery_distance_meters, 0.25, na.rm = TRUE)
Q3 = quantile(deliveries$delivery_distance_meters, 0.75, na.rm = TRUE)
IQR = Q3 - Q1
## Calculando os limites inferior e superior
limite_inf = Q1 - (1.5 * IQR)
limite_sup = Q3 + (1.5 * IQR)
## Obtendo os outliers
outliers = deliveries %>%
filter(delivery_distance_meters < limite_inf | delivery_distance_meters > limite_sup)
## verificando os outliers
head(outliers, 10)
## Removendo os outliers
entregas_sem_outliers = deliveries %>%
filter(delivery_distance_meters >= limite_inf & delivery_distance_meters <= limite_sup)
## Verificando quantas linhas foram excluídas
print(paste('Número de Linhas Excluídas (outliers):', nrow(deliveries) - nrow(entregas_sem_outliers)))
## [1] "Número de Linhas Excluídas (outliers): 19476"
## Verificando a estatística descritiva da base de entregas sem outliers
summary(entregas_sem_outliers)
## delivery_id delivery_order_id driver_id delivery_distance_meters
## Min. :2174658 Min. :68409030 Min. : 133 Min. : 0
## 1st Qu.:2401609 1st Qu.:76244690 1st Qu.: 7549 1st Qu.:1138
## Median :2633036 Median :82953958 Median :18487 Median :1966
## Mean :2631835 Mean :82147886 Mean :20831 Mean :2293
## 3rd Qu.:2859558 3rd Qu.:87874756 3rd Qu.:30950 3rd Qu.:3196
## Max. :3144739 Max. :93139817 Max. :66459 Max. :6991
## NA's :11474
## delivery_status
## Length:359367
## Class :character
## Mode :character
##
##
##
##
## Visualizando a distribuição das distâncias percorridas sem os outliers
ggplot(entregas_sem_outliers, aes(x = delivery_distance_meters)) +
geom_boxplot(fill = "orange", color = "black") +
labs(
title = "Boxplot da Distância Percorrida Pelos Entregadores",
x = "Distância em Metros"
) +
theme_minimal(base_size = 14) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold", size = 15, margin = margin(b = 12))
)
* Agora os dados de distância percorrida estão fazendo muito mais
sentido dentro do contexto da análise. Se tratando de cidades grandes, é
totalmente plausível que um entregador percorra cerca de 7km.
## Verificando a distribuição de entregas realizadas e entregas não realizadas
entregas_sem_outliers %>%
group_by(delivery_status) %>%
summarise('número_entregas' = n())
## Mantendo na base apenas as entregas que foram realizadas
entregas_sem_outliers = entregas_sem_outliers %>%
filter(delivery_status == 'DELIVERED')
## VISUALIZANDO O RESULTADO
head(entregas_sem_outliers, 5)
Este dataset possui informações sobre os entregadores parceiros. Eles ficam em nossos hubs e toda vez que um pedido é processado, são eles fazem as entregas na casa dos consumidores.
## Analisando a estrutura dos dados (dimensão da base de dados e tipo das colunas)
glimpse(drivers)
## Rows: 4,824
## Columns: 3
## $ driver_id <int> 133, 138, 140, 143, 148, 165, 172, 174, 187, 196, 202, 21…
## $ driver_modal <chr> "MOTOBOY", "MOTOBOY", "MOTOBOY", "BIKER", "MOTOBOY", "MOT…
## $ driver_type <chr> "LOGISTIC OPERATOR", "FREELANCE", "FREELANCE", "FREELANCE…
## visualizando os dados
head(drivers, 10)
## Verificando se existem dados ausentes
colSums(is.na(drivers))
## driver_id driver_modal driver_type
## 0 0 0
## Verificando se existem dados duplicados
sum(duplicated(drivers))
## [1] 0
## Verificando a distribuição por tipo de veículo
drivers %>%
group_by(driver_modal) %>%
summarise('quantidade_motoristas_veículo' = n())
## Verificando a distribuição por tipo de contrato
drivers %>%
group_by(driver_type) %>%
summarise('quantidade_motoristas_tipo' = n())
Este dataset possui informações sobre os hubs do Delivery Center. Entenda que os Hubs são os centros de distribuição dos pedidos e é dali que saem as entregas.
## Analisando a estrutura dos dados (dimensão da base de dados e tipo das colunas)
glimpse(hubs)
## Rows: 32
## Columns: 6
## $ hub_id <int> 2, 3, 4, 5, 8, 13, 16, 17, 18, 20, 21, 22, 25, 26, 27, 2…
## $ hub_name <chr> "BLUE SHOPPING", "GREEN SHOPPING", "RED SHOPPING", "FUNK…
## $ hub_city <chr> "PORTO ALEGRE", "PORTO ALEGRE", "PORTO ALEGRE", "RIO DE …
## $ hub_state <chr> "RS", "RS", "RS", "RJ", "RJ", "RJ", "RJ", "SP", "RJ", "R…
## $ hub_latitude <dbl> -30.04741, -30.03741, -30.02195, -23.00075, -22.92148, -…
## $ hub_longitude <dbl> -51.21351, -51.20352, -51.20838, -43.31828, -43.23477, -…
## visualizando os dados
head(hubs, 10)
## Verificando se existem dados ausentes
colSums(is.na(hubs))
## hub_id hub_name hub_city hub_state hub_latitude
## 0 0 0 0 0
## hub_longitude
## 0
## Verificando se existem dados duplicados
sum(duplicated(hubs))
## [1] 0
## Verificando a quantidade de Centros por Estado
hubs %>%
group_by(hub_state) %>%
summarise('Centros_por_Estado' = n()) %>%
arrange(desc(Centros_por_Estado))
## Removendo as colunas que não são relevantes para o projeto
hubs = hubs %>%
select(hub_id, hub_state)
## Verificando o resultado
head(hubs, 5)
Este dataset possui informações sobre as vendas processadas através da plataforma do Delivery Center.
## Analisando a estrutura dos dados (dimensão da base de dados e tipo das colunas)
glimpse(orders)
## Rows: 368,999
## Columns: 29
## $ order_id <int> 68405119, 68405123, 68405206, 684054…
## $ store_id <int> 3512, 3512, 3512, 3401, 3401, 786, 1…
## $ channel_id <int> 5, 5, 5, 5, 5, 5, 5, 35, 35, 5, 5, 5…
## $ payment_order_id <int> 68405119, 68405123, 68405206, 684054…
## $ delivery_order_id <int> 68405119, 68405123, 68405206, 684054…
## $ order_status <chr> "CANCELED", "CANCELED", "CANCELED", …
## $ order_amount <dbl> 62.70, 62.70, 115.50, 55.90, 37.90, …
## $ order_delivery_fee <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 9.90, …
## $ order_delivery_cost <dbl> NA, NA, NA, NA, NA, NA, NA, 0.00, 6.…
## $ order_created_hour <int> 0, 0, 0, 0, 0, 0, 0, 1, 2, 13, 13, 1…
## $ order_created_minute <int> 1, 4, 13, 19, 26, 56, 56, 56, 32, 57…
## $ order_created_day <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ order_created_month <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ order_created_year <int> 2021, 2021, 2021, 2021, 2021, 2021, …
## $ order_moment_created <chr> "1/1/2021 12:01:36 AM", "1/1/2021 12…
## $ order_moment_accepted <chr> "", "", "", "", "", "", "", "1/1/202…
## $ order_moment_ready <chr> "", "", "", "", "", "", "", "", "1/2…
## $ order_moment_collected <chr> "", "", "", "", "", "", "", "", "1/2…
## $ order_moment_in_expedition <chr> "", "", "", "", "", "", "", "", "1/2…
## $ order_moment_delivering <chr> "", "", "", "", "", "", "", "", "1/2…
## $ order_moment_delivered <chr> "", "", "", "", "", "", "", "", "", …
## $ order_moment_finished <chr> "", "", "", "", "", "", "", "", "1/2…
## $ order_metric_collected_time <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 6.63…
## $ order_metric_paused_time <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 4.55…
## $ order_metric_production_time <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 2391…
## $ order_metric_walking_time <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 7.17…
## $ order_metric_expediton_speed_time <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 11.7…
## $ order_metric_transit_time <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 21.7…
## $ order_metric_cycle_time <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 2424…
## visualizando os dados
head(orders, 10)
## Mantendo apenas as colunas relevantes na base 'orders'
orders = orders %>%
select(order_id, store_id, channel_id, payment_order_id, delivery_order_id, order_status, order_moment_created, order_moment_finished)
## visualizando o resultado
head(orders, 5)
## Verificando se existem dados ausentes
colSums(is.na(orders))
## order_id store_id channel_id
## 0 0 0
## payment_order_id delivery_order_id order_status
## 0 0 0
## order_moment_created order_moment_finished
## 0 0
## Verificando se existem dados duplicados
sum(duplicated(orders))
## [1] 0
## Analisando a distribuição do Status de Pedidos
orders %>%
group_by(order_status) %>%
summarise('Pedidos_por_Status' = n())
## Mantendo apenas os pedidos finalizados
pedidos_finalizados = orders %>%
filter(order_status == 'FINISHED')
## visualizando
pedidos_finalizados
## Verificando a estatística descritiva das variáveis
summary(orders)
## order_id store_id channel_id payment_order_id
## Min. :68405119 Min. : 3 Min. : 1.000 Min. :68405119
## 1st Qu.:76355221 1st Qu.: 415 1st Qu.: 5.000 1st Qu.:76355221
## Median :83245994 Median : 707 Median : 5.000 Median :83245994
## Mean :82307261 Mean :1198 Mean : 7.838 Mean :82307261
## 3rd Qu.:88030551 3rd Qu.:1528 3rd Qu.: 5.000 3rd Qu.:88030551
## Max. :93139817 Max. :4679 Max. :49.000 Max. :93139817
## delivery_order_id order_status order_moment_created
## Min. :68405119 Length:368999 Length:368999
## 1st Qu.:76355221 Class :character Class :character
## Median :83245994 Mode :character Mode :character
## Mean :82307261
## 3rd Qu.:88030551
## Max. :93139817
## order_moment_finished
## Length:368999
## Class :character
## Mode :character
##
##
##
Este dataset possui informações sobre os pagamentos realizados ao Delivery Center.
## Analisando a estrutura dos dados (dimensão da base de dados e tipo das colunas)
glimpse(payments)
## Rows: 400,834
## Columns: 6
## $ payment_id <int> 4427917, 4427918, 4427941, 4427948, 4427955, 4427956,…
## $ payment_order_id <int> 68410055, 68410055, 68412721, 68413340, 68414018, 684…
## $ payment_amount <dbl> 118.44, 394.81, 206.95, 58.80, 45.80, 106.80, 57.80, …
## $ payment_fee <dbl> 0.00, 7.90, 5.59, 1.59, 0.92, 2.88, 1.56, 0.40, 3.12,…
## $ payment_method <chr> "VOUCHER", "ONLINE", "ONLINE", "ONLINE", "ONLINE", "O…
## $ payment_status <chr> "PAID", "PAID", "PAID", "PAID", "PAID", "PAID", "PAID…
## Visualizando os dados
payments
## Verificando a existência de dados ausentes
colSums(is.na(payments))
## payment_id payment_order_id payment_amount payment_fee
## 0 0 0 175
## payment_method payment_status
## 0 0
## Verificando dados duplicados
sum(duplicated(payments))
## [1] 0
## verificando se existem ids duplicados
sum(duplicated(payments$payment_order_id))
## [1] 50500
## Verificando as estatísticas descritivas dos pagamentos
summary(payments)
## payment_id payment_order_id payment_amount payment_fee
## Min. :4427917 Min. :68410055 Min. : 0.00 Min. : 0.000
## 1st Qu.:4690206 1st Qu.:76517588 1st Qu.: 32.70 1st Qu.: 0.440
## Median :4955490 Median :83251962 Median : 65.88 Median : 1.040
## Mean :4951200 Mean :82341814 Mean : 93.09 Mean : 1.881
## 3rd Qu.:5211231 3rd Qu.:88025313 3rd Qu.: 121.40 3rd Qu.: 2.710
## Max. :5540806 Max. :93139817 Max. :100000.11 Max. :2000.000
## NA's :175
## payment_method payment_status
## Length:400834 Length:400834
## Class :character Class :character
## Mode :character Mode :character
##
##
##
##
## verificando a presença de outliers
ggplot(payments, aes(x = payment_amount)) +
geom_boxplot(fill = "#A8D5BA", color = "black") +
labs(
title = "Boxplot do Valor dos Pagamentos",
x = "Pagamento (R$)"
) +
theme_minimal(base_size = 14) +
theme(
plot.title = element_text(hjust = 0.5, face = "bold", size = 15, margin = margin(b = 12))
)
## Obtendo os intervalos interquartis
Q1 = quantile(payments$payment_amount, 0.25, na.rm = TRUE)
Q3 = quantile(payments$payment_amount, 0.75, na.rm = TRUE)
IQR = Q3 - Q1
## Calculando os limites inferior e superior
limite_inf = Q1 - (1.5 * IQR)
limite_sup = Q3 + (1.5 * IQR)
## Obtendo os outliers
outliers_pagamentos = payments %>%
filter(payment_amount < limite_inf | payment_amount > limite_sup)
## verificando os outliers
outliers_pagamentos %>%
arrange(desc(payment_amount))
## Verificando a distribuição de classes da coluna 'payment_status'
payments %>%
group_by(payment_status) %>%
summarise(numero_pagamentos = n())
## Mantendo apenas os pagamentos que foram de fato pagos
pagamentos_feitos = payments %>%
filter(payment_status == 'PAID')
## Visualizando
head(pagamentos_feitos, 5)
Este dataset possui informações sobre os lojistas. Eles utilizam a Plataforma do Delivery Center para vender seus itens (good e/ou food) nos marketplaces.
## Analisando a estrutura dos dados (dimensão da base de dados e tipo das colunas)
glimpse(stores)
## Rows: 951
## Columns: 7
## $ store_id <int> 3, 6, 8, 53, 54, 56, 58, 82, 83, 84, 85, 88, 89, 90, …
## $ hub_id <int> 2, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 8, 8,…
## $ store_name <chr> "CUMIURI", "PIMGUCIS DA VIVA ", "RASMUR S ", "PAPA SU…
## $ store_segment <chr> "FOOD", "FOOD", "FOOD", "FOOD", "FOOD", "FOOD", "FOOD…
## $ store_plan_price <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 49.0, 49.0, 0.0, 0.0, 49.0, …
## $ store_latitude <dbl> NA, -30.03741, -30.03741, -22.92148, -22.92148, -22.9…
## $ store_longitude <dbl> NA, -51.20352, -51.20352, -43.23482, -43.23482, -43.2…
## visualizando os dados
stores
## Verificando se existem dados ausentes
colSums(is.na(stores))
## store_id hub_id store_name store_segment
## 0 0 0 0
## store_plan_price store_latitude store_longitude
## 115 16 16
## Verificando se existem dados duplicados
sum(duplicated(stores))
## [1] 0
## Verificando a quantidade de Centros por Estado
stores %>%
group_by(store_segment) %>%
summarise('Lojas por Tipo' = n())
## Removendo as colunas que não são relevantes para o projeto
stores = stores %>%
select(store_id, hub_id, store_segment, store_latitude, store_longitude)
## Verificando o resultado
head(stores, 5)
## agregando os dados de pagamentoque contém o mesmo payment_order_id
pagamentos_agrupados = pagamentos_feitos %>%
group_by(payment_order_id) %>%
summarise(
valor_total = sum(payment_amount, na.rm = TRUE),
taxa_pagamento = sum(payment_fee, na.rm = TRUE),
payment_status = first(payment_status), # ou unique() se quiser validar depois
.groups = 'drop'
)
## Agregando os dados de entregas que contém o mesmo delivery_order_id
entregas_agrupadas = entregas_sem_outliers %>%
group_by(delivery_order_id, driver_id) %>%
summarise(
viagens = n(),
distancia_total_percorrida = sum(delivery_distance_meters, na.rm = TRUE),
delivery_status = first(delivery_status),
.groups = "drop"
)
## Calculando o total de viagens por delivery_order_id (somando todos os motoristas)
total_viagens_por_order = entregas_agrupadas %>%
group_by(delivery_order_id) %>%
summarise(total_viagens = sum(viagens), .groups = "drop")
# Adicionando esse total na base de entregas
entregas_com_total = entregas_agrupadas %>%
left_join(total_viagens_por_order, by = "delivery_order_id")
# Calculando o peso de cada motorista (proporção das viagens)
entregas_com_total = entregas_com_total %>%
mutate(peso_motorista = viagens / total_viagens)
# Juntando a base de entregas com com pagamentos
base_entregas = entregas_com_total %>%
left_join(pagamentos_agrupados, by = c("delivery_order_id" = "payment_order_id")) %>%
mutate(
valor_proporcional = valor_total * peso_motorista,
taxa_proporcional = taxa_pagamento * peso_motorista
)
## juntando a base de entregadores na base de entregas
base_entregas = base_entregas %>%
left_join(drivers, by = c('driver_id' = 'driver_id'))
## Juntando a base orders na base_entregas
base_entregas = base_entregas %>%
left_join(pedidos_finalizados, by = c('delivery_order_id' = 'delivery_order_id'))
## juntando com a base de lojas
base_entregas = base_entregas %>%
left_join(stores, by = ('store_id' = 'store_id'))
## juntando com a base de hubs
base_entregas = base_entregas %>%
left_join(hubs, by = ('hub_id' = 'hub_id'))
## calculando receita, custo e lucro de cada entrega
base_entregas = base_entregas %>%
mutate(
receita = (valor_proporcional * 0.15),
custo = (5 * total_viagens),
lucro = receita - custo
)
## ## Removendo as colunas que não são mais necessárias e organizando as colunas restantes
base_entregas = base_entregas %>%
select(delivery_order_id, driver_id, payment_order_id,
delivery_status, order_status, payment_status,
distancia_total_percorrida, total_viagens, valor_proporcional, taxa_proporcional, receita, custo, lucro,
order_moment_created, order_moment_finished,
driver_modal, driver_type, store_segment, hub_state, store_latitude, store_longitude
) %>%
rename(valor = valor_proporcional,
taxa = taxa_proporcional)
## visualizando o resultado
head(base_entregas)
## Verificando se existem dados ausentes
colSums(is.na(base_entregas))
## delivery_order_id driver_id
## 0 5239
## payment_order_id delivery_status
## 2102 0
## order_status payment_status
## 2102 4274
## distancia_total_percorrida total_viagens
## 0 0
## valor taxa
## 4274 4274
## receita custo
## 4274 0
## lucro order_moment_created
## 4274 2102
## order_moment_finished driver_modal
## 2102 5239
## driver_type store_segment
## 5239 2102
## hub_state store_latitude
## 2102 3030
## store_longitude
## 3030
## Removendo dados que não possuem status de pagamento e o valor pago (valor)
base_entregas = base_entregas %>%
filter(!is.na(payment_status))
## criando uma base com os 20 motoristas que mais rodaram
top_20_moto = base_entregas %>%
filter(!is.na(driver_id) & driver_modal == 'MOTOBOY') %>%
group_by(driver_id) %>%
summarise(
distancia_total_motorista = sum(distancia_total_percorrida , na.rm = TRUE),
driver_modal = first(driver_modal),
driver_type = first(driver_type),
.groups = 'drop'
) %>%
arrange(desc(distancia_total_motorista)) %>%
slice_head(n = 20)
## visualizando o resultado
top_20_moto
A análise do top 20 de motoboys que mais percorreram distância revela uma predominância de motoristas vinculados a operadores logísticos (LOGISTIC OPERATOR), que ocupam 13 das 20 posições — incluindo todas as cinco primeiras. Isso sugere que esse tipo de vínculo está associado a uma maior intensidade operacional, seja por maior disponibilidade, confiabilidade ou integração com o sistema de logística da empresa.
Por outro lado, os motoristas freelance, embora em menor número, também demonstram desempenho expressivo, com distâncias percorridas próximas às dos operadores logísticos. Isso indica que existe um potencial relevante de contribuição entre os freelancers, que pode ser explorado com políticas adequadas de incentivo, bonificação por desempenho ou maior integração à operação.
Dessa forma, a distribuição dos vínculos entre os motoboys mais ativos oferece insumos valiosos para decisões estratégicas relacionadas à gestão da frota, como o dimensionamento ideal entre operadores contratados e autônomos, além da criação de modelos híbridos de engajamento e retenção que maximizem o desempenho logístico.
## Visualizando o top 20 para bicicletas
top_20_bike = base_entregas %>%
filter(!is.na(driver_id) & driver_modal == 'BIKER') %>%
group_by(driver_id) %>%
summarise(
distancia_total_motorista = sum(distancia_total_percorrida , na.rm = TRUE),
driver_modal = first(driver_modal),
driver_type = first(driver_type),
.groups = 'drop'
) %>%
arrange(desc(distancia_total_motorista)) %>%
slice_head(n = 20)
## visualizando o resultado
top_20_bike
Todos os 20 bikers que mais percorreram distância atuam como freelancers, sem nenhum operador logístico entre os mais ativos. Isso mostra que o modal bicicleta é sustentado quase que exclusivamente por profissionais autônomos, o que destaca sua importância operacional mesmo fora de estruturas formais.
Esse padrão sugere alto potencial entre freelancers, indicando que ações como bonificação por desempenho, fidelização ou programas de incentivo podem fortalecer ainda mais esse grupo. Também aponta para uma possível oportunidade de estruturação logística voltada aos bikers, ampliando eficiência e previsibilidade no uso desse modal.
## calculando a distância média percorrida por motoqueiros em cada estado
motoqueiros_estado = base_entregas %>%
filter(driver_modal == "MOTOBOY" & !is.na(distancia_total_percorrida) & !is.na(hub_state)) %>%
group_by(hub_state) %>%
summarise(
distancia_media_motoqueiros = mean(distancia_total_percorrida, na.rm = TRUE),
distancia_total_motoqueiros = sum(distancia_total_percorrida, na.rm = TRUE),
n_entregas = sum(total_viagens),
.groups = 'drop'
) %>%
mutate(
distancia_total_km = distancia_total_motoqueiros / 1000,
posicao_label = ifelse(distancia_total_km < 100000, -0.5, 1.1), # ajusta com base no comprimento
hjust_label = ifelse(distancia_total_km < 100000, -0.1, 1.1) # direita ou esquerda
)
## calculando a distância média percorrida por motoqueiros em cada estado
bikers_estado = base_entregas %>%
filter(driver_modal == "BIKER" & !is.na(distancia_total_percorrida) & !is.na(hub_state)) %>%
group_by(hub_state) %>%
summarise(
distancia_media_bikers = mean(distancia_total_percorrida, na.rm = TRUE),
distancia_total_bikers = sum(distancia_total_percorrida, na.rm = TRUE),
n_entregas = sum(total_viagens),
.groups = 'drop'
) %>%
mutate(
distancia_total_km = distancia_total_bikers / 1000,
posicao_label = ifelse(distancia_total_km < 10000, -0.5, 1.1), # ajusta com base no comprimento
hjust_label = ifelse(distancia_total_km < 10000, -0.1, 1.1) # direita ou esquerda
)
## Gerando o gráfico de barras para as distâncias médias percorridas de moto em cada estado
g1 = ggplot(motoqueiros_estado,
aes(x = fct_reorder(hub_state, distancia_media_motoqueiros),
y = distancia_media_motoqueiros / 1000,
fill = hub_state)) + # <-- divisão feita aqui
geom_col() +
geom_text(aes(label = paste0(round(distancia_media_motoqueiros / 1000, 1), " km")), # <-- aqui também
hjust = 1.1, size = 4) +
labs(
title = "Distância Média (Quilômetros)",
x = NULL,
y = NULL
) +
coord_flip() +
scale_fill_brewer(palette = "Set2") +
theme_classic(base_size = 14) +
theme(
legend.position = 'none',
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
plot.title = element_text(face = "bold", size = 13, hjust = 0.5, lineheight = 1.1),
axis.title = element_text(size = 12),
axis.text = element_text(size = 12)
)
## Gerando o gráfico de barras para as distâncias totais percorridas de moto em cada estado
g2 = ggplot(motoqueiros_estado,
aes(x = fct_reorder(hub_state, distancia_total_motoqueiros),
y = distancia_total_motoqueiros / 1000, # conversão aqui
fill = hub_state)) +
geom_col() +
geom_text(aes(label = paste0(round(distancia_total_motoqueiros / 1000, 1), " km"), # e aqui também
hjust = hjust_label),
size = 4) +
labs(
title = "Distância Total (Quilômetros)",
x = NULL,
y = NULL
) +
coord_flip() +
scale_fill_brewer(palette = "Set2") +
theme_classic(base_size = 14) +
theme(
legend.position = 'none',
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
plot.title = element_text(face = "bold", size = 13, hjust = 0.5, lineheight = 1.1),
axis.title = element_text(size = 12),
axis.text = element_text(size = 12)
)
## Visualizando os dois gráficos juntos
(g1 | g2) +
plot_annotation(
title = "Distâncias Percorridas por Motoboys",
theme = theme(
plot.title = element_text(face = "bold", size = 18, hjust = 0.5)
)
)
* A leitura conjunta dos dois gráficos mostra que, enquanto RS e PR
possuem maiores distâncias médias por entrega, são SP e RJ que
concentram o volume operacional dos motoboys, com muito mais entregas
sendo realizadas. SP, em particular, se destaca como o principal polo de
entregas com motocicleta, mesmo com menor distância média — o que sugere
alta densidade de pedidos em regiões urbanas compactas.
## Gerando o gráfico de barras para as distâncias médias percorridas de bike em cada estado
g3 = ggplot(bikers_estado,
aes(x = fct_reorder(hub_state, distancia_media_bikers),
y = distancia_media_bikers / 1000, # conversão para km
fill = hub_state)) +
geom_col() +
geom_text(aes(label = paste0(round(distancia_media_bikers / 1000, 1), " km")),
hjust = 1.1, size = 4) +
labs(
title = "Distância Média (Quilômetros)",
x = NULL,
y = NULL
) +
coord_flip() +
scale_fill_brewer(palette = "Accent") +
theme_classic(base_size = 14) +
theme(
legend.position = 'none',
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
plot.title = element_text(face = "bold", size = 16, hjust = 0, lineheight = 1.1),
axis.title = element_text(size = 13),
axis.text = element_text(size = 12)
)
## Gerando o gráfico de barras para as distâncias totais percorridas de bike em cada estado
g4 = ggplot(bikers_estado,
aes(x = fct_reorder(hub_state, distancia_total_km),
y = distancia_total_km,
fill = hub_state)) +
geom_col() +
geom_text(aes(label = paste0(round(distancia_total_km, 1), " km"),
hjust = hjust_label),
size = 4) +
labs(
title = "Distância Total (Quilômetros)",
x = NULL,
y = NULL
) +
coord_flip() +
scale_fill_brewer(palette = "Accent") +
theme_classic(base_size = 14) +
theme(
legend.position = 'none',
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
plot.title = element_text(face = "bold", size = 16, hjust = 0, lineheight = 1.1),
axis.title = element_text(size = 13),
axis.text = element_text(size = 12)
)
## Visualizando os dois gráficos juntos
(g3 | g4) +
plot_annotation(
title = "Distâncias Percorridas por Bikers",
theme = theme(
plot.title = element_text(face = "bold", size = 18, hjust = 0.5)
)
)
* A leitura cruzada dos dois gráficos indica que, embora PR e RS tenham
distâncias médias mais altas por entrega, seu volume total de entregas
com bikers é muito inferior ao de RJ e SP. Isso pode significar uma
operação de menor escala nesses estados ou menor adesão ao modal
bicicleta.
## criando os indicadores de receita média e total separada por segmento (food x good)
receita_segmento = base_entregas %>%
filter(!is.na(store_segment)) %>%
group_by(store_segment) %>%
summarise(
receita_total = sum(receita, na.rm = TRUE),
receita_media = mean(receita, na.rm = TRUE),
n_entregas = n(),
.groups = 'drop'
) %>%
mutate(
prop_receita = receita_total / sum(receita_total),
rotulo = paste0(
"R$ ", round(receita_total / 1e6, 2), "M",
"\n(", round(prop_receita * 100, 1), "%)"
),
posicao_label = ifelse(receita_total < 1000000, -0.1, 1.1),
hjust_label = ifelse(receita_total < 1000000, -0.1, 1.1),
receita_total_milhoes = receita_total / 1e6
) %>%
mutate(
store_segment = factor(store_segment, levels = c("FOOD", "GOOD"))
)
## Gráfico 1: Receita média por segmento
g_receita_media = ggplot(receita_segmento,
aes(x = store_segment,
y = receita_media,
fill = store_segment)) +
geom_col() +
geom_text(aes(label = paste0("R$ ", round(receita_media, 2))),
vjust = 2, size = 4) +
labs(
title = "Receita Média (Reais)",
x = NULL,
y = NULL
) +
scale_fill_brewer(palette = "Pastel1") +
theme_classic(base_size = 14) +
theme(
legend.position = 'none',
axis.ticks.x = element_blank(),
axis.ticks.y = element_blank(),
axis.text.y = element_blank(),
plot.title = element_text(face = "bold", size = 13, hjust = 0.5, lineheight = 0.5),
axis.title = element_text(size = 12),
axis.text = element_text(size = 12)
)
## Gráfico 2: Receita total por segmento
g_receita_total = ggplot(receita_segmento,
aes(x = store_segment,
y = receita_total_milhoes,
fill = store_segment)) +
geom_col() +
geom_text(aes(label = rotulo),
vjust = 1.2, size = 4) +
labs(
title = "Receita Total (Milhões de Reais)",
x = NULL,
y = NULL
) +
scale_fill_brewer(palette = "Pastel1") +
theme_classic(base_size = 14) +
theme(
legend.position = 'none',
axis.ticks.x = element_blank(),
axis.ticks.y = element_blank(),
axis.text.y = element_blank(),
plot.title = element_text(face = "bold", size = 13, hjust = 0.5, lineheight = 0.5),
axis.title = element_text(size = 12),
axis.text = element_text(size = 12)
)
## Unificando os dois gráficos com título geral
(g_receita_total | g_receita_media) +
plot_annotation(
title = "Receitas por Segmento de Loja",
theme = theme(
plot.title = element_text(face = "bold", size = 18, hjust = 0.5)
)
)
* O gráfico comparativo entre os segmentos FOOD e GOOD revela dois
padrões distintos de desempenho que devem orientar estratégias
específicas para cada um.
O segmento FOOD concentra a maior parte da receita total da operação, respondendo por 4,14 milhões de reais, o equivalente a 80,8% do total. Isso reflete um alto volume de entregas e uma operação já consolidada. No entanto, a receita média por entrega é significativamente inferior, ficando em 13,29 reais, o que aponta para um ticket médio mais baixo.
Por outro lado, o segmento GOOD, embora represente apenas 19,2% da receita total, apresenta uma receita média por entrega muito superior, de 34,94 reais. Isso indica que cada entrega do segmento GOOD gera mais valor individualmente, mesmo com menor frequência ou volume.
Para o segmento FOOD, o foco deve estar em ganhos de escala, eficiência operacional e retenção de entregadores, já que o volume é o principal motor da receita. Estratégias logísticas e de incentivo ao aumento do número de pedidos tendem a gerar impacto direto no faturamento.
Para o segmento GOOD, há uma oportunidade clara de crescimento via expansão da base de clientes e aumento da frequência de pedidos. Como o ticket médio é elevado, ações direcionadas — como campanhas de marketing segmentadas, parcerias com lojas premium, ou programas de fidelidade — podem aumentar significativamente a receita com menos esforço logístico.
A empresa pode também considerar diferenciações nas políticas de precificação e bonificação entre os segmentos, dado o perfil distinto de cada um.
## criando os indicadores de receita média e total separada por segmento (food x good)
receita_por_estado = base_entregas %>%
filter(!is.na(hub_state)) %>%
group_by(hub_state) %>%
summarise(
receita_total = sum(receita, na.rm = TRUE),
receita_media = mean(receita, na.rm = TRUE),
n_entregas = n(),
.groups = 'drop'
) %>%
mutate(
receita_total_milhoes = receita_total / 1e6,
rotulo_total = paste0("R$ ", round(receita_total / 1e6, 2), " M"),
rotulo_media = paste0("R$ ", round(receita_media, 2)),
hjust_total = ifelse(receita_total_milhoes < 1.5, -0.1, 1.1),
) %>%
arrange(desc(receita_total))
## Gráfico 1: Receita Total
g_total_estado = ggplot(receita_por_estado,
aes(x = fct_reorder(hub_state, receita_total_milhoes),
y = receita_total_milhoes, fill = hub_state)) +
geom_col() +
geom_text(aes(
label = rotulo_total,
hjust = hjust_total
), size = 4) +
labs(
title = "Receita Total (Milhões de Reais)",
x = NULL,
y = NULL
) +
coord_flip() +
scale_fill_brewer(palette = "Pastel1") +
theme_classic(base_size = 14) +
theme(
legend.position = 'none',
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
plot.title = element_text(face = "bold", size = 13, hjust = 0.5, lineheight = 1.1),
axis.title = element_text(size = 12),
axis.text = element_text(size = 12)
)
## Gráfico 2: Receita Média
g_media_estado = ggplot(receita_por_estado,
aes(x = fct_reorder(hub_state, receita_media),
y = receita_media, fill = hub_state)) +
geom_col() +
geom_text(aes(
label = rotulo_media,
hjust = 1.1
), size = 4) +
labs(
title = "Receita Média (Reais)",
x = NULL,
y = NULL
) +
coord_flip() +
scale_fill_brewer(palette = "Pastel1") +
theme_classic(base_size = 14) +
theme(
legend.position = 'none',
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
plot.title = element_text(face = "bold", size = 13, hjust = 0.5, lineheight = 1.1),
axis.title = element_text(size = 12),
axis.text = element_text(size = 12),
)
## Unificando os dois gráficos com título geral
(g_total_estado | g_media_estado) +
plot_annotation(
title = "Receitas Estaduais",
theme = theme(
plot.title = element_text(face = "bold", size = 18, hjust = 0.5)
)
)
* São Paulo se destaca como o principal mercado da operação,
apresentando tanto a maior receita total (R$ 2,64 milhões) quanto a
maior receita média por entrega (R$ 17,56). Isso indica um alto volume
de pedidos combinados a um ticket médio elevado, sugerindo uma operação
mais consolidada e madura no estado.
O Rio de Janeiro aparece em segundo lugar nos dois indicadores: sua receita total é de R$ 1,83 milhões, e a receita média por entrega é de R$ 13,97. Embora abaixo de São Paulo, os valores ainda demonstram uma performance relevante e estável, com bom potencial de manutenção ou crescimento.
Já o Rio Grande do Sul apresenta uma receita total bem inferior (R$ 0,39 milhões), mas mantém uma receita média por entrega competitiva (R$ 12,49), próxima à do Rio de Janeiro. Isso sugere que o estado tem um volume menor de entregas, mas com um valor médio razoável por pedido, o que pode indicar um mercado ainda em expansão, porém com boas margens.
*Por fim, o Paraná registra os menores valores tanto em receita total (R$ 0,27 milhões) quanto em receita média por entrega (R$ 9,83). A combinação de baixo volume com ticket médio mais baixo pode indicar uma operação em estágio inicial ou com menor penetração de mercado, representando uma possível oportunidade de crescimento ou a necessidade de reavaliar estratégias locais.
## Calculando o lucro total
lucro_total = sum(base_entregas$lucro)
## Calculando o bônus total
bonus_total = lucro_total * 0.2
## Calculando o bônus individual de cada funcionário
bonus_individual = bonus_total / 2000
## Visualizando o resultado
print(paste("A Bonificação de Cada Funcionário Será de R$", round(bonus_individual, 2)))
## [1] "A Bonificação de Cada Funcionário Será de R$ 329.04"