Aula 3 - Análise de Vendas

Data de Publicação

14 de setembro de 2026

Motivação

Nas duas últimas aulas você montou a bancada e aprendeu a interrogar dados. Na Aula 1, criou o projeto-vendas com Git e um ambiente isolado. Na Aula 2, carregou o Online Retail, executou o ritual de inspeção inicial e diagnosticou os primeiros problemas de qualidade: cancelamentos disfarçados de quantidade negativa, preços zerados, duplicatas. Você já sabe olhar para os dados. Falta agora responder, com eles, a uma pergunta de negócio de verdade.

É isso que este roteiro propõe, e ele é diferente dos dois anteriores em um ponto importante: aqui não há uma sequência única de células que leva a um resultado combinado de antemão. Há um enunciado, um ponto de partida em código que carrega e prepara a base exatamente como você a deixou na Aula 2, e um espaço de decisões que só você pode tomar. Diagnosticar problemas, como fizemos na aula passada, é relativamente objetivo. Decidir o que fazer com eles, para responder a uma pergunta de gestão, é uma escolha analítica, e escolhas analíticas precisam ser justificadas, não apenas executadas.

Esse é o salto desta aula: sair do papel de quem descreve os dados para o papel de quem os usa para recomendar uma ação.

Objetivos de aprendizagem

Ao final deste roteiro, você será capaz de:

  1. Traduzir uma pergunta de negócio em critérios de análise verificáveis nos dados;
  2. Reaplicar o ritual de inspeção e diagnóstico da Aula 2 para separar vendas válidas de cancelamentos e registros anômalos;
  3. Construir indicadores de negócio (faturamento, ticket médio, produtos e países mais relevantes) a partir de dados tabulares;
  4. Quantificar o impacto financeiro de cancelamentos e devoluções sobre o resultado;
  5. Documentar e justificar, por escrito, os critérios usados na análise, transformando resultados em recomendações práticas.
Como usar este roteiro

Este roteiro pressupõe o projeto-vendas das Aulas 1 e 2 funcionando, com o Online Retail já baixado em data/. As primeiras células carregam e preparam a base, exatamente o ponto em que a Aula 2 parou; a partir daí, o enunciado é o guia. Este script não é a análise completa: é a fundação sobre a qual as respostas ao enunciado devem ser construídas, célula a célula, no seu próprio notebook. Ao final, três perguntas ajudam a testar se os seus critérios estão sólidos antes de entregar.

1. Retomando o projeto

Como sempre, comece verificando onde o projeto parou:

# Onde paramos? O git log conta a historia ate aqui:
!cd ./projeto-vendas && git log --oneline

# E o estado atual? Espera-se uma area de trabalho limpa:
!cd ./projeto-vendas && git status
bbe6150 (HEAD -> master) 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 seis commits das Aulas 1 e 2 e o status diz que não há nada a commitar, o ambiente e os dados estão prontos. Nenhuma biblioteca nova é necessária hoje: pandas e openpyxl, instalados na Aula 2, bastam.

2. O enunciado

O gestor de vendas da empresa precisa entender o desempenho comercial do último ano e identificar oportunidades para aumentar o faturamento. Com base no histórico de transações, quais produtos, clientes, países e períodos devem ser priorizados em nossa estratégia de vendas, e quais fatores estão prejudicando o resultado, como cancelamentos e devoluções?

Para responder, a análise deverá considerar:

  • Produtos com maior faturamento, volume e frequência de compra;
  • Países e clientes mais relevantes para o negócio;
  • Valor médio dos pedidos;
  • Impacto financeiro de cancelamentos e devoluções;
  • Possíveis outliers ou problemas de qualidade dos dados;
  • Evolução mensal do faturamento e identificação de sazonalidade;

Os cientistas de dados deverão definir e justificar os critérios utilizados para diferenciar vendas válidas, cancelamentos, devoluções e registros anômalos.

3. Preparando os dados

O ponto de partida é o mesmo carregamento da Aula 2, com duas colunas novas calculadas a partir das originais: a receita de cada item e um sinalizador de cancelamento, derivado do prefixo C no número da fatura, exatamente como você confirmou na aula passada.

