Analisar um dataset de vendas reais para desvendar os fatores que impulsionam o desempenho ao longo do tempo, combinando análise temporal avançada e segmentação detalhada, com o intuito de extrair insights acionáveis e preparar uma base robusta para modelos preditivos de Machine Learning.
Escolhi este projeto como o ápice da minha jornada em EDA porque vendas são o coração de muitos negócios, e entender seus padrões é um desafio real e universal. Quis ir além do básico, explorando como o tempo, os clientes e as condições afetam os resultados, e transformar dados brutos em uma história que não só informe, mas também inspire soluções práticas e tecnológicas.
# Initial Setup
import pandas as pd # Data manipulation and analysis
import numpy as np # Numerical operations
import seaborn as sns # Statistical visualizations
import matplotlib.pyplot as plt # Basic plotting
import plotly.express as px # Interactive, simple charts
import plotly.graph_objects as go # Customized interactive visualizations
# Environment Configuration
sns.set_style("whitegrid") # Set clean visual style for charts
plt.rcParams["figure.figsize"] = (10, 6) # Default figure size for consistency
Para descobrir o que impulsiona, influencia e aumenta as vendas, escolhi ferramentas robustas para análise, exploração, tratamento e visualização de dados, combinadas com insights estatísticos de explorações anteriores.
# Reading the Superstore Sales Dataset
df = pd.read_csv("superstoresales.csv") # Load sales data from CSV file
# Gathering Basic Information
df.info() # Display dataset structure and data types
<class 'pandas.core.frame.DataFrame'> RangeIndex: 9800 entries, 0 to 9799 Data columns (total 18 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Row ID 9800 non-null int64 1 Order ID 9800 non-null object 2 Order Date 9800 non-null object 3 Ship Date 9800 non-null object 4 Ship Mode 9800 non-null object 5 Customer ID 9800 non-null object 6 Customer Name 9800 non-null object 7 Segment 9800 non-null object 8 Country 9800 non-null object 9 City 9800 non-null object 10 State 9800 non-null object 11 Postal Code 9789 non-null float64 12 Region 9800 non-null object 13 Product ID 9800 non-null object 14 Category 9800 non-null object 15 Sub-Category 9800 non-null object 16 Product Name 9800 non-null object 17 Sales 9800 non-null float64 dtypes: float64(2), int64(1), object(15) memory usage: 1.3+ MB
A análise inicial revelou ações necessárias:
Row ID
, redundante com o índice do Pandas, para simplificar o dataset.Postal Code
e Sales
são numéricas, limitando estatísticas aos valores de venda e exigindo transformações em outras colunas.Postal Code
, o que demanda estratégias específicas de tratamento.# Previewing the Dataset
df.head() # Show first 5 rows to understand data layout
Row ID | Order ID | Order Date | Ship Date | Ship Mode | Customer ID | Customer Name | Segment | Country | City | State | Postal Code | Region | Product ID | Category | Sub-Category | Product Name | Sales | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | CA-2017-152156 | 08/11/2017 | 11/11/2017 | Second Class | CG-12520 | Claire Gute | Consumer | United States | Henderson | Kentucky | 42420.0 | South | FUR-BO-10001798 | Furniture | Bookcases | Bush Somerset Collection Bookcase | 261.9600 |
1 | 2 | CA-2017-152156 | 08/11/2017 | 11/11/2017 | Second Class | CG-12520 | Claire Gute | Consumer | United States | Henderson | Kentucky | 42420.0 | South | FUR-CH-10000454 | Furniture | Chairs | Hon Deluxe Fabric Upholstered Stacking Chairs,... | 731.9400 |
2 | 3 | CA-2017-138688 | 12/06/2017 | 16/06/2017 | Second Class | DV-13045 | Darrin Van Huff | Corporate | United States | Los Angeles | California | 90036.0 | West | OFF-LA-10000240 | Office Supplies | Labels | Self-Adhesive Address Labels for Typewriters b... | 14.6200 |
3 | 4 | US-2016-108966 | 11/10/2016 | 18/10/2016 | Standard Class | SO-20335 | Sean O'Donnell | Consumer | United States | Fort Lauderdale | Florida | 33311.0 | South | FUR-TA-10000577 | Furniture | Tables | Bretford CR4500 Series Slim Rectangular Table | 957.5775 |
4 | 5 | US-2016-108966 | 11/10/2016 | 18/10/2016 | Standard Class | SO-20335 | Sean O'Donnell | Consumer | United States | Fort Lauderdale | Florida | 33311.0 | South | OFF-ST-10000760 | Office Supplies | Storage | Eldon Fold 'N Roll Cart System | 22.3680 |
# Checking the type of each column
df.dtypes
Row ID int64 Order ID object Order Date object Ship Date object Ship Mode object Customer ID object Customer Name object Segment object Country object City object State object Postal Code float64 Region object Product ID object Category object Sub-Category object Product Name object Sales float64 dtype: object
# Analyzing Numerical Distribution
df.describe() # Summarize statistical properties of all numerical columns
Row ID | Postal Code | Sales | |
---|---|---|---|
count | 9800.000000 | 9789.000000 | 9800.000000 |
mean | 4900.500000 | 55273.322403 | 230.769059 |
std | 2829.160653 | 32041.223413 | 626.651875 |
min | 1.000000 | 1040.000000 | 0.444000 |
25% | 2450.750000 | 23223.000000 | 17.248000 |
50% | 4900.500000 | 58103.000000 | 54.490000 |
75% | 7350.250000 | 90008.000000 | 210.605000 |
max | 9800.000000 | 99301.000000 | 22638.480000 |
Algumas análises iniciais revelam informações valiosas:
Sales
ou Postal Code
negativos)Vamos explorar os nomes das colunas para identificar relações iniciais e planejar análises futuras.
# Checking Column Names for Initial Pattern Identification
df.columns # List all column names to explore potential relationships
Index(['Row ID', 'Order ID', 'Order Date', 'Ship Date', 'Ship Mode', 'Customer ID', 'Customer Name', 'Segment', 'Country', 'City', 'State', 'Postal Code', 'Region', 'Product ID', 'Category', 'Sub-Category', 'Product Name', 'Sales'], dtype='object')
É possível idenficiar uma coluna com nomes (Customer Name
). Apesar de fictícios, por ética e aplicabilidade real, optarei por removê-la, usando Customer ID
para relações futuras.
A partir dos nomes das colunas, destaco relações iniciais:
Order Date
, Ship Date
) combinadas com parâmetros como cidade, país, segmento e região podem revelar distribuições e períodos ideais para campanhas, com datas como forte variável independente.Product ID
), segmentos (Segment
) e categorias (Category
) versus regiões (Region
) mostram potencial para análises promissoras.Ship Mode
, datas) pode influenciar vendas por região, sugerindo relações a explorar.A riqueza e variedade do dataset, com múltiplas combinações possíveis, tornam este projeto relevante, aplicável e cheio de potencial.
Vamos começar com os os passos que já determinamos anteriormente:
Row ID
e Customer Name
E depois vamos seguir com duplicatas e outliers
# Dropping Unnecessary Columns
df.drop(columns=['Row ID', 'Customer Name'], axis=1, inplace=True) # Remove redundant and ethically sensitive columns
# Verifying Remaining Columns
df.columns # Display updated column list after removal
Index(['Order ID', 'Order Date', 'Ship Date', 'Ship Mode', 'Customer ID', 'Segment', 'Country', 'City', 'State', 'Postal Code', 'Region', 'Product ID', 'Category', 'Sub-Category', 'Product Name', 'Sales'], dtype='object')
Vamos transformar as datas em tipos de dados que podemos utilizar em nossa análise (datetime
).
Primeiro, confirmando quais colunas são de data:
# Checking the first row just to see wich columns have the data structure
df.head(1)
Order ID | Order Date | Ship Date | Ship Mode | Customer ID | Segment | Country | City | State | Postal Code | Region | Product ID | Category | Sub-Category | Product Name | Sales | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | CA-2017-152156 | 08/11/2017 | 11/11/2017 | Second Class | CG-12520 | Consumer | United States | Henderson | Kentucky | 42420.0 | South | FUR-BO-10001798 | Furniture | Bookcases | Bush Somerset Collection Bookcase | 261.96 |
Assim como o nome (Date
) sugere, temos duas colunas com data (Order Date
e Ship Date
), vamos modificá-las.
# Converting date columns to datetime format (dd/mm/yyyy)
df['Order Date'] = pd.to_datetime(df['Order Date'], format='%d/%m/%Y')
df['Ship Date'] = pd.to_datetime(df['Ship Date'], format='%d/%m/%Y')
# Checking
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 9800 entries, 0 to 9799 Data columns (total 16 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Order ID 9800 non-null object 1 Order Date 9800 non-null datetime64[ns] 2 Ship Date 9800 non-null datetime64[ns] 3 Ship Mode 9800 non-null object 4 Customer ID 9800 non-null object 5 Segment 9800 non-null object 6 Country 9800 non-null object 7 City 9800 non-null object 8 State 9800 non-null object 9 Postal Code 9789 non-null float64 10 Region 9800 non-null object 11 Product ID 9800 non-null object 12 Category 9800 non-null object 13 Sub-Category 9800 non-null object 14 Product Name 9800 non-null object 15 Sales 9800 non-null float64 dtypes: datetime64[ns](2), float64(2), object(12) memory usage: 1.2+ MB
Tudo certo, indo para os nulos agora.
Remover a quantidade de nulos (11 Postal Code
) não impactará significativamente um dataset com cerca de 10.000 entradas.
Mas vamos analisar os dados antes:
# Checking Entries with Null 'Postal Code'
df[df['Postal Code'].isna()] # Filter rows where 'Postal Code' is null
Order ID | Order Date | Ship Date | Ship Mode | Customer ID | Segment | Country | City | State | Postal Code | Region | Product ID | Category | Sub-Category | Product Name | Sales | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2234 | CA-2018-104066 | 2018-12-05 | 2018-12-10 | Standard Class | QJ-19255 | Corporate | United States | Burlington | Vermont | NaN | East | TEC-AC-10001013 | Technology | Accessories | Logitech ClearChat Comfort/USB Headset H390 | 205.03 |
5274 | CA-2016-162887 | 2016-11-07 | 2016-11-09 | Second Class | SV-20785 | Consumer | United States | Burlington | Vermont | NaN | East | FUR-CH-10000595 | Furniture | Chairs | Safco Contoured Stacking Chairs | 715.20 |
8798 | US-2017-150140 | 2017-04-06 | 2017-04-10 | Standard Class | VM-21685 | Home Office | United States | Burlington | Vermont | NaN | East | TEC-PH-10002555 | Technology | Phones | Nortel Meridian M5316 Digital phone | 1294.75 |
9146 | US-2017-165505 | 2017-01-23 | 2017-01-27 | Standard Class | CB-12535 | Corporate | United States | Burlington | Vermont | NaN | East | TEC-AC-10002926 | Technology | Accessories | Logitech Wireless Marathon Mouse M705 | 99.98 |
9147 | US-2017-165505 | 2017-01-23 | 2017-01-27 | Standard Class | CB-12535 | Corporate | United States | Burlington | Vermont | NaN | East | OFF-AR-10003477 | Office Supplies | Art | 4009 Highlighters | 8.04 |
9148 | US-2017-165505 | 2017-01-23 | 2017-01-27 | Standard Class | CB-12535 | Corporate | United States | Burlington | Vermont | NaN | East | OFF-ST-10001526 | Office Supplies | Storage | Iceberg Mobile Mega Data/Printer Cart | 1564.29 |
9386 | US-2018-127292 | 2018-01-19 | 2018-01-23 | Standard Class | RM-19375 | Consumer | United States | Burlington | Vermont | NaN | East | OFF-PA-10000157 | Office Supplies | Paper | Xerox 191 | 79.92 |
9387 | US-2018-127292 | 2018-01-19 | 2018-01-23 | Standard Class | RM-19375 | Consumer | United States | Burlington | Vermont | NaN | East | OFF-PA-10001970 | Office Supplies | Paper | Xerox 1881 | 12.28 |
9388 | US-2018-127292 | 2018-01-19 | 2018-01-23 | Standard Class | RM-19375 | Consumer | United States | Burlington | Vermont | NaN | East | OFF-AP-10000828 | Office Supplies | Appliances | Avanti 4.4 Cu. Ft. Refrigerator | 542.94 |
9389 | US-2018-127292 | 2018-01-19 | 2018-01-23 | Standard Class | RM-19375 | Consumer | United States | Burlington | Vermont | NaN | East | OFF-EN-10001509 | Office Supplies | Envelopes | Poly String Tie Envelopes | 2.04 |
9741 | CA-2016-117086 | 2016-11-08 | 2016-11-12 | Standard Class | QJ-19255 | Corporate | United States | Burlington | Vermont | NaN | East | FUR-BO-10004834 | Furniture | Bookcases | Riverside Palais Royal Lawyers Bookcase, Royal... | 4404.90 |
Alguns desses registros são outliers (valores de venda acima de Q3 + desvio padrão), e sem saber a quantidade total de outliers, eles podem ser valiosos para análise.
Possíveis abordagens:
Customer ID
ligados a esses Postal Code
nulos têm outros registros com localidade exata (caso seja única).Primeiro, vamos buscar outros registros desses Customer ID
pra encontrar um Postal Code
válido.
# Identifying Customer IDs with Null 'Postal Code'
null_customer_ids = df[df['Postal Code'] \
.isna()]['Customer ID'].unique() # Get unique Customer IDs with null Postal Code
# Checking Other Records for These Customer IDs
df[df['Customer ID']. \
isin(null_customer_ids)] \
[['Customer ID','Postal Code']] \
.head(10) # Filter all rows matching these Customer IDs and showing only the relevant information
Customer ID | Postal Code | |
---|---|---|
197 | VM-21685 | 7090.0 |
266 | CB-12535 | 27514.0 |
1151 | CB-12535 | 44221.0 |
1298 | QJ-19255 | 55106.0 |
1646 | VM-21685 | 19140.0 |
1647 | VM-21685 | 19140.0 |
1661 | QJ-19255 | 19143.0 |
1662 | QJ-19255 | 19143.0 |
1683 | CB-12535 | 94110.0 |
1758 | RM-19375 | 77095.0 |
Ao analisar os códigos postais de Customer ID
em entradas sem Postal Code
, notei que os valores variam, impedindo o preenchimento dos dados faltantes.
Restam algumas opções:
Postal Code
e usá-los com cautela nas análises futuras.Postal Code
nulo.Como as opções 2, 3 e 4 demandam esforço extra pra um projeto demonstrativo, e os 11 nulos representam menos de 1% do dataset, optei por removê-los, considerando o impacto mínimo.
# Removing all entries with null 'Postal Code'
df.dropna(subset=['Postal Code'], inplace=True) # Drop rows where Postal Code is null
# Checking for remaining nulls in 'Postal Code'
df['Postal Code'].isna().sum() # Sum the number of null values to verify removal
0
Vamos reavaliar o describe
pra checar mudanças significativas após a remoção:
# Reassessing Sales distribution after null removal
df['Sales'].describe() # Display updated statistical summary of Sales column
count 9789.000000 mean 230.116193 std 625.302079 min 0.444000 25% 17.248000 50% 54.384000 75% 210.392000 max 22638.480000 Name: Sales, dtype: float64
Ótimo, comparando média e desvio padrão antes e depois, a remoção dos nulos teve impacto estatístico mínimo, permitindo prosseguir sem preocupações.
Agora vamos verificar duplicatas:
# Checking for duplicate rows in the dataset
df.duplicated().sum() # Count total number of duplicated entries
1
Com apenas uma duplicata, seu impacto é estatisticamente desprezível.
Porém, considerando o contexto do dataset, um usuário pedir o mesmo produto, no mesmo lugar e dia, com o mesmo envio, é plausível — mas a diferença na ordem do pedido (Order ID
) sugere um erro de entrada, justificando a remoção.
# Removing duplicate rows
df.drop_duplicates(inplace=True) # Drop duplicate entries to ensure data integrity
# Verifying removal of duplicates
df.duplicated().sum() # Confirm no duplicates remain
0
Removido com sucesso, agora falta apenas os outliers.
sns.boxplot(data = df['Sales'])
plt.show()
A visualização em caixa não se mostrou muito útil, então podemos normalizar os dados ou verificar a quantidade de outliers antes, para pensar em uma estratégia.
Vamos primeiro analisar a quantidade de outliers:
# Calculating IQR for Sales to identify upper outliers
Q1 = df['Sales'].quantile(0.25) # First quartile (25%)
Q3 = df['Sales'].quantile(0.75) # Third quartile (75%)
IQR = Q3 - Q1 # Interquartile range
upper_bound = Q3 + 1.5 * IQR # Upper limit for outliers
# Counting upper outliers
df[df['Sales'] > upper_bound]['Sales'].count() # Number of values above upper bound
1145
Com mais de 10% das entradas em Sales
sendo outliers superiores, decidi investigar seu impacto antes de qualquer tratamento:
O volume alto sugere que esses valores podem refletir vendas reais e excepcionais, como picos sazonais. Aqui estão as opções consideradas:
Escolhi a primeira abordagem por priorizar a análise dos dados: ela permite comparar os datasets lado a lado e isolar os outliers pra identificar padrões específicos, como os cenários que levam a vendas de alto valor — um ponto particularmente valioso pra este estudo.
Mas a separação será feita em um passo futuro, primeiro vamos tratar o dataset de uma forma em que todas as alterações necessárias sejam feitas.
Pra manter consistência entre df
, df_outliers
e df_typical
, vamos automatizar as alterações: uma função atualizará os subconjuntos após cada mudança no dataset principal.
Primeiro passo: definir a função de separação e aplicar as features temporais iniciais em df
.
Primeiro passo: extrair componentes temporais de Order Date
pra criar colunas como dia da semana, mês e ano, permitindo explorar sazonalidade e tendências.
# Adding initial temporal features to df
df['Order Day of Week'] = df['Order Date'].dt.day_name() # Day of week (e.g., Monday)
df['Order Month'] = df['Order Date'].dt.month_name() # Month name (e.g., January)
df['Order Year'] = df['Order Date'].dt.year # Year (e.g., 2020)
# Checking
df.columns
Index(['Order ID', 'Order Date', 'Ship Date', 'Ship Mode', 'Customer ID', 'Segment', 'Country', 'City', 'State', 'Postal Code', 'Region', 'Product ID', 'Category', 'Sub-Category', 'Product Name', 'Sales', 'Order Day of Week', 'Order Month', 'Order Year'], dtype='object')
As novas colunas — Order Day of Week
, Order Month
e Order Year
— foram criadas com sucesso a partir de Order Date
. Agora podemos analisar vendas por dia da semana (ex.: "sextas vendem mais?") ou sazonalidade anual (ex.: "dezembro lidera?"). Os dados estão prontos pra primeiras visualizações temporais.
Próximo passo: adicionar features como trimestre e tempo de entrega com diferença entre Order Date
e Ship Date
pra aprofundar a análise temporal.
# Adding delivery time feature (difference between Ship Date and Order Date)
df['Delivery Time'] = (df['Ship Date'] - df['Order Date']).dt.days # Delivery time in days
# Adding quarter feature from Order Date
df['Order Quarter'] = df['Order Date'].dt.quarter # Quarter (1 to 4)
# Checking
df.head(1)
Order ID | Order Date | Ship Date | Ship Mode | Customer ID | Segment | Country | City | State | Postal Code | ... | Product ID | Category | Sub-Category | Product Name | Sales | Order Day of Week | Order Month | Order Year | Delivery Time | Order Quarter | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | CA-2017-152156 | 2017-11-08 | 2017-11-11 | Second Class | CG-12520 | Consumer | United States | Henderson | Kentucky | 42420.0 | ... | FUR-BO-10001798 | Furniture | Bookcases | Bush Somerset Collection Bookcase | 261.96 | Wednesday | November | 2017 | 3 | 4 |
1 rows × 21 columns
As features Delivery Time
e Order Quarter
foram adicionadas com sucesso: o tempo de entrega (em dias) mostra o intervalo entre pedido e envio, enquanto o trimestre divide o ano em Q1 a Q4.
Agora, vamos criar colunas de médias móveis de vendas e features segmentadas, como ticket médio e vendas por região. Combinações serão exploradas só nas visualizações. Mas primeiro iremos separar os datasets, as mudanças futuras irão afetar cada dataset de uma forma diferente.
# Creating two datasets
df_outliers = df[df['Sales'] > upper_bound] # Dataset with upper outliers
df_typical = df[df['Sales'] <= upper_bound] # Dataset with typical values
# Resetting index for the outliers dataset
df_outliers.reset_index(drop=True, inplace=True) # Reindex outliers subset
# Resetting index for the typical dataset
df_typical.reset_index(drop=True, inplace=True) # Reindex typical values subset
Próximo passo: adicionar Sales_Moving_Avg
, Avg_Ticket_Customer
e Sales_per_Region
.
def sales_moving_avg(dataframe):
# Ensuring df is sorted by Order Date for correct moving average
dataframe = dataframe.sort_values('Order Date') # Sort by Order Date chronologically
# Aggregating Sales by Order Date
df_daily = dataframe.groupby('Order Date')['Sales'].sum().reset_index() # Sum Sales per day
# Calculating moving average of daily Sales (7-day window)
df_daily['Sales_Moving_Avg'] = df_daily['Sales'].rolling(window=7, min_periods=1).mean()
# Merging back to original df (optional, if you want the column in df)
dataframe = dataframe.merge(df_daily[['Order Date', 'Sales_Moving_Avg']], on='Order Date', how='left')
return dataframe
df_typical = sales_moving_avg(df_typical)
df_outliers = sales_moving_avg(df_outliers)
# Checking
df_outliers[['Order Date','Sales_Moving_Avg']].head(10)
Order Date | Sales_Moving_Avg | |
---|---|---|
0 | 2015-01-06 | 3939.760000 |
1 | 2015-01-06 | 3939.760000 |
2 | 2015-01-06 | 3939.760000 |
3 | 2015-01-13 | 3515.435000 |
4 | 2015-01-13 | 3515.435000 |
5 | 2015-01-13 | 3515.435000 |
6 | 2015-01-13 | 3515.435000 |
7 | 2015-01-20 | 2932.913333 |
8 | 2015-01-20 | 2932.913333 |
9 | 2015-02-11 | 2513.740000 |
# Segmented features
def segmented_features(dataframe):
# Average ticket per customer (mean Sales per Customer ID)
avg_ticket = dataframe.groupby('Customer ID')['Sales'].mean().reset_index(name='Avg_Ticket_Customer')
dataframe = dataframe.merge(avg_ticket, on='Customer ID', how='left')
# Total sales per region (sum of Sales per Region)
sales_region = dataframe.groupby('Region')['Sales'].sum().reset_index(name='Sales_per_Region')
dataframe = dataframe.merge(sales_region, on='Region', how='left')
return dataframe
df_typical = segmented_features(df_typical)
df_outliers = segmented_features(df_outliers)
# Checking
df_typical[['Customer ID','Avg_Ticket_Customer','Region','Sales_per_Region']].head(10)
Customer ID | Avg_Ticket_Customer | Region | Sales_per_Region | |
---|---|---|---|---|
0 | DP-13000 | 59.834933 | Central | 179707.4458 |
1 | PO-19195 | 105.685800 | Central | 179707.4458 |
2 | PO-19195 | 105.685800 | Central | 179707.4458 |
3 | PO-19195 | 105.685800 | Central | 179707.4458 |
4 | MB-18085 | 89.007000 | East | 228163.3060 |
5 | ME-17320 | 127.699250 | South | 126025.0775 |
6 | JO-15145 | 102.945818 | South | 126025.0775 |
7 | ME-17320 | 127.699250 | South | 126025.0775 |
8 | ME-17320 | 127.699250 | South | 126025.0775 |
9 | LS-17230 | 61.480000 | West | 269677.1345 |
As colunas Sales_Moving_Avg
(média móvel de 7 dias), Avg_Ticket_Customer
(ticket médio por cliente) e Sales_per_Region
(vendas totais por região) foram criadas e integradas ao df
. Isso nos dá uma base sólida pra explorar tendências e segmentações.
Como a disposição dos dados foi tratada antes da feature engineering, as insconsistências e nulos não serão um problema. Podemos seguir para a análise temporal exploratória.
Iniciamos a Análise Temporal Exploratória pra revelar padrões de vendas no Superstore Dataset. O primeiro passo é visualizar as séries temporais de Sales
, comparando o dataset sem outliers (df_typical
) e com outliers (df_outliers
) lado a lado, pra entender tendências gerais e picos excepcionais.
Usaremos Plotly pra gráficos interativos, destacando o comportamento temporal.
from plotly.subplots import make_subplots
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Vendas Típicas', 'Vendas Excepcionais'))
# Plotting Sales for full dataset
fig.add_trace(
go.Scatter(x=df_typical['Order Date'], y=df_typical['Sales'], mode='lines', name='Vendas Típicas'),
row=1, col=1
)
# Plotting Sales for outliers dataset
fig.add_trace(
go.Scatter(x=df_outliers['Order Date'], y=df_outliers['Sales'], mode='lines', name='Vendas Excepcionais'),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Vendas ao Longo do Tempo: Típicas vs. Excepcionais",
height=500, width=1000,
showlegend=True
)
# Display the plot
fig.show()
O gráfico interativo mostra Sales
ao longo do tempo em df_typical
e df_outliers
lado a lado. A interatividade do Plotly permite explorar detalhes (ex.: datas específicas de alta).
Próximo passo: comparar Sales
com Sales_Moving_Avg
pra analisar tendências suavizadas em ambos os datasets.
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Média Móvel - Vendas Típicas', 'Média Móvel - Vendas Excepcionais'))
# Plotting Sales Moving Average for typical dataset
fig.add_trace(
go.Scatter(x=df_typical['Order Date'], y=df_typical['Sales_Moving_Avg'], mode='lines', name='Média Móvel (Típicas)'),
row=1, col=1
)
# Plotting Sales Moving Average for outliers dataset
fig.add_trace(
go.Scatter(x=df_outliers['Order Date'], y=df_outliers['Sales_Moving_Avg'], mode='lines', name='Média Móvel (Excepcionais)'),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Média Móvel de Vendas ao Longo do Tempo: Típicas vs. Excepcionais",
height=500, width=1000,
showlegend=True
)
# Display the plot
fig.show()
Analisando Sales_Moving_Avg
, observei que, em df_typical
, a média móvel das vendas cresce ao longo do tempo e dentro de cada ano: aumenta do início ao fim (pico em meses finais) e depois cai, com uma tendência geral de alta anual.
Já em df_outliers
, não há um padrão claro de crescimento contínuo — os outliers mostram picos pontuais em épocas específicas, semelhantes aos de Sales
, indicando que vendas excepcionais estão ligadas a eventos ou condições localizadas, não a uma evolução constante.
# Aggregating Sales by Order Month
def monthly_grouping(dataframe):
monthly_grouped = dataframe.groupby('Order Month')['Sales'].sum().reindex([
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
])
return monthly_grouped
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Vendas por Mês (Típicas)', 'Vendas por Mês (Excepcionais)'))
# Adding bars for typical dataset
fig.add_trace(
go.Bar(x=monthly_grouping(df_typical).index, y=monthly_grouping(df_typical).values, name='Vendas Típicas'),
row=1, col=1
)
# Adding bars for outliers dataset
fig.add_trace(
go.Bar(x=monthly_grouping(df_outliers).index, y=monthly_grouping(df_outliers).values, name='Vendas Excepcionais'),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Vendas por Mês: Típicas vs. Excepcionais",
height=500, width=1000,
showlegend=True
)
# Display the plot
fig.show()
# Aggregating Sales by Order Day of Week
def week_day(dataframe):
week_days = dataframe.groupby('Order Day of Week')['Sales'].sum().reindex([
'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
])
return week_days
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Sales por Dia da Semana (Típicas)', 'Sales por Dia da Semana (Excepcionais)'))
# Adding bars for typical dataset
fig.add_trace(
go.Bar(x=week_day(df_typical).index, y=week_day(df_typical).values, name='Vendas Típicas'),
row=1, col=1
)
# Adding bars for outliers dataset
fig.add_trace(
go.Bar(x=week_day(df_outliers).index, y=week_day(df_outliers).values, name='Vendas Excepcionais'),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Vendas por Dia da Semana: Típicas vs. Excepcionais",
height=500, width=1000,
showlegend=True
)
# Display the plot
fig.show()
Ao analisar as visualizações iniciais de vendas, média móvel, vendas por mês e por dia da semana, notei que os padrões entre df_typical
e df_outliers
não diferem tanto quanto esperado. Isso sugere que as vendas excepcionais podem não ser eventos isolados ou sazonais extremos, mas sim reflexos de clientes que gastam mais consistentemente.
Essa hipótese inicial justifica explorar visualizações focadas no comportamento de clientes e segmentações, pra entender se os outliers são estruturais (ex.: clientes habituais com ticket alto) ou circunstanciais (ex.: picos esporádicos).
Vamos explorar o ticket médio por cliente (Avg_Ticket_Customer
) ao longo do tempo em df_typical
e df_outliers
, pra testar se as vendas excepcionais vêm de clientes que gastam mais consistentemente. Usaremos linhas interativas com Plotly pra comparar tendências temporais nos dois datasets.
# Aggregating Avg_Ticket_Customer by Order Date (mean per day)
typical_ticket_time = df_typical.groupby('Order Date')['Avg_Ticket_Customer'].mean()
outliers_ticket_time = df_outliers.groupby('Order Date')['Avg_Ticket_Customer'].mean()
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Ticket Médio por Cliente - Vendas Típicas', 'Ticket Médio por Cliente - Vendas Excepcionais'))
# Plotting ticket average over time for typical dataset
fig.add_trace(
go.Scatter(x=typical_ticket_time.index, y=typical_ticket_time.values, mode='lines', name='Ticket Médio (Típicas)'),
row=1, col=1
)
# Plotting ticket average over time for outliers dataset
fig.add_trace(
go.Scatter(x=outliers_ticket_time.index, y=outliers_ticket_time.values, mode='lines', name='Ticket Médio (Excepcionais)'),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Ticket Médio por Cliente ao Longo do Tempo: Típicas vs. Excepcionais",
height=500, width=1000,
showlegend=True,
xaxis_title="Data do Pedido",
yaxis_title="Ticket Médio (USD)"
)
# Display the plot
fig.show()
Seguimos a Análise Temporal Exploratória com as vendas por segmento de cliente (Segment
), comparando df_typical
e df_outliers
. Essa visualização busca identificar se um segmento específico domina as vendas excepcionais ou segue padrões distintos nas vendas típicas. Usaremos barras interativas com Plotly pra análise lado a lado.
# Aggregating Sales by Segment
typical_segment = df_typical.groupby('Segment')['Sales'].sum()
outliers_segment = df_outliers.groupby('Segment')['Sales'].sum()
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Vendas por Segmento - Típicas', 'Vendas por Segmento - Excepcionais'))
# Adding bars for typical dataset
fig.add_trace(
go.Bar(x=typical_segment.index, y=typical_segment.values, name='Vendas (Típicas)'),
row=1, col=1
)
# Adding bars for outliers dataset
fig.add_trace(
go.Bar(x=outliers_segment.index, y=outliers_segment.values, name='Vendas (Excepcionais)'),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Vendas por Segmento de Cliente: Típicas vs. Excepcionais",
height=500, width=1000,
showlegend=True,
yaxis_title="Vendas Totais (USD)"
)
# Display the plot
fig.show()
# Aggregating Sales by Segment
typical_delivery = df_typical.groupby('Delivery Time')['Sales'].sum()
outliers_delivery = df_outliers.groupby('Delivery Time')['Sales'].sum()
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Vendas por Tempo de Entrega - Típicas', 'Vendas por Tempo de Entrega - Excepcionais'))
# Adding bars for typical dataset
fig.add_trace(
go.Bar(x=typical_delivery.index, y=typical_delivery.values, name='Vendas (Típicas)'),
row=1, col=1
)
# Adding bars for outliers dataset
fig.add_trace(
go.Bar(x=outliers_delivery.index, y=outliers_delivery.values, name='Vendas (Excepcionais)'),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Vendas por Tempo de Entrega: Típicas vs. Excepcionais",
height=500, width=1000,
showlegend=True,
yaxis_title="Vendas Totais (USD)"
)
# Display the plot
fig.show()
# Aggregating Sales by Segment
typical_category = df_typical.groupby('Category')['Sales'].sum()
outliers_category = df_outliers.groupby('Category')['Sales'].sum()
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Vendas por Categoria - Típicas', 'Vendas por Categoria - Excepcionais'))
# Adding bars for typical dataset
fig.add_trace(
go.Bar(x=typical_category.index, y=typical_category.values, name='Vendas (Típicas)'),
row=1, col=1
)
# Adding bars for outliers dataset
fig.add_trace(
go.Bar(x=outliers_category.index, y=outliers_category.values, name='Vendas (Excepcionais)'),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Vendas por Categoria: Típicas vs. Excepcionais",
height=500, width=1000,
showlegend=True,
yaxis_title="Vendas Totais (USD)"
)
# Display the plot
fig.show()
Ao analisar vendas por categoria, identifiquei uma inversão significativa: em df_typical
, Office Supplies
lidera as vendas, seguido por Furniture
, com Technology
em último. Já em df_outliers
, Technology
é o primeiro, seguido por Furniture
, e Office Supplies
fica em último.
Essa diferença sugere que vendas excepcionais priorizam itens de tecnologia, enquanto vendas típicas dependem de suprimentos de escritório, justificando uma análise mais profunda por subcategorias.
# Aggregating Sales by Segment
typical_category = df_typical.groupby('Sub-Category')['Sales'].sum()
outliers_category = df_outliers.groupby('Sub-Category')['Sales'].sum()
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Vendas por Sub Categoria - Típicas', 'Vendas por Sub Categoria - Excepcionais'))
# Adding bars for typical dataset
fig.add_trace(
go.Bar(x=typical_category.index, y=typical_category.values, name='Vendas (Típicas)'),
row=1, col=1
)
# Adding bars for outliers dataset
fig.add_trace(
go.Bar(x=outliers_category.index, y=outliers_category.values, name='Vendas (Excepcionais)'),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Vendas por Sub Categoria: Típicas vs. Excepcionais",
height=500, width=1000,
showlegend=True,
yaxis_title="Vendas Totais (USD)"
)
# Display the plot
fig.show()
Alguns insights e padrões interessantes foram observados, vamos apenas verificar o impacto financeiro dos outliers comparado às vendas típicas:
# Summing all the values of sales from each dataset
df_outliers_sum = df_outliers['Sales'].sum()
df_typical_sum = df_typical['Sales'].sum()
df_outliers_sum
1448753.0769
df_typical_sum
803572.9638
# Aggregating Sales by Region
typical_region = df_typical.groupby('Region')['Sales'].sum()
outliers_region = df_outliers.groupby('Region')['Sales'].sum()
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Vendas por Região - Típicas', 'Vendas por Região - Excepcionais'))
# Adding bars for typical dataset
fig.add_trace(
go.Bar(x=typical_region.index, y=typical_region.values, name='Vendas (Típicas)'),
row=1, col=1
)
# Adding bars for outliers dataset
fig.add_trace(
go.Bar(x=outliers_region.index, y=outliers_region.values, name='Vendas (Excepcionais)'),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Vendas Totais por Região: Típicas vs. Excepcionais",
height=500, width=1000,
showlegend=True,
yaxis_title="Vendas Totais (USD)"
)
# Display the plot
fig.show()
# Aggregating Sales by Segment and Order Month
typical_segment_month = df_typical.groupby(['Segment', 'Order Month'])['Sales'].sum().unstack().reindex(
columns=['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
)
outliers_segment_month = df_outliers.groupby(['Segment', 'Order Month'])['Sales'].sum().unstack().reindex(
columns=['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
)
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Vendas por Segmento e Mês - Típicas', 'Vendas por Segmento e Mês - Excepcionais'))
# Adding traces for typical dataset
for segment in typical_segment_month.index:
fig.add_trace(
go.Bar(x=typical_segment_month.columns, y=typical_segment_month.loc[segment], name=f'{segment} (Típicas)'),
row=1, col=1
)
# Adding traces for outliers dataset
for segment in outliers_segment_month.index:
fig.add_trace(
go.Bar(x=outliers_segment_month.columns, y=outliers_segment_month.loc[segment], name=f'{segment} (Excepcionais)'),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Vendas por Segmento e Mês: Típicas vs. Excepcionais",
height=500, width=1000,
barmode='stack', # Stacked bars to show total per month
showlegend=True,
yaxis_title="Vendas Totais (USD)"
)
# Display the plot
fig.show()
# Calculating metrics by Segment
typical_metrics = df_typical.groupby('Segment')[['Avg_Ticket_Customer', 'Delivery Time']].mean()
outliers_metrics = df_outliers.groupby('Segment')[['Avg_Ticket_Customer', 'Delivery Time']].mean()
# Creating subplot layout (2 rows, 2 columns)
fig = make_subplots(rows=2, cols=2, subplot_titles=(
'Ticket Médio por Segmento - Típicas', 'Ticket Médio por Segmento - Excepcionais',
'Tempo de Entrega por Segmento - Típicas', 'Tempo de Entrega por Segmento - Excepcionais'
))
# Ticket Médio - Typical
fig.add_trace(
go.Bar(x=typical_metrics.index, y=typical_metrics['Avg_Ticket_Customer'], name='Ticket Médio (Típicas)'),
row=1, col=1
)
# Ticket Médio - Outliers
fig.add_trace(
go.Bar(x=outliers_metrics.index, y=outliers_metrics['Avg_Ticket_Customer'], name='Ticket Médio (Excepcionais)'),
row=1, col=2
)
# Delivery Time - Typical
fig.add_trace(
go.Bar(x=typical_metrics.index, y=typical_metrics['Delivery Time'], name='Tempo de Entrega (Típicas)'),
row=2, col=1
)
# Delivery Time - Outliers
fig.add_trace(
go.Bar(x=outliers_metrics.index, y=outliers_metrics['Delivery Time'], name='Tempo de Entrega (Excepcionais)'),
row=2, col=2
)
# Updating layout
fig.update_layout(
title_text="Métricas por Segmento: Típicas vs. Excepcionais",
height=700, width=1000,
showlegend=True
)
fig.update_yaxes(title_text="Ticket Médio (USD)", row=1, col=1)
fig.update_yaxes(title_text="Ticket Médio (USD)", row=1, col=2)
fig.update_yaxes(title_text="Tempo de Entrega (Dias)", row=2, col=1)
fig.update_yaxes(title_text="Tempo de Entrega (Dias)", row=2, col=2)
# Display the plot
fig.show()
# Aggregating Sales by Segment and Category
typical_segment_category = df_typical.groupby(['Segment', 'Category'])['Sales'].sum().unstack()
outliers_segment_category = df_outliers.groupby(['Segment', 'Category'])['Sales'].sum().unstack()
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Vendas por Segmento e Categoria - Típicas', 'Vendas por Segmento e Categoria - Excepcionais'))
# Heatmap for typical dataset
fig.add_trace(
go.Heatmap(
x=typical_segment_category.columns,
y=typical_segment_category.index,
z=typical_segment_category.values,
colorscale='Viridis',
name='Típicas'
),
row=1, col=1
)
# Heatmap for outliers dataset
fig.add_trace(
go.Heatmap(
x=outliers_segment_category.columns,
y=outliers_segment_category.index,
z=outliers_segment_category.values,
colorscale='Viridis',
name='Excepcionais'
),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Vendas por Segmento e Categoria: Típicas vs. Excepcionais",
height=500, width=1000,
showlegend=False
)
# Display the plot
fig.show()
Encerramos a Análise Exploratória Temporal e Segmentada com uma investigação abrangente em df_typical
e df_outliers
, cobrindo vendas ao longo do tempo, média móvel, vendas por mês, dia da semana, segmento, região, tempo de entrega, categorias e subcategorias. Inicialmente, padrões como sazonalidade, segmentos e tempos de entrega mostraram semelhanças entre vendas típicas e excepcionais, sugerindo que os outliers não têm gatilhos temporais ou operacionais óbvios. Ainda assim, oportunidades como promoções sazonais ou otimização de entregas permanecem viáveis.
A análise por categorias revelou uma inversão significativa: em df_typical
, Office Supplies
lidera, seguido por Furniture
e Technology
em último; em df_outliers
, Technology
é o primeiro, seguido por Furniture
e Office Supplies
em último. Os outliers geram quase o dobro da receita das típicas, com subcategorias líderes (ex.: itens de tecnologia) apresentando ganhos discrepantes e recompensadores. Na análise detalhada por subgrupos, cruzando Segment
com meses e categorias, a proporção de vendas entre segmentos se mantém similar entre os datasets, mas o ticket médio (Avg_Ticket_Customer
) e o poder aquisitivo dos clientes em df_outliers
são significativamente maiores.
Apesar do impacto financeiro dos outliers, a maior fonte de renda total vem de clientes normais (Consumer
), destacando sua relevância no volume de vendas típicas. Esses insights sugerem que estratégias focadas em subcategorias de tecnologia para clientes de alto ticket podem maximizar lucros, enquanto o segmento Consumer
sustenta a base de receita. Com isso, finalizamos esta seção e avançamos pra Análise Bivariada e Multivariada, explorando relações entre variáveis pra refinar essas descobertas.
Iniciamos a Análise Bivariada e Multivariada explorando a relação entre Sales
, Segment
e Category
em df_typical
e df_outliers
. Usaremos barras agrupadas com Plotly pra visualizar como as vendas variam por segmento de cliente (Consumer, Corporate, Home Office) dentro de cada categoria de produto, comparando padrões típicos e excepcionais.
# Aggregating Sales by Segment and Category
typical_segment_category = df_typical.groupby(['Segment', 'Category'])['Sales'].sum().unstack()
outliers_segment_category = df_outliers.groupby(['Segment', 'Category'])['Sales'].sum().unstack()
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Vendas por Segmento e Categoria - Típicas', 'Vendas por Segmento e Categoria - Excepcionais'))
# Adding grouped bars for typical dataset
for category in typical_segment_category.columns:
fig.add_trace(
go.Bar(x=typical_segment_category.index, y=typical_segment_category[category], name=f'{category} (Típicas)'),
row=1, col=1
)
# Adding grouped bars for outliers dataset
for category in outliers_segment_category.columns:
fig.add_trace(
go.Bar(x=outliers_segment_category.index, y=outliers_segment_category[category], name=f'{category} (Excepcionais)'),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Vendas por Segmento e Categoria: Típicas vs. Excepcionais",
height=500, width=1100,
barmode='group', # Grouped bars for comparison
showlegend=True,
yaxis_title="Vendas Totais (USD)"
)
# Display the plot
fig.show()
Examinaremos a interação multivariada entre Sales
, Region
e Category
em df_typical
e df_outliers
. Um heatmap com Plotly será usado pra visualizar o total de vendas por região e categoria, destacando combinações de alto desempenho e diferenças entre vendas típicas e excepcionais.
# Aggregating Sales by Region and Category
typical_region_category = df_typical.groupby(['Region', 'Category'])['Sales'].sum().unstack()
outliers_region_category = df_outliers.groupby(['Region', 'Category'])['Sales'].sum().unstack()
# Creating subplot layout (1 row, 2 columns)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Vendas por Região e Categoria - Típicas', 'Vendas por Região e Categoria - Excepcionais'))
# Heatmap for typical dataset
fig.add_trace(
go.Heatmap(
x=typical_region_category.columns,
y=typical_region_category.index,
z=typical_region_category.values,
colorscale='Viridis',
name='Típicas'
),
row=1, col=1
)
# Heatmap for outliers dataset
fig.add_trace(
go.Heatmap(
x=outliers_region_category.columns,
y=outliers_region_category.index,
z=outliers_region_category.values,
colorscale='Viridis',
name='Excepcionais'
),
row=1, col=2
)
# Updating layout
fig.update_layout(
title_text="Vendas por Região e Categoria: Típicas vs. Excepcionais",
height=500, width=1000,
showlegend=False
)
# Display the plot
fig.show()
Finalizamos a Análise Bivariada e Multivariada com visualizações que cruzaram Sales
com variáveis como Segment
, Category
, Delivery Time
e Region
em df_typical
e df_outliers
. Os resultados reforçam padrões já observados: o segmento Consumer
é o principal responsável pelas vendas totais, tanto em vendas típicas quanto excepcionais, destacando sua relevância no volume de receita. Nas categorias, exceto Technology
, as porporções de vendas são semelhantes entre os datasets; porém, em Technology
, a diferença é discrepante, com df_outliers
gerando valores significativamente maiores, alinhando-se à preferência por itens de tecnologia nas vendas excepcionais.
Um novo insight surgiu ao analisar as regiões: West
e East
se destacam como as mais ativas em compras, tanto em df_typical
quanto em df_outliers
. Isso levanta questões pra investigação futura: será que essas regiões possuem sedes de lojas, taxas ou tempos de entrega menores, ou são áreas com maior concentração populacional em vez de comércio/empresas? Essas hipóteses podem guiar estratégias regionais. Com esses achados, encerramos esta seção e avançamos para Insights.
Sazonalidade oferece oportunidades de pico em vendas típicas:
df_typical
crescem ao longo do ano, com picos nos meses finais (ex.: novembro e dezembro), sugerindo que campanhas sazonais no fim do ano podem impulsionar a receita recorrente.Outliers geram quase o dobro da receita das vendas típicas:
Sales
em df_outliers
é cerca de 2x maior que em df_typical
, indicando que focar em clientes ou pedidos excepcionais pode dobrar o faturamento com menos volume.Technology é a chave dos outliers:
df_outliers
, Technology
lidera as vendas, enquanto em df_typical
fica em último (atrás de Office Supplies
e Furniture
), destacando que investimentos em produtos de tecnologia têm alto retorno em vendas excepcionais.Consumer é a maior fonte de renda total:
Consumer
domina o volume de vendas em ambos os datasets, revelando que estratégias amplas pra clientes individuais sustentam a base de receita.Ticket médio dos outliers é significativamente maior:
Avg_Ticket_Customer
em df_outliers
é muito superior ao de df_typical
(estável perto de 1200 USD vs. variações esporádicas), sugerindo que identificar e reter clientes de alto poder aquisitivo maximiza lucros por pedido.Subcategorias de tecnologia amplificam ganhos nos outliers:
df_outliers
(ex.: itens de tecnologia) geram receitas discrepantes e recompensadoras comparadas a df_typical
, indicando que promoções ou estoque focado nessas subcategorias podem alavancar vendas altas sem afetar as demais.West e East concentram atividade de compras:
West
e East
lideram vendas em df_typical
e df_outliers
, sugerindo que alocar mais recursos (ex.: estoque, marketing) nessas áreas pode aumentar o desempenho geral.Padrões semelhantes limitam diferenciação dos outliers:
df_typical
e df_outliers
, apontando que os gatilhos dos outliers estão mais nas categorias e clientes do que em fatores temporais ou operacionais.Office Supplies sustenta vendas típicas:
df_typical
, Office Supplies
é a categoria mais vendida, sugerindo que manter estoque consistente e promoções regulares nessa área garante estabilidade na receita base.Nesta etapa, reconhecemos o potencial do dataset processado pra integração com modelos de Machine Learning (ML), destacando as descobertas, o feature engineering realizado e as adaptações necessárias pra previsão ou classificação, como prever Sales
ou identificar outliers.
Sales_Moving_Avg
capturando tendências suavizadas.Consumer
domina volume, enquanto Technology
lidera outliers, com West
e East
como regiões-chave.Avg_Ticket_Customer
) muito maior.Segment
, Region
, Category
, Sub-Category
e Order Month
influenciam padrões de vendas.Order Day of Week
, Order Month
, Order Year
, Order Quarter
, Delivery Time
, Sales_Moving_Avg
.Avg_Ticket_Customer
, Sales_per_Region
.df_typical
e df_outliers
baseada em Sales
.Sales
, Delivery Time
) e categóricas já criadas pra análise.Segment
, Region
, Category
(baixa cardinalidade) e Target Encoding pra Sub-Category
(alta cardinalidade), evitando explosão de colunas.Sales
, Delivery Time
e Avg_Ticket_Customer
(ex.: Min-Max ou StandardScaler) pra modelos sensíveis a magnitude.Sales
com modelos robustos a valores extremos.Sales
com features categóricas codificadas e numéricas; Random Forest lida bem com variáveis categóricas e interações.Sales
, Category
e Region
, com suporte nativo a categóricas em algumas versões.Avg_Ticket_Customer
e Sales
, refinando segmentação.Sub-Category
em clusters (ex.: por vendas médias) antes da codificação.Category
sobre Region
).Sub-Category
em embeddings pra capturar relações sem one-hot encoding massivo.Com essas adaptações, o dataset está pronto pra suportar previsões de vendas, identificação de outliers ou segmentação avançada, reforçando estratégias de BI com ML/DL no futuro.
O Projeto explorou o Superstore Dataset com uma abordagem avançada de análise de dados, destacando padrões temporais e segmentados pra decisões de Business Intelligence. Por meio de limpeza, feature engineering e análises exploratórias, identificamos que:
Office Supplies
liderando e Consumer
como base de receita.Technology
e clientes de alto ticket médio, especialmente em West
e East
.O diferencial do projeto está na análise temporal detalhada (Sales_Moving_Avg
, Delivery Time
), segmentação robusta (Segment
, Region
, Category
) e uso de visualizações interativas com Plotly, tudo em df_typical
e df_outliers
. Embora um pipeline de automação não tenha sido implementado, o fluxo é reutilizável trocando o dataset na leitura, com potencial pra expansão futura.
A integração com Machine Learning foi mapeada, com features prontas pra previsão de Sales
ou clustering, adaptando variáveis categóricas pra modelos como Random Forest. Este trabalho entrega uma base sólida pra estratégias de vendas, desde promoções sazonais até foco em tecnologia e regiões-chave, pronta para aplicação real.