Nesse projeto, utilizarei os dados anônimos fornecidos pelo instacart para prever quais produtos adquiridos anteriormente estarão no próximo pedido de um usuário. O instacart é um aplicativo de compras online, onde os compradores selecionam os produtos no app e a entrega é feita na loja mais próxima à você.
Esta é a parte 1 (análise exploratória) de um dos projetos de conclusão do módulo Business Analytics da Formação Cientista de Dados da Data Science Academy (DSA)
Nessa primeira fase farei a análise exploratória dos dados para entender o conteúdo dos datasets e o perfil de compra dos usuários.
“The Instacart Online Grocery Shopping Dataset 2017”, Accessed from:
https://www.instacart.com/datasets/grocery-shopping-2017 on 25/05/2020.
Post do VP da Instacart definindo o problema em detalhes:
https://tech.instacart.com/3-million-instacart-orders-open-sourced-d40d29ead6f2?gi=41f7b19cd164
Para carregar os datasets no google colab em R utilizei a solução descrita no link abaixo:
# Carregando arquivos csv que estão no google drive - caso use google colab
system("gdown --id 1_81hIRMXbE5IHIoJfEmL0NH9Yg0SkiaW") # orders.csv
system("gdown --id 1lCg5HbXqiHXyHWeXgzqbQn59ogCLJ3N3") # products.csv
system("gdown --id 1KlUykoQr9lnCgC--nOkNjQmmn0tAbSOH") # aisles.csv
system("gdown --id 1UmvR8ZnR1lXPstXsKhBSJJE_vsTr8B20") # departments.csv
system("gdown --id 1aOGKfIt-IGMRDsp4KFlJDeuoYgyy4aXU") # order_products__train.csv
# Carregado datasets
orders = read.csv("data/orders.csv", sep = ",")
products = read.csv("data/products.csv", sep = ",")
aisles = read.csv("data/aisles.csv", sep = ",")
departments = read.csv("data/departments.csv", sep = ",")
data_train = read.csv("data/order_products__train.csv", sep = ",")
# Carregar pacotes
library(ggplot2)
library(sqldf)
library(treemap)
library(dplyr)
Para analisar os dados do dataset orders quero primeiro entender como é a demanda dos produtos no decorrer do dia e no decorrer da semana.
O segundo passo é entender o perfil dos clientes. Estamos tendo uma boa taxa de retenção? Qual é o período de tempo médio entre compras de um usuário?
Estrutura dos dados (orders.csv)
# Tamanho do dataset
dim(orders)
# Preview do dataframe
head(orders)
order_id
: order identifieruser_id
: customer identifiereval_set
: which evaluation set this order belongs in - (see SET described below)order_number
: the order sequence number for this user (1 = first, n = nth)order_dow
: the day of the week the order was placed onorder_hour_of_day
: the hour of the day the order was placed ondays_since_prior
: days since the last order, capped at 30 (with NAs for order_number = 1)Quantidade de valores missing
# Função - Proporção dos valores missing
propmiss = function(dataframe) {
m = sapply(dataframe, function(x) {
data.frame(
nmiss = sum(is.na(x)),
n=length(x),
propmiss = sum(is.na(x))/length(x)
)
})
d = data.frame(t(m))
d = sapply(d, unlist)
d = as.data.frame(d)
d$variable = row.names(d)
row.names(d) = NULL
d = cbind(d[ncol(d)],d[-ncol(d)])
return(d[order(d$propmiss), ])
}
# Proporção dos valores missing
propmiss(orders)
6% dos pedidos contém valores NA para days_since_prior_order, ou seja, 6% dos pedidos desse dataset são referentes ao primeiro pedido feito pelo usuário.
Fluxo de compras na semana
A contagem dos dias da semana começam no sábado, sendo sábado = 0, domingo = 1, segunda = 2, etc. Pelo gráfico abaixo, percebe-se que as pessoas fazem mais compras aos sábados e aos domingos, sendo quarta o dia de menor fluxo.
# Fluxo de compras na semana
options(repr.plot.width=12, repr.plot.height=7)
ggplot(orders) +
geom_bar(aes(x=order_dow), fill="steelblue") + ggtitle("Quantidade de pedidos por dia da semana")+
xlab("Dia da semana") + ylab("Quantidade de pedidos")
Fluxo de compras durante o dia
Os gráficos indicam um maior fluxo de compras entre as 10 e as 16 horas. Depois desse horário há uma diminuição gradual do fluxo.
# Fluxo de compras em um dia
ggplot(orders) +
geom_bar(aes(x=order_hour_of_day), fill="steelblue") + ggtitle("Quantidade de pedidos por hora") +
xlab("Hora do dia") + ylab("Quantidade de pedidos")
É importante entender a relação entre essas tabelas e o que elas sozinhas significam. O que o instacart entende como categoria (aisle)? e departamento? Quais as categorias e departamentos com uma maior variedade de produtos?
Estrutura dos dados (products.csv)
# Tamanho do dataset de produtos
dim(products)
# Estrutura do dataframe
head(products)
product_id
: product identifierproduct_name
: name of the productaisle_id
: foreign keydepartment_id
: foreign keyQuantidade de Produtos por Categoria (Aisles)
# Nº de produtos por aisles
products_by_aisles = sqldf( "SELECT ais.aisle_id, ais.aisle, pro.product_id
FROM products AS pro
LEFT JOIN aisles AS ais
ON ais.aisle_id = pro.aisle_id" )
# 10 aisles com maior variedade de produtos
sqldf( "SELECT aisle, COUNT(product_id) AS n_products
FROM products_by_aisles
GROUP BY aisle
ORDER BY n_products DESC
LIMIT 10")
Me parece que existe uma variedade enorme de alimentos não saudáveis. 1246 tipos diferentes de chocolate? Interessante!
Produtos por Departamento
# Nº de produtos por departamento
products_by_depto = sqldf( "SELECT dep.department_id, dep.department, pro.product_id
FROM products AS pro
LEFT JOIN departments AS dep
ON pro.department_id = dep.department_id" )
# Departamentos com maior variedade de produtos
n_products_by_depto = sqldf( "SELECT department, COUNT(product_id) AS n_products
FROM products_by_depto
GROUP BY department
ORDER BY n_products DESC")
# Ordenar em ordem decrescente (preparação para o plot)
n_products_by_depto$department = factor(n_products_by_depto$department, levels = n_products_by_depto$department[order(n_products_by_depto$n_products)])
# Departamentos com maior variedade de produtos
ggplot(n_products_by_depto) +
geom_col(aes(x=department, y=n_products), fill="steelblue") + ggtitle("Variedades de produtos por departamento")+
xlab("Departamento") + ylab("Quantidade de produtos")+ coord_flip()
Relação entre produtos, departamentos e categorias (aisles)
# Preparo dos dados para visualização do treemap
options(repr.plot.width=7, repr.plot.height=7)
tmp = sqldf( " SELECT *
FROM products AS pro
LEFT JOIN aisles AS ais
ON pro.aisle_id = ais.aisle_id " )
tmp = tmp[,c(-2,-3,-5)] # Remover variáveis product_name, aisle_id(1), aisle_id(2)
tmp = sqldf( " SELECT *
FROM tmp
LEFT JOIN departments AS dep
ON tmp.department_id = dep.department_id " )
tmp = tmp[,c(-2,-4)] # Remover variáveis department_id
tmp = sqldf( " SELECT department, aisle, COUNT(product_id) AS n_products
FROM tmp
GROUP BY department, aisle " )
# Plot treemap
treemap(tmp,index=c("department","aisle"),vSize="n_products",title="",palette="Pastel2",border.col="#FFFFFF")
As próximas variáveis (order_number
e days_since_prior_order
) são variáveis que definem o perfil do usuário e portanto vou calculá-las de modo agregado.
Perfil do cliente baseado no nº de Dias desde a última compra
O days_since_prior_order
revela se um cliente tem o costume de fazer compras semanais, quizenais etc. Para entender melhor, vou pegar o exemplo do cliente nº 100 (aleatório).
Analizando abaixo, o cliente nº 100 já está em sua 6ª compra e compra de 30 em 30 dias mais ou menos. Para definir esse número para cada usuário, usarei a média dos days_since_prior_order
.
# Exemplo dos dados do cliente nº 100
sqldf("SELECT * FROM orders WHERE user_id = 100")
# Período médio entre compras (truncado em 30 dias)
options(repr.plot.width=12, repr.plot.height=7)
user_days_since_order = sqldf( "SELECT user_id, AVG(days_since_prior_order) AS avg_days
FROM orders
WHERE days_since_prior_order IS NOT NULL
GROUP BY user_id")
# Histograma
ggplot(user_days_since_order) +
geom_histogram(aes(x=avg_days), fill="steelblue") + ggtitle("Perfil do cliente baseado no período médio entre compras") +
xlab("Período médio entre compras") + ylab("Contagem de clientes")
# Box plot - uma outra forma de visualizar
options(repr.plot.width=12, repr.plot.height=7)
boxplot(user_days_since_order$avg_days, ylab = "Período médio entre compras" , col = "steelblue", main = "Perfil do cliente baseado no período médio entre compras")
Analizando os gráficos acima, a maioria dos clientes faz compras em média quinzenalemente, a maioria entre 10 à 20 dias. Poucas pessoas fazem compras semanais.
Perfil do cliente baseado no nº de pedidos já feitos
O order_number revela qual é o grau de maturidade do cliente, se ele é um cliente antigo ou novo. Para isso, devo considerar apenas o maior order_number
de cada usuário.
# Pedido mais recente feito pelo usuário
options(repr.plot.width=12, repr.plot.height=7)
user_order_number = sqldf( " SELECT user_id, MAX(order_number) as max_order_number
FROM orders
GROUP BY user_id " )
# Histograma
ggplot(user_order_number) +
geom_histogram(aes(x=max_order_number), fill="steelblue") + ggtitle("Perfil do cliente baseado no nº de pedidos já feitos")+
xlab("nº do pedido") + ylab("Contagem de clientes com esse perfil")
Pelo gráfico acima vemos que os clientes são muito eufóricos no início. Há muitos clientes que estão fazendo sua 10ª compra pelo site, porém, há uma queda brusca na retenção de usuários após o 15º pedido e esse número continua caindo gradativamente.
Abaixo temos o dataset data_train
para a criação do modelo. Esse dataset contém quais produtos foram comprados em cada ordem de compra. Através dele, conseguiremos montar um modelo que mostra que tipos de produtos devem ser recomendados para que os clientes comprem novamente. Vou analisar brevemente esse dataset, mas uma explorção mais profunda será feita com o pacote APRIORI em uma segunda etapa.
# Número total de produtos
dim(data_train)
# Número total de pedidos
length(unique(data_train$order_id))
# Estrutura dos datasets (os dois tem a mesma estrutura)
head(data_train)
order_id
: foreign keyproduct_id
: foreign keyadd_to_cart_order
: order in which each product was added to cartreordered
: 1 if this product has been ordered by this user in the past, 0 otherwise