from pathlib import Path

import pandas as pd

data_path = Path("projeto-vendas") / "data" / "online_retail.xlsx"

column_names = {
    "InvoiceNo": "invoice_no",
    "StockCode": "stock_code",
    "Description": "description",
    "Quantity": "quantity",
    "InvoiceDate": "invoice_date",
    "UnitPrice": "unit_price",
    "CustomerID": "customer_id",
    "Country": "country",
}

retail = pd.read_excel(data_path).rename(columns=column_names)

Com a base carregada, as mesmas duas colunas calculadas da Aula 2:

retail["invoice_date"] = pd.to_datetime(retail["invoice_date"], errors="coerce")
retail["revenue"] = retail["quantity"] * retail["unit_price"]
retail["is_cancelled"] = retail["invoice_no"].str.startswith("C", na=False)
print("Arquivo carregado com sucesso.")
print(f"Dimensão da base: {retail.shape[0]:,} linhas × {retail.shape[1]} colunas")
Arquivo carregado com sucesso.
Dimensão da base: 541,909 linhas × 10 colunas

4. Visão geral da base

Antes de qualquer indicador de negócio, refaça o retrato geral que a Aula 2 ensinou a tirar: tamanho, tipos e uma primeira olhada nos números.

print("=" * 60)
print("VISAO GERAL")
print("=" * 60)

print(f"Numero de linhas: {retail.shape[0]:,}")
print(f"Numero de colunas originais: {retail.shape[1] - 2}")
print(f"Data inicial: {retail['invoice_date'].min()}")
print(f"Data final: {retail['invoice_date'].max()}")

print(f"Quantidade de faturas: {retail['invoice_no'].nunique():,}")
print(f"Quantidade de clientes: {retail['customer_id'].nunique():,}")
print(f"Quantidade de produtos: {retail['stock_code'].nunique():,}")
print(f"Quantidade de paises: {retail['country'].nunique():,}")
============================================================
VISAO GERAL
============================================================
Numero de linhas: 541,909
Numero de colunas originais: 8
Data inicial: 2010-12-01 08:26:00
Data final: 2011-12-09 12:50:00
Quantidade de faturas: 25,900
Quantidade de clientes: 4,372
Quantidade de produtos: 4,070
Quantidade de paises: 38

Colunas e tipos de dados:

print(retail.dtypes)
invoice_no              object
stock_code              object
description             object
quantity                 int64
invoice_date    datetime64[ns]
unit_price             float64
customer_id            float64
country                 object
revenue                float64
is_cancelled              bool
dtype: object

Primeiras linhas:

display(retail.head())
invoice_no stock_code description quantity invoice_date unit_price customer_id country revenue is_cancelled
0 536365 85123A WHITE HANGING HEART T-LIGHT HOLDER 6 2010-12-01 08:26:00 2.55 17850.0 United Kingdom 15.30 False
1 536365 71053 WHITE METAL LANTERN 6 2010-12-01 08:26:00 3.39 17850.0 United Kingdom 20.34 False
2 536365 84406B CREAM CUPID HEARTS COAT HANGER 8 2010-12-01 08:26:00 2.75 17850.0 United Kingdom 22.00 False
3 536365 84029G KNITTED UNION FLAG HOT WATER BOTTLE 6 2010-12-01 08:26:00 3.39 17850.0 United Kingdom 20.34 False
4 536365 84029E RED WOOLLY HOTTIE WHITE HEART. 6 2010-12-01 08:26:00 3.39 17850.0 United Kingdom 20.34 False

Estatísticas das variáveis numéricas:

display(retail[["quantity", "unit_price", "revenue"]].describe().round(2))
quantity unit_price revenue
count 541909.00 541909.00 541909.00
mean 9.55 4.61 17.99
std 218.08 96.76 378.81
min -80995.00 -11062.06 -168469.60
25% 1.00 1.25 3.40
50% 3.00 2.08 9.75
75% 10.00 4.13 17.40
max 80995.00 38970.00 168469.60

