Na Aula 3 você respondeu, em Python, a uma pergunta de negócio de verdade: quais produtos, clientes, países e períodos priorizar, e o quanto cancelamentos e devoluções custam ao resultado. O raciocínio que sustentou aquela análise, isolar vendas válidas, agregar por pedido e por produto, medir o que foi perdido, não pertence ao pandas. Ele pertence a quem analisa dados, e pode ser expresso em qualquer ferramenta capaz de ler uma tabela.
R é essa outra ferramenta. Ela já apareceu de passagem na Aula 1, como linguagem relevante em estatística e na academia, e agora é hora de conhecê-la na prática, através do tidyverse, uma coleção de pacotes que oferece para o R o mesmo tipo de sintaxe expressiva e encadeada que o pandas oferece para o Python. Este roteiro reproduz, passo a passo, a mesma análise da Aula 3, e depois vai além dela: apresenta perguntas de negócio novas, que o enunciado original não pedia, para mostrar que o valor de dominar duas ferramentas não é redundância, é alcance.
Objetivos de aprendizagem
Ao final deste roteiro, você será capaz de:
Reconhecer as equivalências entre pandas e tidyverse: groupby().agg() e group_by() |> summarise(), filtros booleanos e filter(), pd.to_datetime e as funções do pacote lubridate;
Reproduzir em R os indicadores de negócio calculados em Python na Aula 3: faturamento, ticket médio, produtos mais relevantes e impacto de cancelamentos;
Escrever pipelines tidyverse (|>) para responder a perguntas de negócio que ainda não haviam sido formuladas, usando o mesmo dataset;
Comparar criticamente os resultados das duas linguagens sobre os mesmos dados, como forma de validar a tradução.
Como usar este roteiro
Este roteiro pressupõe o projeto-vendas das Aulas 1 a 3 funcionando, com o Online Retail já baixado em data/. Assim como a Aula 3, este script não é a análise completa: é uma tradução de referência, seção a seção, mais uma seção nova de perguntas extras. Ao final, quatro perguntas ajudam a testar se os critérios usados aqui, os mesmos da Aula 3, continuam sólidos quando expressos em outra linguagem.
Configurando o ambiente R
A Aula 1 isolou o ambiente Python por projeto com uv venv, e registrou versões exatas em requirements.txt. Aqui simplificamos deliberadamente: instalamos os pacotes uma vez, de forma global na máquina, sem isolar por projeto.
R tem um equivalente ao uv venv: o pacote renv, que cria um ambiente isolado e reprodutível por projeto, com um renv.lock no lugar do requirements.txt. Ele fica fora do escopo desta aula, cujo foco é a tradução da análise, mas vale saber que ele existe: se um dia você depender de R em um projeto sério, renv é o próximo passo natural, pela mesma razão que uv venv foi o da Aula 1.
1. Retomando o projeto
Como sempre, comece verificando onde o projeto parou:
cd ./projeto-vendas# Onde paramos? O git log conta a historia ate aqui:git log --oneline# E o estado atual? Espera-se uma area de trabalho limpa:git status
bbe6150 Adiciona pandas e registra dependencias em requirements.txt
1b4999b Adiciona .gitignore para ambiente, dados e segredos
2ce3f11 Adiciona README com a descricao do projeto
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
modified: requirements.txt
no changes added to commit (use "git add" and/or "git commit -a")
Se o log mostra os commits das Aulas 1 a 3 e o status diz que não há nada a commitar, o ambiente e os dados estão prontos. Os dados já baixados na Aula 2 servem sem qualquer alteração.
2. O enunciado
O enunciado é exatamente o mesmo da Aula 3: o gestor de vendas precisa entender o desempenho comercial do último ano e identificar oportunidades para aumentar o faturamento, considerando:
Evolução mensal do faturamento e sazonalidade;
Produtos com maior faturamento, volume e frequência de compra;
Países e clientes mais relevantes;
Valor médio dos pedidos;
Impacto financeiro de cancelamentos e devoluções;
Outliers e problemas de qualidade dos dados;
Recomendações práticas para a gestão comercial.
O que muda aqui não é a pergunta, é a ferramenta usada para respondê-la, e o quanto ela permite ir além do que foi pedido.
3. Preparando os dados
O carregamento e a preparação replicam exatamente a Aula 3: a mesma coluna de receita por item e o mesmo sinalizador de cancelamento, derivado do prefixo C no número da fatura.
library(readxl)library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.2.0 ✔ readr 2.2.0
✔ forcats 1.0.1 ✔ stringr 1.6.0
✔ ggplot2 4.0.2 ✔ tibble 3.3.1
✔ lubridate 1.9.5 ✔ tidyr 1.3.2
✔ purrr 1.2.1
── 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
Os nomes das colunas são padronizados em snake_case, convenção adotada no restante do código. Com a base carregada, confirmamos suas dimensões:
cat("Arquivo carregado com sucesso.\n", glue::glue("Dimensão da base: {nrow(retail)} linhas x {ncol(retail)} colunas\n"),sep ="")
Arquivo carregado com sucesso.
Dimensão da base: 541909 linhas x 10 colunas
4. Visão geral da base
Antes de qualquer indicador de negócio, o mesmo retrato geral tirado na Aula 3: tamanho, tipos e uma primeira olhada nos números. Estrutura e tipos de dados:
Linhas exatamente duplicadas: 5,268
Percentual de duplicatas: 0.9721%
Registros potencialmente problemáticos:
issues <-tibble(metrica =c("Faturas canceladas", "Quantidade negativa", "Quantidade igual a zero","Preço negativo", "Preço igual a zero", "Descrição ausente","Cliente não identificado", "Data inválida ou ausente" ),quantidade =c(sum(retail$is_cancelled),sum(retail$quantity <0),sum(retail$quantity ==0),sum(retail$unit_price <0),sum(retail$unit_price ==0),sum(is.na(retail$description)),sum(is.na(retail$customer_id)),sum(is.na(retail$invoice_date)) ))issues
# A tibble: 8 × 2
metrica quantidade
<chr> <int>
1 Faturas canceladas 9288
2 Quantidade negativa 10624
3 Quantidade igual a zero 0
4 Preço negativo 2
5 Preço igual a zero 2515
6 Descrição ausente 1454
7 Cliente não identificado 135080
8 Data inválida ou ausente 0
7. Indicadores de pedidos, produtos, países e clientes
Com as vendas válidas isoladas, podemos mudar a unidade de análise conforme a pergunta: pedidos, produtos, países ou clientes. Cada tabela abaixo tem uma linha por entidade analisada e reúne as métricas necessárias para responder ao enunciado.
Valor médio dos pedidos
Primeiro, agregamos os itens de uma mesma fatura. O resultado terá uma linha por pedido, permitindo calcular seu valor médio sem dar mais peso aos pedidos que possuem muitos itens.
O ticket médio responde diretamente à pergunta sobre o valor médio dos pedidos. A mediana e os percentis complementam a resposta: se a média estiver muito acima da mediana, poucos pedidos de alto valor estão puxando o indicador para cima.
Produtos com maior faturamento, volume e frequência
Para comparar produtos, usamos três métricas diferentes:
revenue: faturamento gerado;
units_sold: unidades vendidas;
order_count: quantidade de pedidos distintos em que o produto apareceu, nossa medida de frequência de compra.
products |>slice_max(revenue, n =10, with_ties =FALSE)
# A tibble: 10 × 5
stock_code description units_sold revenue order_count
<chr> <chr> <dbl> <dbl> <int>
1 DOT DOTCOM POSTAGE 706 206249. 706
2 22423 REGENCY CAKESTAND 3 TIER 13879 174485. 1988
3 23843 PAPER CRAFT , LITTLE BIRDIE 80995 168470. 1
4 85123A WHITE HANGING HEART T-LIGHT HOLDER 37599 104340. 2189
5 47566 PARTY BUNTING 18295 99504. 1685
6 85099B JUMBO BAG RED RETROSPOT 48474 94340. 2089
7 23166 MEDIUM CERAMIC TOP STORAGE JAR 78033 81701. 247
8 M Manual 7224 78110. 289
9 POST POSTAGE 3150 78102. 1126
10 23084 RABBIT NIGHT LIGHT 30788 66965. 994
Produtos com maior volume vendido:
products |>slice_max(units_sold, n =10, with_ties =FALSE)
# A tibble: 10 × 5
stock_code description units_sold revenue order_count
<chr> <chr> <dbl> <dbl> <int>
1 23843 PAPER CRAFT , LITTLE BIRDIE 80995 168470. 1
2 23166 MEDIUM CERAMIC TOP STORAGE JAR 78033 81701. 247
3 84077 WORLD WAR 2 GLIDERS ASSTD DESIGNS 55047 13842. 535
4 85099B JUMBO BAG RED RETROSPOT 48474 94340. 2089
5 85123A WHITE HANGING HEART T-LIGHT HOLDER 37599 104340. 2189
6 22197 POPCORN HOLDER 36761 34299. 803
7 84879 ASSORTED COLOUR BIRD ORNAMENT 36461 59095. 1455
8 21212 PACK OF 72 RETROSPOT CAKE CASES 36419 21259. 1320
9 23084 RABBIT NIGHT LIGHT 30788 66965. 994
10 22492 MINI PAINT SET VINTAGE 26633 16938. 380
Produtos com maior frequência de compra:
products |>slice_max(order_count, n =10, with_ties =FALSE)
# A tibble: 10 × 5
stock_code description units_sold revenue order_count
<chr> <chr> <dbl> <dbl> <int>
1 85123A WHITE HANGING HEART T-LIGHT HOLDER 37599 104340. 2189
2 85099B JUMBO BAG RED RETROSPOT 48474 94340. 2089
3 22423 REGENCY CAKESTAND 3 TIER 13879 174485. 1988
4 47566 PARTY BUNTING 18295 99504. 1685
5 20725 LUNCH BAG RED RETROSPOT 19353 35752. 1564
6 84879 ASSORTED COLOUR BIRD ORNAMENT 36461 59095. 1455
7 22720 SET OF 3 CAKE TINS PANTRY DESIGN 7493 38158. 1385
8 21212 PACK OF 72 RETROSPOT CAKE CASES 36419 21259. 1320
9 20727 LUNCH BAG BLACK SKULL. 12240 22421. 1273
10 22457 NATURAL SLATE HEART CHALKBOARD 9172 28148. 1249
Os três rankings não são necessariamente iguais. Um produto pode aparecer em muitos pedidos, mas com poucas unidades por pedido, enquanto outro pode ter grande volume concentrado em poucas compras de atacado.
Países mais relevantes para o negócio
A relevância de um país é medida por faturamento, quantidade de pedidos e número de clientes identificados. O faturamento ordena a tabela, enquanto as demais colunas ajudam a distinguir mercados amplos de vendas concentradas em poucos compradores.
countries <- sales |>group_by(country) |>summarise(revenue =sum(revenue),order_count =n_distinct(invoice_no),customer_count =n_distinct(customer_id, na.rm =TRUE),units_sold =sum(quantity),.groups ="drop" )top_countries <- countries |>slice_max(revenue, n =10, with_ties =FALSE)top_countries
Como registros sem customer_id não podem ser atribuídos a uma pessoa específica, eles são excluídos somente desta agregação. Isso não os remove das demais análises de vendas válidas.
O enunciado da Aula 3 não pedia isto, mas o mesmo dataset permite outras perguntas de gestão. Três exemplos, cada um resolvido com um pipeline tidyverse sobre as vendas válidas isoladas na seção 6.
Produtos mais vendidos por trimestre, no Reino Unido
A maior parte das vendas vem do Reino Unido, sede da empresa. Faz sentido perguntar: quais produtos lideram em unidades vendidas, trimestre a trimestre, só nesse mercado?
# A tibble: 25 × 5
quarter stock_code description units_sold revenue
<chr> <chr> <chr> <dbl> <dbl>
1 2010-Q4 84077 WORLD WAR 2 GLIDERS ASSTD DESIGNS 4571 993.
2 2010-Q4 85123A WHITE HANGING HEART T-LIGHT HOLDER 3633 10110.
3 2010-Q4 22834 HAND WARMER BABUSHKA DESIGN 3322 4482.
4 2010-Q4 21212 PACK OF 72 RETROSPOT CAKE CASES 3266 2266.
5 2010-Q4 22693 GROW A FLYTRAP OR SUNFLOWER IN TIN 2617 2509.
6 2011-Q1 23166 MEDIUM CERAMIC TOP STORAGE JAR 74215 77184.
7 2011-Q1 85099B JUMBO BAG RED RETROSPOT 10224 19241.
8 2011-Q1 85123A WHITE HANGING HEART T-LIGHT HOLDER 8874 24541.
9 2011-Q1 84077 WORLD WAR 2 GLIDERS ASSTD DESIGNS 8609 2067.
10 2011-Q1 17003 BROCADE RING PURSE 6903 1484.
# ℹ 15 more rows
country == "United Kingdom" só funciona porque conferimos antes o rótulo exato do país nos dados; a Aula 2 já ensinou a não supor esse tipo de valor sem checar.
Concentração de receita: quantos clientes sustentam o faturamento?
Uma leitura comum em gestão comercial é a curva de Pareto: uma fração pequena de clientes costuma responder por boa parte da receita. Vale checar se isso se confirma aqui.
customer_revenue <- customers |>select(customer_id, revenue) |>arrange(desc(revenue)) |>mutate(customer_rank =row_number(),cumulative_share =cumsum(revenue) /sum(revenue) )customers_for_80pct <- customer_revenue |>filter(cumulative_share >=0.8) |>slice_head(n =1) |>pull(customer_rank)total_customers <-nrow(customer_revenue)cat(glue::glue("{customers_for_80pct} de {total_customers} clientes ","({scales::percent(customers_for_80pct / total_customers, accuracy = 0.1)}) ","respondem por 80% do faturamento das vendas válidas.\n"))
1133 de 4338 clientes (26.1%) respondem por 80% do faturamento das vendas válidas.
A seção 4 já apontou sazonalidade mensal como pergunta em aberto. Antes de chegar lá, um recorte mais fino: existe um dia da semana melhor para vendas?
Vale notar o que falta nos dados: se um dia da semana não aparece na tabela acima, é porque não há faturas registradas nele, o que já é, por si só, um achado sobre a operação.
Recapitulando o que este roteiro entrega:
Reproduziu em R e tidyverse toda a análise da Aula 3: visão geral, qualidade dos dados, vendas válidas, valor médio dos pedidos, rankings de produtos, países e clientes, e impacto de cancelamentos;
Foi além do enunciado original com três perguntas novas: produtos líderes por trimestre no Reino Unido, concentração de receita por cliente e sazonalidade por dia da semana.
O que ainda falta é o mesmo que faltava na Aula 3: sazonalidade mensal completa, investigação direta dos outliers e as recomendações práticas para a gestão comercial.
Para casa
Complete, no seu notebook, os pontos que este roteiro ainda não cobre: sazonalidade mensal completa e a investigação direta dos outliers levantados nas perguntas acima;
Escreva, em células de texto, os critérios que você usou para definir venda válida, cancelamento e registro anômalo, e compare com as respostas que você deu na Aula 3: mudaram?
Encerre com pelo menos três recomendações práticas para a gestão comercial, apoiadas nos números que você calculou;
Antes de considerar o notebook pronto, rode a partir de uma sessão R limpa (Restart R and Run All Chunks, o equivalente R ao Restart Kernel and Run All), e só então commite no projeto-vendas com uma mensagem descritiva.
A ferramenta muda. O raciocínio que você defende sobre os dados, não.