← Voltar pro projeto

Nove Tabelas, Um Só Negócio: o Modelo Relacional da Olist

Antes de treinar qualquer modelo, eu preciso entender o formato do dado. E o dataset da Olist não é uma tabela só, é um mini banco relacional de verdade: nove CSVs, cada um representando uma entidade do negócio (pedido, item, pagamento, avaliação, cliente, vendedor, produto), ligados entre si por chave. Rodei tudo isso no notebook 01_intro_eda.ipynb, dentro do próprio diretório do dataset, e deixei ele público no Colab: confere o notebook completo aqui.

O modelo relacional

orders = pd.read_csv('olist_orders_dataset.csv')
order_items = pd.read_csv('olist_order_items_dataset.csv')
payments = pd.read_csv('olist_order_payments_dataset.csv')
reviews = pd.read_csv('olist_order_reviews_dataset.csv')
customers = pd.read_csv('olist_customers_dataset.csv')
sellers = pd.read_csv('olist_sellers_dataset.csv')
products = pd.read_csv('olist_products_dataset.csv')
geolocation = pd.read_csv('olist_geolocation_dataset.csv')
category_translation = pd.read_csv('product_category_name_translation.csv')

olist_orders_dataset

99.441 linhas

  • order_id (PK)
  • customer_id
  • order_status
  • timestamps de compra/aprovação/entrega

olist_order_items_dataset

112.650 linhas

  • order_id
  • order_item_id
  • product_id
  • seller_id
  • price
  • freight_value

olist_order_payments_dataset

103.886 linhas

  • order_id
  • payment_type
  • payment_installments
  • payment_value

olist_order_reviews_dataset

99.224 linhas

  • review_id (PK)
  • order_id
  • review_score
  • comentário

olist_customers_dataset

99.441 linhas

  • customer_id (PK)
  • customer_unique_id
  • customer_zip_code_prefix
  • customer_city/state

olist_sellers_dataset

3.095 linhas

  • seller_id (PK)
  • seller_zip_code_prefix
  • seller_city/state

olist_products_dataset

32.951 linhas

  • product_id (PK)
  • product_category_name
  • peso/dimensões

olist_geolocation_dataset

1.000.163 linhas

  • zip_code_prefix
  • lat/lng
  • city/state

product_category_name_translation

71 linhas

  • product_category_name (PT)
  • product_category_name_english

order_id é a chave que costura quase tudo: aparece em orders, order_items, payments e reviews. customer_id, product_id, seller_id e o prefixo de CEP fecham o resto das ligações. Um detalhe que já vale marcar: olist_order_reviews_dataset.csv tem 104.719 linhas de texto bruto no arquivo, mas o pandas só reconhece 99.224 registros de verdade ao ler o CSV. A diferença é porque um monte de comentário de review tem quebra de linha literal dentro do campo de texto (o cliente escreveu em vários parágrafos), e o parser do pandas conta isso corretamente como um registro só, enquanto contar linha bruta do arquivo superestima. Boa lembrança de que "número de linha do arquivo" e "número de registro" nem sempre são a mesma coisa num CSV com campo de texto livre.

Qualidade do dado: nulo tem história

Nem todo nulo é problema, às vezes ele é informação. Nas tabelas onde nulo aparece:

TabelaColunaNulos%
ordersorder_approved_at1600,2%
ordersorder_delivered_carrier_date1.7831,8%
ordersorder_delivered_customer_date2.9653,0%
reviewsreview_comment_title87.65688,3%
reviewsreview_comment_message58.24758,7%
productsproduct_category_name (+ 3 outras colunas de produto)6101,9%
productspeso/dimensões (4 colunas)20,0%

order_delivered_customer_date nulo em 3% dos pedidos não é erro de captura, é pedido que nunca chegou (cancelado, extraviado, ainda em trânsito quando o dataset foi congelado). Isso vira feature importante lá na frente, quando eu for montar o cenário de atraso: um pedido sem data de entrega não tem como calcular atraso, então esses 2.965 pedidos precisam de tratamento explícito (excluir da análise de atraso, ou tratar como categoria própria), não posso simplesmente preencher com zero ou com a média. Review sem título ou comentário (88% e 59% dos casos) também não é problema, a maioria dos clientes só dá a nota e não escreve nada, comportamento normal de review de e-commerce.

Montando o dataframe mestre

A granularidade natural do dataset é item de pedido, não pedido inteiro: um order_id pode ter vários itens, de vendedores diferentes, cada um com seu próprio preço e frete. Por isso o merge parte de order_items, não de orders:

produtos_com_categoria_en = products.merge(category_translation, on='product_category_name', how='left')

mestre = (
    order_items
    .merge(orders, on='order_id', how='left')
    .merge(customers, on='customer_id', how='left')
    .merge(produtos_com_categoria_en, on='product_id', how='left')
    .merge(sellers, on='seller_id', how='left')
    .merge(payments, on='order_id', how='left')
    .merge(reviews, on='order_id', how='left')
)

order_items sozinho tem 112.650 linhas. Depois de todos os merges, o dataframe mestre tem 118.310 linhas, mais do que o ponto de partida. Isso não é bug: quando um pedido é pago em várias parcelas registradas como linhas separadas em payments, ou recebe mais de uma avaliação em reviews, o merge multiplica aquela linha de item de pedido pra cada combinação. É um comportamento esperado de merge relacional, mas é exatamente o tipo de coisa que, se eu não checar o shape antes e depois, passa despercebido e infla contagem em qualquer agregação futura.

Os quatro cenários de ML

Com o dataframe mestre em mãos, definido nos capítulos seguintes:

  1. Atraso na entrega (classificação binária): comparar order_delivered_customer_date com order_estimated_delivery_date.
  2. Nota da avaliação (classificação multiclasse ou regressão): review_score, de 1 a 5.
  3. Valor do frete ou do pedido (regressão): freight_value, ou a soma de price por pedido.
  4. Segmentação de clientes (clustering, sem target): Recência, Frequência e Valor monetário por cliente, técnica RFM.

Pedidos por mês: o pico de Black Friday

Uma primeira olhada temporal, contando pedido único por mês de compra:

Carregando dados reais...

Setembro de 2016 começa com só 4 pedidos (a Olist mal tinha começado), o volume cresce mês a mês ao longo de 2017, e novembro de 2017 dá um salto brusco pra 7.544 pedidos, contra 4.631 em outubro e 5.673 em dezembro do mesmo ano. Isso é a Black Friday, um pico isolado que quebra a tendência suave de crescimento, e vai ser um detalhe importante quando eu for pensar em sazonalidade nos capítulos de feature engineering. Setembro e outubro de 2018 aparecem com só 16 e 4 pedidos, sinal de que o dataset foi congelado no meio do mês, não que as vendas despencaram.

Fechando o capítulo

Modelo relacional mapeado, dataframe mestre montado (118.310 linhas), qualidade de dado checada (os nulos de entrega e de review têm explicação, não são erro), e os quatro cenários definidos. No próximo capítulo eu entro na visualização de verdade: mapa geográfico de pedido e atraso por estado, distribuição de categoria, forma de pagamento, e a relação entre atraso e nota de avaliação, que já dá um "aha moment" antes mesmo de treinar o primeiro modelo.