5. Qualidade dos dados

Refaça também o diagnóstico da Aula 2, agora consolidado em um único quadro: ausentes, duplicatas e a contagem dos suspeitos já conhecidos (cancelamentos, quantidades e preços fora do esperado).

print("=" * 60)
print("QUALIDADE DOS DADOS")
print("=" * 60)
============================================================
QUALIDADE DOS DADOS
============================================================

Valores ausentes, coluna a coluna:

quality = (
    pd.DataFrame(
        {
            "missing_values": retail.isna().sum(),
            "missing_percentage": retail.isna().mean().mul(100),
        }
    )
    .sort_values("missing_percentage", ascending=False)
    .rename_axis("column")
)

display(quality.round(2))
missing_values missing_percentage
column
customer_id 135080 24.93
description 1454 0.27
stock_code 0 0.00
invoice_no 0 0.00
invoice_date 0 0.00
quantity 0 0.00
unit_price 0 0.00
country 0 0.00
revenue 0 0.00
is_cancelled 0 0.00

Duplicatas, considerando somente as colunas originais:

source_columns = [
    "invoice_no",
    "stock_code",
    "description",
    "quantity",
    "invoice_date",
    "unit_price",
    "customer_id",
    "country"
]

duplicate_count = retail.duplicated(subset=source_columns).sum()

print(f"Linhas exatamente duplicadas: {duplicate_count:,}")
print(f"Percentual de duplicatas: {duplicate_count / len(retail) * 100:.2f}%")
Linhas exatamente duplicadas: 5,268
Percentual de duplicatas: 0.97%

Registros potencialmente problemáticos:

issues = pd.Series(
    {
        "Faturas canceladas": retail["is_cancelled"].sum(),
        "Quantidade negativa": retail["quantity"].lt(0).sum(),
        "Quantidade igual a zero": retail["quantity"].eq(0).sum(),
        "Preço negativo": retail["unit_price"].lt(0).sum(),
        "Preço igual a zero": retail["unit_price"].eq(0).sum(),
        "Descrição ausente": retail["description"].isna().sum(),
        "Cliente não identificado": retail["customer_id"].isna().sum(),
        "Data inválida ou ausente": retail["invoice_date"].isna().sum(),
    },
    name="quantity",
)

issues.to_frame()
quantity
Faturas canceladas 9288
Quantidade negativa 10624
Quantidade igual a zero 0
Preço negativo 2
Preço igual a zero 2515
Descrição ausente 1454
Cliente não identificado 135080
Data inválida ou ausente 0

Valores extremos, quantidade e preço unitário:

percentiles = [0.01, 0.25, 0.50, 0.75, 0.99]
retail["quantity"].describe(percentiles=percentiles).round(2)
count    541909.00
mean          9.55
std         218.08
min      -80995.00
1%           -2.00
25%           1.00
50%           3.00
75%          10.00
99%         100.00
max       80995.00
Name: quantity, dtype: float64
retail["unit_price"].describe(percentiles=percentiles).round(2)
count    541909.00
mean          4.61
std          96.76
min      -11062.06
1%            0.19
25%           1.25
50%           2.08
75%           4.13
99%          18.00
max       38970.00
Name: unit_price, dtype: float64

6. Isolando as vendas válidas

Aqui começa a parte que o enunciado pede e que a Aula 2 deliberadamente não respondeu: o que conta como venda válida? O corte abaixo é um ponto de partida razoável, não a palavra final, exclui cancelamentos e linhas com quantidade ou preço não positivos, mas você pode (e deve) questioná-lo diante do que encontrar.

valid_sales_filter = (
    ~retail["is_cancelled"]
    & retail["quantity"].gt(0)
    & retail["unit_price"].gt(0)
)

1sales = retail.loc[valid_sales_filter].copy()
1
.copy() cria um DataFrame independente para as vendas válidas e evita alterações acidentais em retail nas etapas seguintes.
print("=" * 60)
print("VENDAS POSITIVAS")
print("=" * 60)

print(f"Linhas de vendas positivas: {len(sales):,}")
print(f"Pedidos validos: {sales['invoice_no'].nunique():,}")
print(f"Clientes identificados: {sales['customer_id'].nunique():,}")
print(f"Unidades vendidas: {sales['quantity'].sum():,}")
print(f"Faturamento bruto: £ {sales['revenue'].sum():,.2f}")
============================================================
VENDAS POSITIVAS
============================================================
Linhas de vendas positivas: 530,104
Pedidos validos: 19,960
Clientes identificados: 4,338
Unidades vendidas: 5,588,376
Faturamento bruto: £ 10,666,684.54

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.

orders = (
    sales
1    .groupby("invoice_no", as_index=False)
    .agg(
        order_date=("invoice_date", "min"),
        customer_id=("customer_id", "first"),
        country=("country", "first"),
        item_quantity=("quantity", "sum"),
        order_value=("revenue", "sum"),
2    )
)
1
Cada grupo representa uma fatura, portanto uma linha da tabela resultante representa um pedido.
2
A agregação nomeada documenta, para cada nova coluna, a variável de origem e a função aplicada.
order_value_summary = pd.Series(
    {
        "ticket_medio": orders["order_value"].mean(),
        "ticket_mediano": orders["order_value"].median(),
    },
    name="valor_em_libras",
)

order_value_summary.round(2).to_frame()
valor_em_libras
ticket_medio 534.40
ticket_mediano 303.84

Distribuição do valor dos pedidos:

order_percentiles = [0.25, 0.50, 0.75, 0.90, 0.99]
orders["order_value"].describe(percentiles=order_percentiles).round(2)
count     19960.00
mean        534.40
std        1780.49
min           0.38
25%         152.51
50%         303.84
75%         495.62
90%         940.89
99%        4821.17
max      168469.60
Name: order_value, dtype: float64

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 = (
    sales
    .dropna(subset=["description"])
    .groupby(["stock_code", "description"], as_index=False)
    .agg(
        units_sold=("quantity", "sum"),
        revenue=("revenue", "sum"),
        order_count=("invoice_no", "nunique"),
    )
)

Produtos com maior faturamento:

top_products_by_revenue = products.nlargest(10, "revenue")
top_products_by_revenue.round(2)
stock_code description units_sold revenue order_count
4150 DOT DOTCOM POSTAGE 706 206248.77 706
1262 22423 REGENCY CAKESTAND 3 TIER 13879 174484.74 1988
2590 23843 PAPER CRAFT , LITTLE BIRDIE 80995 168469.60 1
3773 85123A WHITE HANGING HEART T-LIGHT HOLDER 37599 104340.29 2189
2669 47566 PARTY BUNTING 18295 99504.33 1685
3761 85099B JUMBO BAG RED RETROSPOT 48474 94340.05 2089
2045 23166 MEDIUM CERAMIC TOP STORAGE JAR 78033 81700.92 247
4151 M Manual 7224 78110.27 289
4153 POST POSTAGE 3150 78101.88 1126
1951 23084 RABBIT NIGHT LIGHT 30788 66964.99 994

Produtos com maior volume vendido:

top_products_by_volume = products.nlargest(10, "units_sold")
top_products_by_volume.round(2)
stock_code description units_sold revenue order_count
2590 23843 PAPER CRAFT , LITTLE BIRDIE 80995 168469.60 1
2045 23166 MEDIUM CERAMIC TOP STORAGE JAR 78033 81700.92 247
2769 84077 WORLD WAR 2 GLIDERS ASSTD DESIGNS 55047 13841.85 535
3761 85099B JUMBO BAG RED RETROSPOT 48474 94340.05 2089
3773 85123A WHITE HANGING HEART T-LIGHT HOLDER 37599 104340.29 2189
1051 22197 POPCORN HOLDER 36761 34298.87 803
2875 84879 ASSORTED COLOUR BIRD ORNAMENT 36461 59094.93 1455
370 21212 PACK OF 72 RETROSPOT CAKE CASES 36419 21259.10 1320
1951 23084 RABBIT NIGHT LIGHT 30788 66964.99 994
1327 22492 MINI PAINT SET VINTAGE 26633 16937.82 380

Produtos com maior frequência de compra:

top_products_by_frequency = products.nlargest(10, "order_count")
top_products_by_frequency.round(2)
stock_code description units_sold revenue order_count
3773 85123A WHITE HANGING HEART T-LIGHT HOLDER 37599 104340.29 2189
3761 85099B JUMBO BAG RED RETROSPOT 48474 94340.05 2089
1262 22423 REGENCY CAKESTAND 3 TIER 13879 174484.74 1988
2669 47566 PARTY BUNTING 18295 99504.33 1685
101 20725 LUNCH BAG RED RETROSPOT 19353 35752.01 1564
2875 84879 ASSORTED COLOUR BIRD ORNAMENT 36461 59094.93 1455
1552 22720 SET OF 3 CAKE TINS PANTRY DESIGN 7493 38158.39 1385
370 21212 PACK OF 72 RETROSPOT CAKE CASES 36419 21259.10 1320
104 20727 LUNCH BAG BLACK SKULL. 12240 22421.21 1273
1295 22457 NATURAL SLATE HEART CHALKBOARD 9172 28147.96 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
    .groupby("country", as_index=False)
    .agg(
        revenue=("revenue", "sum"),
        order_count=("invoice_no", "nunique"),
        customer_count=("customer_id", "nunique"),
        units_sold=("quantity", "sum"),
    )
)

top_countries = countries.nlargest(10, "revenue")
top_countries.round(2)
country revenue order_count customer_count units_sold
36 United Kingdom 9025222.08 18019 3920 4662390
24 Netherlands 285446.34 94 9 200361
10 EIRE 283453.96 288 3 147173
14 Germany 228867.14 457 94 119261
13 France 209715.11 392 87 112103
0 Australia 138521.31 57 9 83901
31 Spain 61577.11 90 30 27940
33 Switzerland 57089.90 54 21 30629
3 Belgium 41196.34 98 25 23237
32 Sweden 38378.33 36 8 36083

Clientes mais relevantes para o negócio

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.

identified_sales = sales.dropna(subset=["customer_id"])

customers = (
    identified_sales
    .groupby("customer_id", as_index=False)
    .agg(
        revenue=("revenue", "sum"),
        order_count=("invoice_no", "nunique"),
        units_sold=("quantity", "sum"),
        country=("country", "first"),
    )
)

top_customers = customers.nlargest(10, "revenue")
top_customers.round(2)
customer_id revenue order_count units_sold country
1689 14646.0 280206.02 73 196915 Netherlands
4201 18102.0 259657.30 60 64124 United Kingdom
3728 17450.0 194550.79 46 69993 United Kingdom
3008 16446.0 168472.50 2 80997 United Kingdom
1879 14911.0 143825.06 201 80265 EIRE
55 12415.0 124914.53 21 77374 Australia
1333 14156.0 117379.63 55 57885 EIRE
3771 17511.0 91062.38 31 64549 United Kingdom
2702 16029.0 81024.84 63 40208 United Kingdom
0 12346.0 77183.60 1 74215 United Kingdom

8. Cancelamentos e devoluções

Por fim, o enunciado pede o impacto financeiro de cancelamentos e devoluções, o lado espelhado das vendas válidas: tudo o que foi excluído do faturamento bruto calculado na seção 6.

cancellation_filter = retail["is_cancelled"] | retail["quantity"].lt(0)
cancellations = retail.loc[cancellation_filter].copy()

refunded_amount = abs(retail.loc[retail["revenue"] < 0, "revenue"].sum())
print(f"Faturas canceladas: {retail.loc[retail['is_cancelled'], 'invoice_no'].nunique():,}")
print(f"Linhas com quantidade negativa: {(retail['quantity'] < 0).sum():,}")
print(f"Valor absoluto das devolucoes/ajustes: £ {refunded_amount:,.2f}")
print(f"Receita liquida de toda a base: £ {retail['revenue'].sum():,.2f}")
Faturas canceladas: 3,836
Linhas com quantidade negativa: 10,624
Valor absoluto das devolucoes/ajustes: £ 918,936.61
Receita liquida de toda a base: £ 9,747,747.93

Recapitulando o que este roteiro entrega:

  • Retomou o projeto-vendas e os dados exatamente como a Aula 2 os deixou;
  • Refez a visão geral e o diagnóstico de qualidade da base completa;
  • Propôs um critério inicial de vendas válidas, separando-as dos cancelamentos;
  • Calculou o ticket médio e comparou os produtos por faturamento, volume e frequência de compra;
  • Identificou os países e clientes mais relevantes por meio de métricas comerciais explícitas;
  • Quantificou o impacto financeiro de cancelamentos e devoluções.

O que ainda falta é o que transforma um script em uma análise completa: sazonalidade mensal, investigação dos outliers e, principalmente, as recomendações práticas para a gestão comercial que o enunciado pede.

Perguntas para consolidar

Três perguntas para testar seus critérios antes de considerar a análise pronta. Elas não têm uma resposta única certa: o que importa é que você consiga justificar a escolha que fez.

Pergunta 1: cancelamento é a mesma coisa que devolução?

O critério de vendas válidas da seção 6 trata toda linha com is_cancelled verdadeiro ou quantity negativa da mesma forma. Isso é preciso o suficiente para o enunciado, que pede o impacto de cancelamentos e devoluções separadamente?

Releia o dicionário de variáveis da Aula 2: invoice_no começando com C é a convenção do sistema para cancelamento, registrado no mesmo momento da venda. Uma devolução, no mundo real, costuma acontecer depois, como um evento separado, e pode ou não gerar uma fatura com C. Verifique nos próprios dados se todas as linhas com quantity negativa têm invoice_no começando com C, como fizemos na Aula 2; se houver exceções, elas podem ser a diferença entre as duas categorias que o enunciado pede para separar.

Pergunta 2: quando um outlier é erro e quando é atacado legítimo?

O describe da seção 5 mostra valores de quantity na casa das dezenas de milhares em uma única linha. Excluir essas linhas do faturamento por serem outliers, sem verificar mais nada, é uma decisão segura?

A documentação do dataset já avisa: há muitos clientes atacadistas nesta base. Antes de descartar uma linha como erro, olhe o customer_id e o country dela: um mesmo cliente comprando o mesmo produto em grande volume, repetidamente, é um padrão de atacado, não um erro de digitação isolado. Descartar esses registros sem checar pode subestimar exatamente os clientes mais relevantes que o enunciado pede para identificar.

Pergunta 3: qual recorte de “vendas válidas” você vai defender?

Além de cancelamentos, a seção 6 também exclui quantity igual a zero e unit_price igual a zero. Existem linhas com preço zero e customer_id ausente ao mesmo tempo, como a Aula 2 pediu para você investigar?

Preço zero sem cliente identificado tem cara de brinde, amostra grátis ou ajuste interno de estoque, não de uma venda. Se for esse o caso, mantê-las no cálculo de faturamento infla artificialmente o volume sem representar receita real. O ponto não é decorar essa resposta, mas perceber que cada exclusão do filtro precisa de uma evidência nos próprios dados, não de uma suposição.

Para casa

  1. Complete, no seu notebook, os pontos do enunciado que este roteiro ainda não cobre: evolução mensal do faturamento e investigação direta dos outliers levantados nas perguntas acima;
  2. Escreva, em células de texto, os critérios que você usou para definir venda válida, cancelamento e registro anômalo, e por quê;
  3. Encerre com pelo menos três recomendações práticas para a gestão comercial, apoiadas nos números que você calculou;
  4. Antes de considerar o notebook pronto, use Restart Kernel and Run All, como a Aula 1 ensinou, e só então commite no projeto-vendas com uma mensagem descritiva.

Uma análise só vira decisão quando alguém assume a responsabilidade pelos critérios que usou. Esconder-se atrás do código é fácil; justificar a escolha é o trabalho de verdade.